Blogs under ‘Reading’ category mainly gives highlights/snippets of the topic post for my personal learning purposes (may also contain other learning notes on the side). For this topic in full, you can visit the original post here:
// During first render functionCounter() { const count = 0; // Returned by useState() // ... <p>You clicked {count} times</p> // ... }
// After a click, our function is called again functionCounter() { const count = 1; // Returned by useState() // ... <p>You clicked {count} times</p> // ... }
// After another click, our function is called again functionCounter() { const count = 2; // Returned by useState() // ... <p>You clicked {count} times</p> // ... }
When update the state (setCount), React calls component again with a different value for count (which is a constant inside the function). Each render = component gets called again with a differentcount value (which was set as a constant) for that particular render.
// During first render functionCounter() { // ... functionhandleAlertClick() { setTimeout(() => { alert('You clicked on: ' + 0); }, 3000); } // ... <button onClick={handleAlertClick} /> // The one with 0 inside // ... }
// After a click, our function is called again functionCounter() { // ... functionhandleAlertClick() { setTimeout(() => { alert('You clicked on: ' + 1); }, 3000); } // ... <button onClick={handleAlertClick} /> // The one with 1 inside // ... }
Above example: although the function is async, but the handler will still “see” the value at the time it is being called. Because inside each render, props and state and any values using them (including event handlers) “belongs” to that particular render, thus variables in their scope will only “see” the value for that particular component call.
// First render, props are {id: 10} functionExample() { // ... useEffect( // Effect from first render () => { ChatAPI.subscribeToFriendStatus(10, handleStatusChange); // Cleanup for effect from first render return () => { ChatAPI.unsubscribeFromFriendStatus(10, handleStatusChange); }; } ); // ... }
// Next render, props are {id: 20} functionExample() { // ... useEffect( // Effect from second render () => { ChatAPI.subscribeToFriendStatus(20, handleStatusChange); // Cleanup for effect from second render return () => { ChatAPI.unsubscribeFromFriendStatus(20, handleStatusChange); }; } ); // ... }
Cleanup function is called before the next render, values it “sees” = values captured in current render.
Synchronization
Not lifecycle. React is about the destination, not journey. React synchronizes DOM according to current state & props. Same goes with useEffect: allow for synchronize things outside of React tree according to current props & state. This is different from the mental model of mount/update/unmount (which is the journey), would fail to synchronize the results if attempt to write an effect that behaves differently depending on whether the component renders for the first time or not.
Diff Effects
Running all effects on every render = not efficient (could also lead to infinite loops). Fix by doing the following:
// React can't peek inside of functions, but it can compare deps. // Since all deps are the same, it doesn’t need to run the new effect.
Avoid re-running effect by providing dependency array, will check agains this array during every render, and run the effect if one (or more) of values in the array is different from the previous render.
Dependency Honesty
include all values insdie the component that are used inside the effect.
useEffect(() => { const id = setInterval(() => { dispatch({ type: 'tick' }); // Instead of setCount(c => c + step); }, 1000); return () => clearInterval(id); }, [dispatch]);
React guarantees the dispatch function to be constant throughout the component lifetime. (You may omit dispatch, setState, and useRef container values from the deps because React guarantees them to be static. But it also doesn’t hurt to specify them.) When setting a state variable depends on the current value of another state variable, replace both with useReducer. Consider useReducer for cases where setSomething(something => ...) is necessary. useReducer allow to decouple expressing the “actions” that happened in your component from how the state updates in response to them (decouple the update logic from describing what happened).
Functions in Effects
Move function directly into effect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
functionSearchResults() { // ... useEffect(() => { // We moved these functions inside! functiongetFetchUrl() { return'https://hn.algolia.com/api/v1/search?query=react'; } async functionfetchData() { const result = await axios(getFetchUrl()); setData(result.data); }
fetchData(); }, []); // ✅ Deps are OK // ... }
If use any state values inside the function, will need to add that state into the dependency array:
async functionfetchData() { const result = await axios(getFetchUrl()); setData(result.data); }
fetchData(); }, [query]); // ✅ Deps are OK
// ... }
If functions does not use any values from within the component scope, can hoist the functions to remove them from dependency array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// ✅ Not affected by the data flow function getFetchUrl(query) { return'https://hn.algolia.com/api/v1/search?query=' + query; }
functionSearchResults() { useEffect(() => { const url = getFetchUrl('react'); // ... Fetch data and do something ... }, []); // ✅ Deps are OK
useEffect(() => { const url = getFetchUrl('redux'); // ... Fetch data and do something ... }, []); // ✅ Deps are OK
// ... }
Can also use useCallback hook:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
functionSearchResults() { // ✅ Preserves identity when its own deps are the same const getFetchUrl = useCallback((query) => { return'https://hn.algolia.com/api/v1/search?query=' + query; }, []); // ✅ Callback deps are OK
useEffect(() => { const url = getFetchUrl('react'); // ... Fetch data and do something ... }, [getFetchUrl]); // ✅ Effect deps are OK
useEffect(() => { const url = getFetchUrl('redux'); // ... Fetch data and do something ... }, [getFetchUrl]); // ✅ Effect deps are OK
// ... }
useCallback = adding another layer of dependency checks – make the function itself change when necessary (instead of calling effect on every change).