如何在 state 和 props 更改时渲染组件? [英] How to render a component when state and props are changed?

查看:15
本文介绍了如何在 state 和 props 更改时渲染组件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要显示 props 值(这是一个简单的字符串).每次我得到新的搜索结果时,我都会发送 props.
在第一次渲染时,props 将始终是 undefined.


Header.jsx

function Header() {const [searchString, setString] = useState('');const onChangHandler = (e) =>{setString(e.target.value);};const activeSearch = () =>{如果(searchString.length > 0){<首页搜索结果={searchString}/>;}};返回 (<div><输入占位符='在这里搜索'值={搜索字符串}onChange={(e) =>onChangHandler(e)}/><button onClick={activeSearch}>Search</button></标题>

);}

我搜索了之前的 stackoverflow 问题和 reactjs.org,但没有找到答案.

Home.jsx

import React, { useEffect, useState } from 'react';功能主页({搜索结果}){const [itemSearchResults, setResults] = useState([]);const [previousValue, setPreviousValue] = useState();//当 props 第一次被定义或改变时,什么函数会重新渲染?useEffect(() => {//不起作用设置结果(搜索结果);}, [搜索结果]);返回 (<div><h3>主页</h3><h1>{itemSearchResults}</h1>

);}导出默认首页;

App.js

function App() {返回 (

<标题/><首页/><页脚/>

);}

我发送输入字符串只是为了检查 props 是否会在子组件(Home")发生变化.

这里的任何专家都知道是什么问题吗?

解决方案

为什么它不起作用?

这是因为 Home 组件从未使用过,即使它包含在以下代码段中:

<块引用>

const activeSearch = () =>{如果(searchString.length > 0){<首页搜索结果={searchString}/>;}};

activeSearch 函数有几个问题:

  • 它被用作事件处理程序,尽管它使用 JSX (在渲染阶段之外)
  • 它不返回 JSX (在渲染阶段仍然会失败)

JSX 应该只在 React 生命周期的渲染阶段使用.任何事件处理程序都存在于这个阶段之外,所以它可能使用的任何 JSX 都不会出现在最终的树中.

数据决定渲染什么

也就是说,解决方案是使用状态以便知道在渲染阶段要渲染什么.

function Header() {const [searchString, setString] = useState('');const [showResults, setShowResults] = useState(false);const onChangHandler = (e) =>{//避免每次字符更改都获取结果.设置显示结果(假);setString(e.target.value);};const activeSearch = () =>setShowResults(searchString.length > 0);返回 (<div><输入值={搜索字符串}onChange={(e) =>onChangHandler(e)}/><button onClick={activeSearch}>Search</button>{showResults &&<主页查询={searchString}/>}

);}

useEffect 根据道具变化触发效果

然后,Home 组件可以通过 useEffect 触发对某个服务的新搜索请求.

function Home({ 查询}) {const [results, setResults] = useState(null);useEffect(() => {让discardResult = false;fetchResults(query).then((response) => !discardResult && setResults(response));//此返回的函数将在查询更改之前和卸载时运行.返回 () =>{//防止前一个慢的结果的竞争条件//请求可以覆盖加载状态或来自的最新结果//更快的请求.丢弃结果 = 真;//每当查询更改时重置结果状态.设置结果(空);}}, [询问]);返回结果 ?(<ul>{results.map((result) => <li>{result}</li>))}</ul>) : `正在加载...`;}

确实,通过 useEffect文章重点:

<块引用>

useEffect(() => {设置内部状态(外部状态);}, [externalState]);

...但在我们的例子中,我们不是在同步状态,我们实际上是在触发一个效果(获取结果),这正是 useEffect 甚至存在的原因.

const { useState, useEffect } = React;const FAKE_DELAY = 5;//秒函数首页({查询}){const [results, setResults] = useState(null);useEffect(() => {让 queryChanged = false;console.log('获取搜索结果', 查询);setTimeout(() => {如果(查询更改){console.log('查询自上次获取以来已更改,结果被丢弃', 查询);返回;}setResults(['example', 'result', 'for', query])}, FAKE_DELAY * 1000);返回 () =>{//防止竞争条件查询更改 = 真;设置结果(空);};}, [询问]);返回 (<div>{结果 ?(<ul>{results.map((result) => (<li>{结果}</li>))}) : `正在加载...(${FAKE_DELAY} 秒)`}

);}函数头(){const [searchString, setString] = useState('');const [showResults, setShowResults] = useState(false);const onChangHandler = (e) =>{//避免每次字符更改都获取结果.设置显示结果(假);setString(e.target.value);};const activeSearch = () =>setShowResults(searchString.length > 0);返回 (<div><输入占位符='在这里搜索'值={搜索字符串}onChange={(e) =>onChangHandler(e)}/><button onClick={activeSearch}>Search</button>{showResults &&<主页查询={searchString}/>}

);}ReactDOM.render(

, document.querySelector("#app"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script><div id="app"></div>


更好的解决方案:不受控制的输入

您的情况的另一种技术是使用 不受控制的 使用 ref 并且仅在单击按钮时更新搜索字符串,而不是在更改输入值时更新.

function Header() {const [searchString, setString] = useState('');const inputRef = useRef();const activeSearch = () =>{setString(inputRef.current.value);}返回 (<div><输入引用={输入引用}/><button onClick={activeSearch}>Search</button>{searchString.length >0 &&<主页查询={searchString}/>}

);}

const { useState, useEffect, useRef } = React;const FAKE_DELAY = 5;//秒函数首页({查询}){const [results, setResults] = useState(null);useEffect(() => {让 queryChanged = false;console.log('获取搜索结果', 查询);setTimeout(() => {如果(查询更改){console.log('查询自上次获取以来已更改,结果被丢弃', 查询);返回;}setResults(['example', 'result', 'for', query])}, FAKE_DELAY * 1000);返回 () =>{//防止竞争条件查询更改 = 真;设置结果(空);};}, [询问]);返回 (<div>{结果 ?(<ul>{results.map((result) => (<li>{结果}</li>))}) : `正在加载...(${FAKE_DELAY} 秒)`}

);}函数头(){const [searchString, setString] = useState('');const inputRef = useRef();const activeSearch = () =>{setString(inputRef.current.value);}返回 (<div><输入占位符='在这里搜索'参考={输入参考}/><button onClick={activeSearch}>Search</button>{searchString.length >0 &&<主页查询={searchString}/>}

);}ReactDOM.render(

, document.querySelector("#app"))

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script><div id="app"></div>


传递状态

<块引用>

[下一行]将Home组件引入到Header组件中,使得重复

 {searchString.length >0 &&<主页查询={searchString}/>}

为了使 Header 组件可重用,最快的方法是 提升状态.

//这个组件不需要状态,我们现在接收//一个回调函数代替.函数头({ onSubmit }){const inputRef = useRef();const activeSearch = () =>{//使用回调函数而不是状态设置器.onSubmit(inputRef.current.value);}返回 (<div><输入引用={输入引用}/><button onClick={activeSearch}>Search</button>

);}功能应用(){//状态提升到父 (App) 组件.const [searchString, setString] = useState('');返回 (

<Header onSubmit={setString}/>{searchString.length >0 &&<主页查询={searchString}/>}<页脚/>

);}

如果该解决方案仍然太有限,那么还有其他方法可以传递数据,而这些数据将是题外话,以便在本答案中全部提出,因此我将链接更多信息:

I need to show the props value (which is a simple string). Each time I get new search results, I'm sending in the props.
At the very first render the props will always be undefined.

Edit:
Header.jsx

function Header() {
  const [searchString, setString] = useState('');
      
  const onChangHandler = (e) => {
    setString(e.target.value);
  };
  
  const activeSearch = () => {
    if (searchString.length > 0) {
      <Home searchResults={searchString} />;
    }
  };

  return (
    <div>
        <input
          placeholder='Search here'
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
      </header>
    </div>
  );
}

I searched for previous stackoverflow questions and reactjs.org but found no answer.

Home.jsx

import React, { useEffect, useState } from 'react';

function Home({ searchResults }) {
  const [itemSearchResults, setResults] = useState([]);
  const [previousValue, setPreviousValue] = useState();

  // What function will re-render when the props are first defined or changed ? 
   useEffect(() => { // Doesn't work
    setResults(searchResults);
   }, [searchResults]);
           
  return (
    <div>
      <h3>Home</h3>
      <h1>{itemSearchResults}</h1>
    </div>
  );
}

export default Home;

App.js

function App() {
  return (
    <div className='App'>
      <Header />
      <Home />
      <Footer />
    </div>
  );
}

I'm sending the input string only to check if the props will change at the child component ("Home").

Any experts here know what's the problem?

解决方案

Why it doesn't work?

It's because the Home component is never used, even if it's included in the following snippet:

const activeSearch = () => {
  if (searchString.length > 0) {
    <Home searchResults={searchString} />;
  }
};

The activeSearch function has a couple problems:

JSX should only be used within the render phase of React's lifecycle. Any event handler exists outside this phase, so any JSX it might use won't end up in the final tree.

The data dictates what to render

That said, the solution is to use the state in order to know what to render during the render phase.

function Header() {
  const [searchString, setString] = useState('');
  const [showResults, setShowResults] = useState(false);

  const onChangHandler = (e) => {
    // to avoid fetching results for every character change.
    setShowResults(false);
    setString(e.target.value);
  };

  const activeSearch = () => setShowResults(searchString.length > 0);

  return (
    <div>
        <input
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
        {showResults && <Home query={searchString} />}
    </div>
  );
}

useEffect to trigger effects based on changing props

And then, the Home component can trigger a new search request to some service through useEffect.

function Home({ query }) {
  const [results, setResults] = useState(null);

  useEffect(() => {
    let discardResult = false;

    fetchResults(query).then((response) => !discardResult && setResults(response));

    // This returned function will run before the query changes and on unmount.
    return () => {
      // Prevents a race-condition where the results from a previous slow
      // request could override the loading state or the latest results from
      // a faster request.
      discardResult = true;

      // Reset the results state whenever the query changes.
      setResults(null);
    }
  }, [query]);

  return results ? (
    <ul>{results.map((result) => <li>{result}</li>))}</ul>
  ) : `Loading...`;
}

It's true that it's not optimal to sync some state with props through useEffect like the article highlights:

useEffect(() => {
  setInternalState(externalState);
}, [externalState]);

...but in our case, we're not syncing state, we're literally triggering an effect (fetching results), the very reason why useEffect even exists.

const { useState, useEffect } = React;

const FAKE_DELAY = 5; // seconds

function Home({ query }) {
  const [results, setResults] = useState(null);
  
  useEffect(() => {
    let queryChanged = false;
    
    console.log('Fetch search results for', query);
    
    setTimeout(() => {
      
      if (queryChanged) {
        console.log('Query changed since last fetch, results discarded for', query);
        return;
      }
      setResults(['example', 'result', 'for', query])
    }, FAKE_DELAY * 1000);
    
    return () => {
      // Prevent race-condition
      queryChanged = true;
      setResults(null);
    };
  }, [query]);
  
  return (
    <div>
      {results ? (
        <ul>
          {results.map((result) => (
            <li>{result}</li>
          ))}
        </ul>
      ) : `Loading... (${FAKE_DELAY} seconds)`}
    </div>
  );
}

function Header() {
  const [searchString, setString] = useState('');
  const [showResults, setShowResults] = useState(false);

  const onChangHandler = (e) => {
    // to avoid fetching results for every character change.
    setShowResults(false);
    setString(e.target.value);
  };

  const activeSearch = () => setShowResults(searchString.length > 0);

  return (
    <div>
        <input
          placeholder='Search here'
          value={searchString}
          onChange={(e) => onChangHandler(e)}
        />
        <button onClick={activeSearch}>Search</button>
        {showResults && <Home query={searchString} />}
    </div>
  );
}

ReactDOM.render(<Header />, document.querySelector("#app"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

<div id="app"></div>


Better solution: Uncontrolled inputs

Another technique in your case would be to use an uncontrolled <input> by using a ref and only updating the search string on click of the button instead of on change of the input value.

function Header() {
  const [searchString, setString] = useState('');
  const inputRef = useRef();

  const activeSearch = () => {
    setString(inputRef.current.value);
  }

  return (
    <div>
        <input ref={inputRef} />
        <button onClick={activeSearch}>Search</button>
        {searchString.length > 0 && <Home query={searchString} />}
    </div>
  );
}

const { useState, useEffect, useRef } = React;

const FAKE_DELAY = 5; // seconds

function Home({ query }) {
  const [results, setResults] = useState(null);
  
  useEffect(() => {
    let queryChanged = false;
    
    console.log('Fetch search results for', query);
    
    setTimeout(() => {
      
      if (queryChanged) {
        console.log('Query changed since last fetch, results discarded for', query);
        return;
      }
      setResults(['example', 'result', 'for', query])
    }, FAKE_DELAY * 1000);
    
    return () => {
      // Prevent race-condition
      queryChanged = true;
      setResults(null);
    };
  }, [query]);
  
  return (
    <div>
      {results ? (
        <ul>
          {results.map((result) => (
            <li>{result}</li>
          ))}
        </ul>
      ) : `Loading... (${FAKE_DELAY} seconds)`}
    </div>
  );
}

function Header() {
  const [searchString, setString] = useState('');
  const inputRef = useRef();

  const activeSearch = () => {
    setString(inputRef.current.value);
  }

  return (
    <div>
        <input
          placeholder='Search here'
          ref={inputRef}
        />
        <button onClick={activeSearch}>Search</button>
        {searchString.length > 0 && <Home query={searchString} />}
    </div>
  );
}

ReactDOM.render(<Header />, document.querySelector("#app"))

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>

<div id="app"></div>


Passing the state around

[The following line] brings the Home component inside the Header component, which makes duplicate

 {searchString.length > 0 && <Home query={searchString} />}

In order to make the Header component reusable, the quickest way would be to lift the state up.

// No state needed in this component, we now receive
// a callback function instead.
function Header({ onSubmit }) {
  const inputRef = useRef();

  const activeSearch = () => {
    // Uses the callback function instead of a state setter.
    onSubmit(inputRef.current.value);
  }

  return (
    <div>
        <input ref={inputRef} />
        <button onClick={activeSearch}>Search</button>
    </div>
  );
}

function App() {
  // State lifted up to the parent (App) component.
  const [searchString, setString] = useState('');

  return (
    <div className='App'>
       <Header onSubmit={setString} />
       {searchString.length > 0 && <Home query={searchString} />}
       <Footer />
    </div> 
  );
}

If that solution is still too limited, there are other ways to pass data around which would be off-topic to bring them all up in this answer, so I'll link some more information instead:

这篇关于如何在 state 和 props 更改时渲染组件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆