简体   繁体   English

当 state 在 useEffect() 中更新时,我应该如何触发重新渲染

[英]How should I trigger a re-render when the state is updated inside useEffect()

I'm trying to figure out how I should structure my code with React hooks such that:我试图弄清楚我应该如何使用 React 钩子来构造我的代码,这样:

  1. I have a state variable that represents whether or not my app is "loading" results我有一个 state 变量,它表示我的应用程序是否正在“加载”结果
  2. This loading state is set to true when useEffect() runs to fetch data to displayloading state 在useEffect()运行以获取要显示的数据时设置为true
  3. At the end of useEffect() I set loading is set to falseuseEffect()结束时,我将loading设置为false
  4. In child components, this loading state is passed as a prop to change various components在子组件中,这个loading state作为prop传递来改变各种组件

A simplified model of my code is below:我的代码的简化 model 如下:

const App = () => {
   const [state, stateDispatch] = useReducer(stateReducer,{loading: false})
   useEffect(() => {
      stateDispatch({type:'loadingResults'});
      loadResults();
      stateDispatch({type:'finishedLoadingResults'});
   });
   return (
      <ExampleComponent loading={state.loading} />
   );
}

const stateReducer = (prevState,action) => {
    switch(action.type) {
      case 'loadingResults':
        return {...prevState, loading:true};
      case 'finishedLoadingResults':
        return {...prevState, loading: false};
      default:
        throw new Error();
    }

}

The example "child" component is below:示例“子”组件如下:

const SampleComponent = (props) => {
    const [buttonText, setButtonText] = useState('Load More');

    useEffect(() => {
        if (props.loading) {
            setButtonText('Loading...')
        } else {
            setButtonText('Load More');
        }
    },[props.loading]);

    return (
        <div onClick={(event) => props.getBooks(event,false)} className="moreResults">{buttonText}</div>
    )
}

With this current setup, I never see the child component re-rendered - even though it appears that the if (props.loading) is indeed evaluated correctly.使用当前的设置,我永远不会看到重新渲染子组件 - 即使看起来if (props.loading)确实被正确评估了。 For example, if I put a console.log inside that if check and the else check, I see both print out when the loading state is toggled.例如,如果我在 if check 和 else check 中放置一个console.log ,我会在loading state 切换时看到两者都打印出来。

Alternatively, if I define state.loading has a dependency for the useEffect() function in the App component, I get infinite re-renders since I'm toggling the loading state inside useEffect() .或者,如果我在App组件中定义state.loadinguseEffect() function 有依赖关系,我会得到无限的重新渲染,因为我正在切换loading Z9ED39E2EA931586B6E985Ef 内部useEffect()

Any idea what I'm missing here?知道我在这里缺少什么吗?

You don't need additional useEffect or useState in your child component.您的子组件中不需要额外useEffectuseState The prop is changing in the parent, so that's enough to rerender your component.父项中的道具正在发生变化,因此足以重新渲染您的组件。

const SampleComponent = (props) => (
  <div onClick={(event) => props.getBooks(event,false)}
   className="moreResults">{props.loading ? 'Loading...' : 'Load More'}
  </div>
)

A component decides to re-render only when any of its prop or state changes.组件仅在其任何 prop 或 state 更改时才决定重新渲染。

Javascript runs everything synchronously because of its single threaded nature. Javascript 由于其单线程特性而同步运行所有内容。 So the react diffing function to identify whether the component needs to be re-rendered or not will happen in synchronous order which is after the useEffect hook execution completes in this case.因此,react diffing function 以识别组件是否需要重新渲染将以同步顺序发生,即在这种情况下 useEffect 钩子执行完成之后。

useEffect(() => {
    stateDispatch({type:'loadingResults'}); 
    // -> state update happened
    // -> but re-render won't happen, since useEffect metho execution is not complete yet
    loadResults();
    stateDispatch({type:'finishedLoadingResults'});
 });

But by then you have finished loading and reset the state.但是到那时你已经完成加载并重置 state。 Since there is no change, the component won't re-render.由于没有变化,组件不会重新渲染。

Also you cannot set the state outside the hook because, any setState triggers will trigger a re-render which will again set the state and this will create an infinite loop.此外,您不能在钩子之外设置 state,因为任何 setState 触发器都会触发重新渲染,这将再次设置 state,这将创建一个无限循环。

Solution: Call the setState for loading inside an if condition outside of the useEffect block.Something like this.解决方案:调用 setState 以在 useEffect 块之外的 if 条件内加载。类似这样的东西。

const App = () => {
   const [state, stateDispatch] = useReducer(stateReducer,{loading: false})
   useEffect(() => {
      if(!state.loading) {
          loadResults();
          stateDispatch({type:'finishedLoadingResults'});
      }
   },[state.loading]);
   if(notResults) {
      stateDispatch({type:'loadingResults'});
   }
   return (
      <ExampleComponent loading={state.loading} />
   );
}
  • First of all, the useEffect in your App component has no dependency list, so it runs on every render.首先,你的 App 组件中的 useEffect 没有依赖列表,所以它在每次渲染时运行。 Since it sets the state, this causes a new re-render, after which your useEffect gets called again, and so on.. So you get an infinite loop.由于它设置了 state,这会导致新的重新渲染,之后您的 useEffect 会再次被调用,依此类推。所以您会得到一个无限循环。

    You must add a dependency list to the useEffect.您必须将依赖项列表添加到 useEffect。

  • Secondly, If your loadData function is an asynchronous function (which it should be if you're fetching data), it will return a promise, so you can dispatch "finishedLoadingResults" when the promise resolves. Secondly, If your loadData function is an asynchronous function (which it should be if you're fetching data), it will return a promise, so you can dispatch "finishedLoadingResults" when the promise resolves.

    This gives the following for your useEffect:这为您的 useEffect 提供了以下内容:

    useEffect(() => {
        stateDispatch({ type: "loadingResults" });
        loadResults()
        .then(() => stateDispatch({ type: "finishedLoadingResults" }));
    }, []);
  • Third, as jmargolisvt pointed out already, you don't need an extra useEffect inside your child component.第三,正如 jmargolisvt 已经指出的那样,您的子组件中不需要额外的 useEffect 。 When your App component sets the "loading" state, this will cause the App component together with the child component to re-render, so no need to use the buttonText state variable.当您的 App 组件设置“加载”state 时,这将导致 App 组件与子组件一起重新渲染,因此无需使用 buttonText state 变量。

You can see the working code in this Sandbox .您可以在此 Sandbox中看到工作代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 useEffect 中使用 setInterval 和更新 state 如何防止其他组件重新渲染 - Using setInterval and updating state inside useEffect how to prevent other component to re-render 更新请求后如何触发useEffect重新渲染 - How to trigger useEffect to re-render again after update request 使用 useEffect 通过更新 state 来触发重新渲染,但不能在依赖数组中包含 state? - Using useEffect to trigger a re-render by updating state, but cannot have state in dependency array? 组件未重新渲染时如何获取更新的 redux-toolkit state - How to get updated redux-toolkit state when component is not re-render <Redux>状态更新但不重新渲染 - <Redux> state updated but nor re-render 更新来自redux的道具后,react组件应如何重新呈现自身? - How should the react component re-render itself when props from redux have been updated? 更新子组件内的 state 时如何重新渲染子组件 - How to re Re-render a child component when updating the state inside the child component 当redux状态更改时,应该使用哪种React循环方法重新渲染组件? - Which React cycle method should I use to re-render component when redux state has changed? 当 state 更新时,组件不会使用更新的 state 重新渲染(反应) - When state is updated, component does not re-render with updated state (REACT) 当 object 已加载从 API 更新时,重新渲染 UseEffect 的最佳方法 - Best way to have UseEffect re-render when an object has loaded updated from an API
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM