I have a functional component using Hooks:
function Component(props) { const [ items, setItems ] = useState([]); // In a callback Hook to prevent unnecessary re-renders const handleFetchItems = useCallback(() => { fetchItemsFromApi().then(setItems); }, []); // Fetch items on mount useEffect(() => { handleFetchItems(); }, []); // I want this effect to run only when 'props.itemId' changes, // not when 'items' changes useEffect(() => { if (items) { const item = items.find(item => item.id === props.itemId); console.log("Item changed to " item.name); } }, [ items, props.itemId ]) // Clicking the button should NOT log anything to console return ( <Button onClick={handleFetchItems}>Fetch items</Button> ); }
The component fetches some items
on mount and saves them to state.
The component receives an itemId
prop (from React Router).
Whenever the props.itemId
changes, I want this to trigger an effect, in this case logging it to console.
The problem is that, since the effect is also dependent on items
, the effect will also run whenever items
changes, for instance when the items
are re-fetched by pressing the button.
This can be fixed by storing the previous props.itemId
in a separate state variable and comparing the two, but this seems like a hack and adds boilerplate. Using Component classes this is solved by comparing current and previous props in componentDidUpdate
, but this is not possible using functional components, which is a requirement for using Hooks.
What is the best way to trigger an effect dependent on multiple parameters, only when one of the parameters change?
PS. Hooks are kind of a new thing, and I think we all are trying our best to figure out how to properly work with them, so if my way of thinking about this seems wrong or awkward to you, please point it out.
To solve the "react hook useeffect has a missing dependency" warning, you can: Add the missing dependency. Move the dependency inside the useEffect hook. Disable the ESLint rule.
If you don't specify it, the effect runs after each render. If it's empty ( [] ), the effect runs once, after the initial render.
React's useEffect Hook doesn't come with a feature to run it only on update, however, this custom hook should help you to accomplish it.
Side Effect Runs Only Once After Initial Render You can pass an empty array as the second argument to the useEffect hook to tackle this use case. useEffect(() => { // Side Effect }, []); In this case, the side effect runs only once after the initial render of the component.
Syntax useEffect(()=>{},[]); ()=>{} − Function passed to this hook [ ] − It tells the hook when to re-render the component. For example − [props] − If props values are changed then this hook is called again. [ ] − This hook will be called once only when the component is rendered to the screen. Example
This hook is used to set up subscriptions, handle side-effects or to send analytics data to the server. It is the combination of componentDidMount, componentDidUpdate and componentWillUnmount methods of class-based components. The function passed to this hook will run only after the component is rendered.
For example − [props] − If props values are changed then this hook is called again. [ ] − This hook will be called once only when the component is rendered to the screen. In this example, we will build a React application that displays the message when the child component is mounted in the DOM.
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
The React Team says that the best way to get prev values is to use useRef: https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
function Component(props) { const [ items, setItems ] = useState([]); const prevItemIdRef = useRef(); useEffect(() => { prevItemIdRef.current = props.itemId; }); const prevItemId = prevItemIdRef.current; // In a callback Hook to prevent unnecessary re-renders const handleFetchItems = useCallback(() => { fetchItemsFromApi().then(setItems); }, []); // Fetch items on mount useEffect(() => { handleFetchItems(); }, []); // I want this effect to run only when 'props.itemId' changes, // not when 'items' changes useEffect(() => { if(prevItemId !== props.itemId) { console.log('diff itemId'); } if (items) { const item = items.find(item => item.id === props.itemId); console.log("Item changed to " item.name); } }, [ items, props.itemId ]) // Clicking the button should NOT log anything to console return ( <Button onClick={handleFetchItems}>Fetch items</Button> ); }
I think that this could help in your case.
Note: if you don't need the previous value, another approach is to write one useEffect more for props.itemId
React.useEffect(() => { console.log('track changes for itemId'); }, [props.itemId]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With