What is useCallback in React and when to use it? - javascript

I have gone through a couple of articles on useCallback and useMemo on when to use and when not to use but I have mostly seen very contrived code. I was looking at a code at my company where I have noticed someone have done this:
const takePhoto = useCallback(() => {
launchCamera({ mediaType: "photo", cameraType: "front" }, onPickImage);
}, []);
const pickPhotoFromLibrary = async () => {
launchImageLibrary({ mediaType: "photo" }, onPickImage);
}
const onUploadPress = useCallback(() => {
Alert.alert(
"Upload Photo",
"From where would you like to take your photo?",
[
{ text: "Camera", onPress: () => takePhoto() },
{ text: "Library", onPress: () => pickPhotoFromLibrary() },
]
);
}, [pickPhotoFromLibrary, takePhoto]);
This is how onUploadPress is called:
<TouchableOpacity
style={styles.retakeButton}
onPress={onUploadPress}
>
Do you think this is the correct way of calling it? Based on my understanding from those articles, this looks in-correct. Can someone tell me when to use useCallback and also maybe explain useCallback in more human terms?
Article I read: When to useMemo and useCallback.

useCallback returns a normal JavaScript function regarding how to use it. It is the same as the one it gets as first parameter regarding what it does. The difference is that this function doesn't get recreated on a new memory reference every time the component re-renders, while a normal function does. It gets recreated on a new reference if one of the variables inside useCalback's dependency array changes.
Now, why would you wanna bother with this? Well, It's worth it whenever the normal behavior of a function is problematic for you. For example, if you have that function in the dependency array of an useEffect, or if you pass it down to a component that is memoized with memo.
The callback of an useEffect gets called on the first render and every time one of the variables inside the dependency array changes. And since normally a new version of that function is created on every render, the callback might get called infinitely. So useCallback is used to memoize it.
A memoized component with memo re-renders only if its state or props changes, not because its parent re-renders. And since normally a new version of that passed function as props is created, when the parent re-renders, the child component gets a new reference, hence it re-renders. So useCallback is used to memoize it.
To illustrate, I created the below working React application. Click on that button to trigger re-renders of the parent and watch the console. Hope it clears things up!
const MemoizedChildWithMemoizedFunctionInProps = React.memo(
({ memoizedDummyFunction }) => {
console.log("MemoizedChildWithMemoizedFunctionInProps renders");
return <div></div>;
}
);
const MemoizedChildWithNonMemoizedFunctionInProps = React.memo(
({ nonMemoizedDummyFunction }) => {
console.log("MemoizedChildWithNonMemoizedFunctionInProps renders");
return <div></div>;
}
);
const NonMemoizedChild = () => {
console.log("Non memoized child renders");
return <div></div>;
};
const Parent = () => {
const [state, setState] = React.useState(true);
const nonMemoizedFunction = () => {};
const memoizedFunction = React.useCallback(() => {}, []);
React.useEffect(() => {
console.log("useEffect callback with nonMemoizedFunction runs");
}, [nonMemoizedFunction]);
React.useEffect(() => {
console.log("useEffect callback with memoizedFunction runs");
}, [memoizedFunction]);
console.clear();
console.log("Parent renders");
return (
<div>
<button onClick={() => setState((prev) => !prev)}>Toggle state</button>
<MemoizedChildWithMemoizedFunctionInProps
memoizedFunction={memoizedFunction}
/>
<MemoizedChildWithNonMemoizedFunctionInProps
nonMemoizedFunction={nonMemoizedFunction}
/>
<NonMemoizedChild />
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
It's to know that memoizing is not free, doing it wrong is worse than not having it. In your case, using useCallback for onUploadPress is a waste because a non memoized function, pickPhotoFromLibrary, is in the dependency array. Also, it's a waste if TouchableOpacity is not memoized with memo, which I'm not sure it's.
As a side note, there is useMemo, which behaves and is used like useCallback to memoize non-function but referenced values such as objects and arrays for the same reasons, or to memoize any result of a heavy calculation that you don't wanna repeat between renders.
A great resource to understand React render process in depth to know when to memorize and how to do it well: React Render.

In simple words, useCallback is used to save the function reference somewhere outside the component render so we could use the same reference again. That reference will be changed whenever one of the variables in the dependencies array changes.
As you know React tries to minimize the re-rendering process by watching some variables' value changes, then it decides to re-render not depending on the old-value and new-value of those variables.
So, the basic usage of useCallback is to hold old-value and the new-value equally.
I will try to demonstrate it more by giving some examples in situations we must use useCalback in.
Example 1: When the function is one of the dependencies array of the useEffect.
function Component(){
const [state, setState] = useState()
// Should use `useCallback`
function handleChange(input){
setState(...)
}
useEffect(()=>{
handleChange(...)
},[handleChange])
return ...
}
Example 2: When the function is being passed to one of the children components. Especially when it is being called on their useEffect hook, it leads to an infinite loop.
function Parent(){
const [state, setState] = useState()
function handleChange(input){
setState(...)
}
return <Child onChange={handleChange} />
}
function Child({onChange}){
const [state, setState] = useState()
useEffect(()=>{
onChange(...)
},[onChange])
return "Child"
}
Example 3: When you use React Context that holds a state and returns only the state setters functions, you need the consumer of that context to not rerender every time the state update as it may harm the performance.
const Context = React.createContext();
function ContextProvider({children}){
const [state, setState] = useState([]);
// Should use `useCallback`
const addToState = (input) => {
setState(prev => [...prev, input]);
}
// Should use `useCallback`
const removeFromState = (input) => {
setState(prev => prev.filter(elem => elem.id !== input.id));
}
// Should use `useCallback` with empty []
const getState = () => {
return state;
}
const contextValue= React.useMemo(
() => ({ addToState , removeFromState , getState}),
[addToState , removeFromState , getState]
);
// if we used `useCallback`, our contextValue will never change, and all the subscribers will not re-render
<Context.Provider value={contextValue}>
{children}
</Context.Provider>
}
Example 4: If you are subscribed to the observer, timer, document events, and need to unsubscribe when the component unmounts or for any other reason. So we need to access the same reference to unsubscribe from it.
function Component(){
// should use `useCallback`
const handler = () => {...}
useEffect(() => {
element.addEventListener(eventType, handler)
return () => element.removeEventListener(eventType, handler)
}, [eventType, element])
return ...
}
That's it, there are multiple situations you can use it too, but I hope these examples demonstrated the main idea behind useCallback. And always remember you don't need to use it if the cost of the re-render is negligible.

Related

React inputs stuck and not changing [duplicate]

The useEffect React hook will run the passed-in function on every change. This can be optimized to let it call only when the desired properties change.
What if I want to call an initialization function from componentDidMount and not call it again on changes? Let's say I want to load an entity, but the loading function doesn't need any data from the component. How can we make this using the useEffect hook?
class MyComponent extends React.PureComponent {
componentDidMount() {
loadDataOnlyOnce();
}
render() { ... }
}
With hooks this could look like this:
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire on every change :(
}, [...???]);
return (...);
}
If you only want to run the function given to useEffect after the initial render, you can give it an empty array as second argument.
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce();
}, []);
return <div> {/* ... */} </div>;
}
TL;DR
useEffect(yourCallback, []) - will trigger the callback only after the first render.
Detailed explanation
useEffect runs by default after every render of the component (thus causing an effect).
When placing useEffect in your component you tell React you want to run the callback as an effect. React will run the effect after rendering and after performing the DOM updates.
If you pass only a callback - the callback will run after each render.
If passing a second argument (array), React will run the callback after the first render and every time one of the elements in the array is changed. for example when placing useEffect(() => console.log('hello'), [someVar, someOtherVar]) - the callback will run after the first render and after any render that one of someVar or someOtherVar are changed.
By passing the second argument an empty array, React will compare after each render the array and will see nothing was changed, thus calling the callback only after the first render.
useMountEffect hook
Running a function only once after component mounts is such a common pattern that it justifies a hook of its own that hides implementation details.
const useMountEffect = (fun) => useEffect(fun, [])
Use it in any functional component.
function MyComponent() {
useMountEffect(function) // function will run only once after it has mounted.
return <div>...</div>;
}
About the useMountEffect hook
When using useEffect with a second array argument, React will run the callback after mounting (initial render) and after values in the array have changed. Since we pass an empty array, it will run only after mounting.
We have to stop thinking in component-life-cycle-methods (i.e. componentDidMount). We have to start thinking in effects. React effects are different from old-style class-life-cycle-methods.
By default effects run after every render cycle, but there are options to opt out from this behaviour. To opt out, you can define dependencies that mean that an effect is only carried out when a change to one of the dependencies is made.
If you explicitly define, that an effect has no dependecy, the effect runs only once, after the first render-cycle.
1st solution (with ESLint-complaint)
So, the first solution for your example would be the following:
function MyComponent() {
const loadDataOnlyOnce = () => {
console.log("loadDataOnlyOnce");
};
useEffect(() => {
loadDataOnlyOnce(); // this will fire only on first render
}, []);
return (...);
}
But then the React Hooks ESLint plugin will complain with something like that:
React Hook useEffect has missing dependency: loadDataOnlyOnce. Either include it or remove the dependency array.
At first this warning seems annoying, but please don't ignore it. It helps you code better and saves you from "stale closures". If you don't know what "stale closures" are, please read this great post.
2nd solution (the right way, if dependency is not dependent on component)
If we add loadDataOnlyOnce to the dependency array, our effect will run after every render-cycle, because the reference of loadDataOnlyOnce changes on every render, because the function is destroyed(garbarge-collected) and a new function is created, but that's exactly what we don't want.
We have to keep the same reference of loadDataOnlyOnce during render-cycles.
So just move the function-definition above:
const loadDataOnlyOnce = () => {
console.log("loadDataOnlyOnce");
};
function MyComponent() {
useEffect(() => {
loadDataOnlyOnce(); // this will fire only on first render
}, [loadDataOnlyOnce]);
return (...);
}
With this change you ensure that the reference of loadDataOnlyOnce will never change. Therefore you can also safely add the reference to the dependency array.
3rd solution (the right way, if dependency is dependent on component)
If the dependency of the effect (loadDataOnlyOnce), is dependent on the component (need props or state), there's React's builtin useCallback-Hook.
An elementary sense of the useCallback-Hook is to keep the reference of a function identical during render-cycles.
function MyComponent() {
const [state, setState] = useState("state");
const loadDataOnlyOnce = useCallback(() => {
console.log(`I need ${state}!!`);
}, [state]);
useEffect(() => {
loadDataOnlyOnce(); // this will fire only when loadDataOnlyOnce-reference changes
}, [loadDataOnlyOnce]);
return (...);
}
function useOnceCall(cb, condition = true) {
const isCalledRef = React.useRef(false);
React.useEffect(() => {
if (condition && !isCalledRef.current) {
isCalledRef.current = true;
cb();
}
}, [cb, condition]);
}
and use it.
useOnceCall(() => {
console.log('called');
})
or
useOnceCall(()=>{
console.log('Fetched Data');
}, isFetched);
Pass an empty array as the second argument to useEffect. This effectively tells React, quoting the docs:
This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
Here's a snippet which you can run to show that it works:
function App() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUser(data.results[0]);
});
}, []); // Pass empty array to only run once on mount.
return <div>
{user ? user.name.first : 'Loading...'}
</div>;
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
I like to define a mount function, it tricks EsLint in the same way useMount does and I find it more self-explanatory.
const mount = () => {
console.log('mounted')
// ...
const unmount = () => {
console.log('unmounted')
// ...
}
return unmount
}
useEffect(mount, [])
leave the dependency array blank . hope this will help you understand better.
useEffect(() => {
doSomething()
}, [])
empty dependency array runs Only Once, on Mount
useEffect(() => {
doSomething(value)
}, [value])
pass value as a dependency. if dependencies has changed since the last time, the effect will run again.
useEffect(() => {
doSomething(value)
})
no dependency. This gets called after every render.
I had this issue with React 18. This is how I handled it:
import { useEffect, useRef } from "react";
export default function Component() {
const isRunned = useRef(false);
useEffect(() => {
if(isRunned.current) return;
isRunned.current = true;
/* CODE THAT SHOULD RUN ONCE */
}, []);
return <div> content </div>;
}
Check here how they explain why useEffect is called more than once.
Here is my version of Yasin's answer.
import {useEffect, useRef} from 'react';
const useOnceEffect = (effect: () => void) => {
const initialRef = useRef(true);
useEffect(() => {
if (!initialRef.current) {
return;
}
initialRef.current = false;
effect();
}, [effect]);
};
export default useOnceEffect;
Usage:
useOnceEffect(
useCallback(() => {
nonHookFunc(deps1, deps2);
}, [deps1, deps2])
);
This does not answer your question exactly but may have the same intended affect of only running a function once and after the first render. Very similar to the componentDidMount function. This uses useState instead of useEffect to avoid dependency lint errors. You simply pass a self-executing anonymous function as the first argument to useState. As an aside, I'm not sure why React doesn't simply provide a hook that does this.
import React, { useState } from "react"
const Component = () => {
useState((() => {
console.log('componentDidMountHook...')
}))
return (
<div className='component'>Component</div>
)
}
export default Component
I found out after some time spend on the internet. useEffect fires once on component mount, then componennt unmounts and mounts again, useEffect fires again. You have to check more on React docs, why they do that.
So, I used custom hook for that. On unmount you have to change your useRef state. In this case do not forget a return statement: when component unmounts, useEffect runs cleanup function after return.
import React, { useEffect, useRef } from "react"
const useDidMountEffect = (
func: () => void,
deps: React.DependencyList | undefined
) => {
const didMount = useRef(false)
useEffect(() => {
if (didMount.current) {
func()
}
return () => {
didMount.current = true
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps)
}
export default useDidMountEffect
Use it like normal useEffect:
useDidMountEffect(() => {
// your action
}, [])
window.onpageshow works even if the user presses the back button to navigate to the page, unlike passing an empty array as second argument of the use-effect hook which does not fire when returning to the page via the back button (thus not on every form of initial page load).
useEffect(() => {
window.onpageshow = async function() {
setSomeState(false)
let results = await AsyncFunction()
console.log(results, 'Fires on on first load,
refresh, or coming to the page via the back button')
};
};
I found that with the once function from lodash the problem may be solved concisely and elegantly.
import { once } from "lodash";
import { useEffect, useRef } from "react";
export const useEffectOnce = (cb: () => void) => {
const callBackOnce = useRef(once(cb)).current;
useEffect(() => callBackOnce(), [callBackOnce]);
};
Incase you just call the function in useeffect after render you add an empty array as the second argument for the useeffect
useEffect=(()=>{
functionName(firstName,lastName);
},[firstName,lastName])

Should a setState function be a dependency of useEffect when passed via hook

So, I've stumbled upon this weird situation:
I have a global React Context provider, providing a global state, like so
const Context = createContext();
const ContextProvider = ({children}) => {
const [state, setState] = useState('');
return <Context.Provider value={{state, setState}}>{children}</Context.Provider>
}
const useMyState = () => {
const {state, setState} = useContext(Context);
return {
state,
setState
}
}
const Component = () => {
const {setState} = useMyState();
useEffect(() => {
elementRef.addEventListener('click', () => {
setState('someState');
});
return () => {
elementRef.removeEventListener('click', () => null);
}
},[])
return <>
// ...
</>
}
eslint suggests that my setState should be added to the useEffect's dependency array,
useEffect(() => {
elementRef.addEventListener('click', () => {
setState('someState');
});
},[setState])
I'm guessing that this might be somehow related to the destructuring of the context inside the useMyState.ts file
but that feels a bit weird and non-intuitive...
my question is is the setState really required inside the dependency array? and if so, why?
my question is is the setState really required inside the dependency array?
No, it isn't, but ESLint doesn't know that, because it has no way to know that the setState member of the context object you're using is stable. You know that (because the setter is guaranteed to be stable by useState, and you're passing it verbatim through context and your useMyState hook), but ESLint doesn't know that.
You can add it as a dependency to make ESLint happy (it won't make any difference if you're already providing an array, because the setter never changes; see below if you're not providing an array), or you can put in a comment to tell ESLint to skip checking that code, or you can turn the rule off (but it's very easy to miss out dependencies, so be careful if you do).
(If you're not providing an array [because you want the effect to run after every render], adding an array with the setter in it will stop that from happening, so you'll want to go with the option of disabling the ESLint error for that one situation. Or there are icky solutions like using a ref with an ever-increasing number value in it. :-) )
There is a problem with that code, though. It's repeatedly adding new event listeners to the element without ever removing them, because with no dependency array, the useEffect callback is called every time the component renders, and you're creating a new event handler function every time, so they'll stack up.
So you'll need to make elementRef.current a dependency, and you'll need a cleanup callback:
const Component = () => {
const {setState} = useMyState();
useEffect(() => {
const handler = () => {
setState("someState");
};
const element = elementRef.current;
// Note −−−−−−−−−−−−−−−−−−^^^^^^^^
element.addEventListener("click", handler);
return () => {
element.removeEventListener("click", handler);
};
}, [elementRef.current]); // <== Optionally add `setState` to this
return <>
// ...
</>;
};

React Hooks missing dependency with function passed from parent to child

I have this type of situation in my current React (v17.0.2) project that gives me 'React Hook useEffect has a missing dependency onTogglePopup' warning.
I need to pass a shared function from parent component to child component and there add an event listener to window when component 'mounts'. It currently does what I want but I believe there must be a better way to accomplish the same and make the linter happy.
I can neither include the function into dependency array nor remove the empty array since I'd loose the functionality.
How do I solve this situation properly without any anti-patterns, hacks or just plain hiding of linter warnings?
const App = ()=>{
const [showPopup, setShowPopup] = useState(false);
const onTogglePopup = ()=>{
setShowPopup(prev => !prev);
// do more App-related stuff here
}
return(
<main>
{/* other elements */}
{showPopup && <Popup onTogglePopup={onTogglePopup}/> }
</main>
)
}
const Popup = ({onTogglePopup})=>{
useEffect(()=>{
const onKeyDown = ev =>{
if (ev.key === 'Escape') onTogglePopup(); // the problem spot
}
window.addEventListener('keydown', onKeyDown);
// remove kbd listener on unmount
return ()=>{
window.removeEventListener('keydown', onKeyDown);
}
},[]);
return createPortal(
<aside id="popup">...
)
}
You can memoize onTogglePopup function before passing it to the child component as a prop.
To memoize the function, use the useCallback hook as shown below:
const onTogglePopup = useCallback(() => {
setShowPopup(prev => !prev);
// do more App-related stuff here
}, []);
Doing this will allow you to safely specify this function in the dependency array of the useEffect hook without causing unnecessary execution of the useEffect hook's callback function every time the parent component re-renders.
Note: useCallback hook also has a dependency array, so you need to be careful not to omit any of its dependencies to avoid the problems because of stale closure.
In your case, if setShowPopup is the only dependency of the useCallback hook, it can be included or excluded without any problem because setShowPopup is a state setter function whose reference is guaranteed to never change.
In App, instead of this:
const onTogglePopup = ()=>{
setShowPopup(prev => !prev);
// do more App-related stuff here
}
Use this:
const onTogglePopup = useCallback(()=>{
setShowPopup(prev => !prev);
// do more App-related stuff here
}, [setShowPopup]);
useCallback preserves the reference to the function unless dependencies change, so you don't trigger useEffect every render.

Why is the cleanup function from `useEffect` called on every render?

I've been learning React and I read that the function returned from useEffect is meant to do cleanup and React performs the cleanup when the component unmounts.
So I experimented with it a bit but found in the following example that the function was called every time the component re-renders as opposed to only the time it got unmounted from the DOM, i.e. it console.log("unmount"); every time the component re-renders.
Why is that?
function Something({ setShow }) {
const [array, setArray] = useState([]);
const myRef = useRef(null);
useEffect(() => {
const id = setInterval(() => {
setArray(array.concat("hello"));
}, 3000);
myRef.current = id;
return () => {
console.log("unmount");
clearInterval(myRef.current);
};
}, [array]);
const unmount = () => {
setShow(false);
};
return (
<div>
{array.map((item, index) => {
return (
<p key={index}>
{Array(index + 1)
.fill(item)
.join("")}
</p>
);
})}
<button onClick={() => unmount()}>close</button>
</div>
);
}
function App() {
const [show, setShow] = useState(true);
return show ? <Something setShow={setShow} /> : null;
}
Live example: https://codesandbox.io/s/vigilant-leavitt-z1jd2
React performs the cleanup when the component unmounts.
I'm not sure where you read this but this statement is incorrect. React performs the cleanup when the dependencies to that hook changes and the effect hook needs to run again with new values. This behaviour is intentional to maintain the reactivity of the view to changing data. Going off the official example, let's say an app subscribes to status updates from a friends' profile. Being the great friend you are, you are decide to unfriend them and befriend someone else. Now the app needs to unsubscribe from the previous friend's status updates and listen to updates from your new friend. This is natural and easy to achieve with the way useEffect works.
useEffect(() => {
chatAPI.subscribe(props.friend.id);
return () => chatAPI.unsubscribe(props.friend.id);
}, [ props.friend.id ])
By including the friend id in the dependency list, we can indicate that the hook needs to run only when the friend id changes.
In your example you have specified the array in the dependency list and you are changing the array at a set interval. Every time you change the array, the hook reruns.
You can achieve the correct functionality simply by removing the array from the dependency list and using the callback version of the setState hook. The callback version always operates on the previous version of the state, so there is no need to refresh the hook every time the array changes.
useEffect(() => {
const id = setInterval(() => setArray(array => [ ...array, "hello" ]), 3000);
return () => {
console.log("unmount");
clearInterval(id);
};
}, []);
Some additional feedback would be to use the id directly in clearInterval as the value is closed upon (captured) when you create the cleanup function. There is no need to save it to a ref.
The React docs have an explanation section exactly on this.
In short, the reason is because such design protects against stale data and update bugs.
The useEffect hook in React is designed to handle both the initial render and any subsequent renders (here's more about it).
Effects are controlled via their dependencies, not by the lifecycle of the component that uses them.
Anytime dependencies of an effect change, useEffect will cleanup the previous effect and run the new effect.
Such design is more predictable - each render has its own independent (pure) behavioral effect. This makes sure that the UI always shows the correct data (since the UI in React's mental model is a screenshot of the state for a particular render).
The way we control effects is through their dependencies.
To prevent cleanup from running on every render, we just have to not change the dependencies of the effect.
In your case concretely, the cleanup is happening because array is changing, i.e. Object.is(oldArray, newArray) === false
useEffect(() => {
// ...
}, [array]);
// ^^^^^ you're changing the dependency of the effect
You're causing this change with the following line:
useEffect(() => {
const id = setInterval(() => {
setArray(array.concat("hello")); // <-- changing the array changes the effect dep
}, 3000);
myRef.current = id;
return () => {
clearInterval(myRef.current);
};
}, [array]); // <-- the array is the effect dep
As others have said, the useEffect was depending on the changes of "array" that was specified in the 2nd parameter in the useEffect. So by setting it to empty array, that'd help to trigger useEffect once when the component mounted.
The trick here is to change the previous state of the Array.
setArray((arr) => arr.concat("hello"));
See below:
useEffect(() => {
const id = setInterval(() => {
setArray((arr) => arr.concat("hello"));
}, 3000);
myRef.current = id;
return () => {
console.log("unmount");
clearInterval(myRef.current);
};
}, []);
I forked your CodeSandbox for demonstration:
https://codesandbox.io/s/heuristic-maxwell-gcuf7?file=/src/index.js
Looking at the code I could guess its because of the second param [array]. You are updating it, so it will call a re-render. Try setting an empty array.
Every state update will call a re-render and unmount, and that array is changing.
It seems expected. As per the documentation here, useEffect is called after first render, every update and unmount.
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
Tip
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate and before
componentWillUnmount combined.
This is a Jest test that shows the render and effect order.
As you can see from the expect, once the dependency foo changes due to the state update it triggers a NEW render followed by the cleanup function of the first render.
it("with useEffect async set state and timeout and cleanup", async () => {
jest.useFakeTimers();
let theRenderCount = 0;
const trackFn = jest.fn((label: string) => { });
function MyComponent() {
const renderCount = theRenderCount;
const [foo, setFoo] = useState("foo");
useEffect(() => {
trackFn(`useEffect ${renderCount}`);
(async () => {
await new Promise<string>((resolve) =>
setTimeout(() => resolve("bar"), 5000)
);
setFoo("bar");
})();
return () => trackFn(`useEffect cleanup ${renderCount}`);
}, [foo]);
++theRenderCount;
trackFn(`render ${renderCount}`);
return <span data-testid="asdf">{foo}</span>;
}
const { unmount } = render(<MyComponent></MyComponent>);
expect(screen.getByTestId("asdf").textContent).toBe("foo");
jest.advanceTimersByTime(4999);
expect(screen.getByTestId("asdf").textContent).toBe("foo");
jest.advanceTimersByTime(1);
await waitFor(() =>
expect(screen.getByTestId("asdf").textContent).toBe("bar")
);
trackFn("before unmount");
unmount();
expect(trackFn.mock.calls).toEqual([
['render 0'],
['useEffect 0'],
['render 1'],
['useEffect cleanup 0'],
['useEffect 1'],
['before unmount'],
['useEffect cleanup 1']
])
});

How can I force a component to re-render with hooks in React?

Considering below hooks example
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Basically we use this.forceUpdate() method to force the component to re-render immediately in React class components like below example
class Test extends Component{
constructor(props){
super(props);
this.state = {
count:0,
count2: 100
}
this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component
}
setCount(){
let count = this.state.count;
count = count+1;
let count2 = this.state.count2;
count2 = count2+1;
this.setState({count});
this.forceUpdate();
//before below setState the component will re-render immediately when this.forceUpdate() is called
this.setState({count2: count
}
render(){
return (<div>
<span>Count: {this.state.count}></span>.
<button onClick={this.setCount}></button>
</div>
}
}
But my query is How can I force above functional component to re-render immediately with hooks?
This is possible with useState or useReducer, since useState uses useReducer internally:
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate isn't intended to be used under normal circumstances, only in testing or other outstanding cases. This situation may be addressed in a more conventional way.
setCount is an example of improperly used forceUpdate, setState is asynchronous for performance reasons and shouldn't be forced to be synchronous just because state updates weren't performed correctly. If a state relies on previously set state, this should be done with updater function,
If you need to set the state based on the previous state, read about the updater argument below.
<...>
Both state and props received by the updater function are guaranteed
to be up-to-date. The output of the updater is shallowly merged with
state.
setCount may not be an illustrative example because its purpose is unclear but this is the case for updater function:
setCount(){
this.setState(({count}) => ({ count: count + 1 }));
this.setState(({count2}) => ({ count2: count + 1 }));
this.setState(({count}) => ({ count2: count + 1 }));
}
This is translated 1:1 to hooks, with the exception that functions that are used as callbacks should better be memoized:
const [state, setState] = useState({ count: 0, count2: 100 });
const setCount = useCallback(() => {
setState(({count}) => ({ count: count + 1 }));
setState(({count2}) => ({ count2: count + 1 }));
setState(({count}) => ({ count2: count + 1 }));
}, []);
React Hooks FAQ official solution for forceUpdate:
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick={forceUpdate}>Force update</button>
Working example
const App = () => {
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
return (
<div>
<button onClick={forceUpdate}>Force update</button>
<p>Forced update {_} times</p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js" integrity="sha256-vMEjoeSlzpWvres5mDlxmSKxx6jAmDNY4zCt712YCI0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js" integrity="sha256-QQt6MpTdAD0DiPLhqhzVyPs1flIdstR4/R7x4GqCvZ4=" crossorigin="anonymous"></script>
<script>var useReducer = React.useReducer</script>
<div id="root"></div>
Generally, you can use any state handling approach you want to trigger an update.
With TypeScript
codesandbox example
useState
const forceUpdate: () => void = React.useState({})[1].bind(null, {}) // see NOTE below
useReducer (recommended)
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void
as custom hook
Just wrap whatever approach you prefer like this
function useForceUpdate(): () => void {
return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}
How this works?
"To trigger an update" means to tell React engine that some value has changed and that it should rerender your component.
[, setState] from useState() requires a parameter. We get rid of it by binding a fresh object {}.
() => ({}) in useReducer is a dummy reducer that returns a fresh object each time an action is dispatched.
{} (fresh object) is required so that it triggers an update by changing a reference in the state.
PS: useState just wraps useReducer internally, so use reducer to reduce complexity. source
NOTE: Referential instability
Using .bind with useState causes a change in function reference between renders.
It is possible to wrap it inside useCallback as already explained in this answer here, but then it wouldn't be a sexy one-liner™. The Reducer version already keeps reference equality (stability) between renders. This is important if you want to pass the forceUpdate function in props to another component.
plain JS
const forceUpdate = React.useState({})[1].bind(null, {}) // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
As the others have mentioned, useState works - here is how mobx-react-lite implements updates - you could do something similar.
Define a new hook, useForceUpdate -
import { useState, useCallback } from 'react'
export function useForceUpdate() {
const [, setTick] = useState(0);
const update = useCallback(() => {
setTick(tick => tick + 1);
}, [])
return update;
}
and use it in a component -
const forceUpdate = useForceUpdate();
if (...) {
forceUpdate(); // force re-render
}
See https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts and https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver.ts
Alternative to #MinhKha's answer:
It can be much cleaner with useReducer:
const [, forceUpdate] = useReducer(x => x + 1, 0);
Usage:
forceUpdate() - cleaner without params
You can simply define the useState like that:
const [, forceUpdate] = React.useState(0);
And usage: forceUpdate(n => !n)
Hope this help !
You should preferably only have your component depend on state and props and it will work as expected, but if you really need a function to force the component to re-render, you could use the useState hook and call the function when needed.
Example
const { useState, useEffect } = React;
function Foo() {
const [, forceUpdate] = useState();
useEffect(() => {
setTimeout(forceUpdate, 2000);
}, []);
return <div>{Date.now()}</div>;
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Simple code
const forceUpdate = React.useReducer(bool => !bool)[1];
Use:
forceUpdate();
Potential option is to force update only on specific component using key. Updating the key trigger a rendering of the component (which failed to update before)
For example:
const [tableKey, setTableKey] = useState(1);
...
useEffect(() => {
...
setTableKey(tableKey + 1);
}, [tableData]);
...
<DataTable
key={tableKey}
data={tableData}/>
You can (ab)use normal hooks to force a rerender by taking advantage of the fact that React doesn't print booleans in JSX code
// create a hook
const [forceRerender, setForceRerender] = React.useState(true);
// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);
// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
<div>{forceRerender}</div>
)
One line solution:
const useForceUpdate = () => useState()[1];
useState returns a pair of values: the current state and a function that updates it - state and setter, here we are using only the setter in order to force re-render.
react-tidy has a custom hook just for doing that called useRefresh:
import React from 'react'
import {useRefresh} from 'react-tidy'
function App() {
const refresh = useRefresh()
return (
<p>
The time is {new Date()} <button onClick={refresh}>Refresh</button>
</p>
)
}
Learn more about this hook
Disclaimer I am the writer of this library.
My variation of forceUpdate is not via a counter but rather via an object:
// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])
Because {} !== {} every time.
Solution in one single line:
const [,forceRender] = useReducer((s) => s+1, 0)
You can learn about useReducer here.
https://reactjs.org/docs/hooks-reference.html#usereducer
This will render depending components 3 times (arrays with equal elements aren't equal):
const [msg, setMsg] = useState([""])
setMsg(["test"])
setMsg(["test"])
setMsg(["test"])
const useForceRender = () => {
const [, forceRender] = useReducer(x => !x, true)
return forceRender
}
Usage
function Component () {
const forceRender = useForceRender()
useEffect(() => {
// ...
forceRender()
}, [])
For regular React Class based components, refer to React Docs for the forceUpdate api at this URL. The docs mention that:
Normally you should try to avoid all uses of forceUpdate() and only
read from this.props and this.state in render()
However, it is also mentioned in the docs that:
If your render() method depends on some other data, you can tell React
that the component needs re-rendering by calling forceUpdate().
So, although use cases for using forceUpdate might be rare, and I have not used it ever, however I have seen it used by other developers in some legacy corporate projects that I have worked on.
So, for the equivalent functionality for Functional Components, refer to the React Docs for HOOKS at this URL. Per the above URL, one can use the "useReducer" hook to provide a forceUpdate functionality for Functional Components.
A working code sample that does not use state or props is provided below, which is also available on CodeSandbox at this URL
import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
// Use the useRef hook to store a mutable value inside a functional component for the counter
let countref = useRef(0);
const [, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
countref.current++;
console.log("Count = ", countref.current);
forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
}
return (
<div className="App">
<h1> {new Date().toLocaleString()} </h1>
<h2>You clicked {countref.current} times</h2>
<button
onClick={() => {
handleClick();
}}
>
ClickToUpdateDateAndCount
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
NOTE: An alternate approach using the useState hook (instead of useReducer) is also available at this URL.
There are many ways to force re-render in Hook.
For me simple way with useState() and tip of reference object values.
const [, forceRender] = useState({});
// Anywhre
forceRender({});
Codesandbox Example
A bit late to the party but I notice that most (all) of the answers have missed the part where you can pass a callback to forceUpdate lifecycle method.
As per the react source code, this callback has the same behavior as the one in the setState method - it is executed after the update.
Hence, the most correct implementation would be like this:
/**
* Increments the state which causes a rerender and executes a callback
* #param {function} callback - callback to execute after state update
* #returns {function}
*/
export const useForceUpdate = (callback) => {
const [state, updater] = useReducer((x) => x + 1, 0);
useEffect(() => {
callback && callback();
}, [state]);
return useCallback(() => {
updater();
}, []);
};
I was working with an array and spotted this issue. However, instead of explicit forceUpdate I found another approach - to deconstruct an array and set a new value for it using this code:
setRoutes(arr => [...arr, newRoute]); // add new elements to the array
setRouteErrors(routeErrs => [...routeErrs]); // the array elements were changed
I found it very interesting that setting even a copy of an array will not trigger the hook. I assume React does the shallow comparison

Categories