Reputation: 1150
When dependencies of useEffect
hook change, the effect function of the useEffect
hook being invoked only after the render cycle (more discussed here). This frequently creates situations when UI become inconsistent due to inconsistency in the component model.
Here is a simple example where useEffect
used to run computation of resultValue
based on value of inputValue
:
function App() {
const [inputValue, incrementInputValue] = useReducer((s, _) => s + 1, 0);
const [resultValue, setResultValue] = useState(0);
useEffect(() => {
// It could be an asynchronous call to business logic etc.
setResultValue(inputValue * 2);
}, [inputValue]);
console.log("Render: %d * 2 = %d", inputValue, resultValue);
return (
<div className="App">
<p>{inputValue} * 2 = {resultValue}</p>
<button onClick={() => { console.log('Click'); incrementInputValue(undefined); }}>
Increment
</button>
</div>
);
}
This code returns the following log:
Render: 0 * 2 = 0
Click
Render: 1 * 2 = 0 // inputValue was updated, but useEffect not called yet
Render: 1 * 2 = 2
Click
Render: 2 * 2 = 2 // inputValue was updated, but useEffect not called yet
Render: 2 * 2 = 4
What would be the best approach to avoid such inconsistencies caused by delayed invocation of useEffect?
Upvotes: 1
Views: 235
Reputation: 53964
There is an entire lifecycle that runs after dispatching an action (incrementInputValue
), you won't make it consistent without "removing" this lifecycle:
incrementInputValue
)inputValue
)useEffect
callback duo to inputValue
change.resultValue
)Therefore as said, you need to get rid of phase 3
.
There are some solutions, you can use a reference, or combine it within a single state.
const reducer = (state, increase) => ({
inputValue: state.inputValue + increase,
resultValue: state.resultValue + increase * 2
});
const initial = { inputValue: 0, resultValue: 0 };
function App() {
const [state, increaseResult] = useReducer(reducer, initial);
useEffect(() => {
console.log(state);
});
return (
<div className="App">
<p>
{state.inputValue} * 2 = {state.resultValue}
</p>
<button
onClick={() => {
console.log('Click');
increaseResult(1);
}}
>
Increment
</button>
</div>
);
}
What is the best approach? It depends on the use case, its more a question of when and why using a reference instead of the state.
In this particular example one can argue that you can derive all logic with just division:
inputValue = resultValue / 2
.
Upvotes: 1