1

I often find myself needing to prevent useEffect from running on the initial render and only triggering it when dependencies change. To make this easier, I want to create a custom hook that handles this. How can I create such a hook in React?

This is my code:

import React, { useState, useEffect } from 'react'; export default function Counter() { const [count, setCount] = useState(0); useEffect(() => { // i want to save the count variable only when count is updated localStorage.setItem('count', count + '') // but it set the localStorage count to 0, making my code useless }, [count]); useEffect(() => { // load save count if available let count = localStorage.getItem('count') if(count) { setCount(parseInt(count)) } }, []) return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } 

This code saves count into the localstorage every time the component loads, so count is set to 0 every time, and when its time to load the count variable from the localStorage, it reads 0

How can I create a React hook to run effectful code only when one of the deps change?

2
  • 1
    I'd suggest you to take a look at react-use package. It has useUpdateEffect hook that does what you want. You can also check its sources to see how that was achieved: github.com/streamich/react-use/blob/master/src/… Commented Jun 12, 2024 at 13:11
  • Thanks! Post it as an answer so that I can mark it accepted. Commented Jun 12, 2024 at 14:48

3 Answers 3

0

This sounds like an X/Y problem. If you want to only save the count variable when it's updated, wrap setCount accordingly.

Something like this...

function useStateSavedToStorage(key, initial) { const [value, _setValue] = React.useState(() => { const valueFromStorage = localStorage.getItem(key); // Doesn't support storing the value `null`, but it's otherwise // tedious to know whether a value exists in local storage but is // null, or doesn't exist at all. if(valueFromStorage !== null) return valueFromStorage; // TODO: support the function form of useState's `initial` return initial; }); const setValue = React.useCallback((newValue) => { // TODO: support the function form for `setValue((x) => ...)` localStorage.setItem(key, newValue); _setValue(newValue); }, []); return [value, setValue]; } 
Sign up to request clarification or add additional context in comments.

Comments

0

You can simply return false if count is equal to 0 (first useEffect call on mount)

 useEffect(() => { if(!count){ return } //.... }, [count]) 

Comments

0

You want a ref to track whether the component has mounted.

The Hook

import { useEffect, useRef } from 'react'; export function useUpdateEffect(callback, dependencies) { // Ref to track the initial mount const isFirstRender = useRef(true); useEffect(() => { // Skip the effect on the first render if (isFirstRender.current) { isFirstRender.current = false; return; } // Run the callback on subsequent renders when dependencies change return callback(); }, dependencies); } 

Example Usage

The useUpdateEffect hook can be used similarly to useEffect, but it will skip execution on the component's initial render due to the if statement at the top

import React, { useState } from 'react'; import useUpdateEffect from './useUpdateEffect'; function ExampleComponent() { const [count, setCount] = useState(0); useUpdateEffect(() => { // This code will not run on the first render console.log('Count has changed:', count); }, [count]); return ( <div> <h1>{count}</h1> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } 

If you're wondering about why I decided to return the callback, this is for whenever you need to run a clean up when the component unmounts to prevent a memory leak.

Comments