[英]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 钩子来构造我的代码,这样:
loading
state is set to true
when useEffect()
runs to fetch data to displayloading
state 在useEffect()
运行以获取要显示的数据时设置为true
useEffect()
I set loading
is set to false
useEffect()
结束时,我将loading
设置为false
loading
state is passed as a prop to change various componentsloading
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.loading
对useEffect()
function 有依赖关系,我会得到无限的重新渲染,因为我正在切换loading
Z9ED39E2EA931586B6E985Ef 内部useEffect()
。
Any idea what I'm missing here?知道我在这里缺少什么吗?
You don't need additional useEffect
or useState
in your child component.您的子组件中不需要额外
useEffect
或useState
。 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" }));
}, []);
You can see the working code in this Sandbox .您可以在此 Sandbox中看到工作代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.