• Jobs
  • About Us
  • professionals
    • Home
    • Jobs
    • Courses and challenges
  • business
    • Home
    • Post vacancy
    • Our process
    • Pricing
    • Assessments
    • Payroll
    • Blog
    • Sales
    • Salary Calculator

0

205
Views
Determine which dependency array variable caused useEffect hook to fire

Is there an easy way to determine which variable in a useEffect's dependency array triggers a function re-fire?

Simply logging out each variable can be misleading, if a is a function and b is an object they may appear the same when logged but actually be different and causing useEffect fires.

For example:

React.useEffect(() => {
  // which variable triggered this re-fire?
  console.log('---useEffect---')
}, [a, b, c, d])

My current method has been removing dependency variables one by one until I notice the behavior that causes excessive useEffect calls, but there must be a better way to narrow this down.

about 3 years ago · Santiago Trujillo
3 answers
Answer question

0

I ended up taking a little bit from various answers to make my own hook for this. I wanted the ability to just drop something in place of useEffect for quickly debugging what dependency was triggering useEffect.

const usePrevious = (value, initialValue) => {
  const ref = useRef(initialValue);
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
  const previousDeps = usePrevious(dependencies, []);

  const changedDeps = dependencies.reduce((accum, dependency, index) => {
    if (dependency !== previousDeps[index]) {
      const keyName = dependencyNames[index] || index;
      return {
        ...accum,
        [keyName]: {
          before: previousDeps[index],
          after: dependency
        }
      };
    }

    return accum;
  }, {});

  if (Object.keys(changedDeps).length) {
    console.log('[use-effect-debugger] ', changedDeps);
  }

  useEffect(effectHook, dependencies);
};

Below are two examples. For each example, I assume that dep2 changes from 'foo' to 'bar'. Example 1 shows the output without passing dependencyNames and Example 2 shows an example with dependencyNames.

Example 1

Before:

useEffect(() => {
  // useEffect code here... 
}, [dep1, dep2])

After:

useEffectDebugger(() => {
  // useEffect code here... 
}, [dep1, dep2])

Console output:

{
  1: {
    before: 'foo',
    after: 'bar'
  }
}

The object key '1' represents the index of the dependency that changed. Here, dep1 changed and is the 2nd item in the dependency, or index 1

Example 2

Before:

useEffect(() => {
  // useEffect code here... 
}, [dep1, dep2])

After:

useEffectDebugger(() => {
  // useEffect code here... 
}, [dep1, dep2], ['dep1', 'dep2'])

Console output:

{
  dep2: {
    before: 'foo',
    after: 'bar'
  }
}
about 3 years ago · Santiago Trujillo Report

0

This library... @simbathesailor/use-what-changed , works like a charm!

  1. Install with npm/yarn and --dev or --no-save
  2. Add import:
import { useWhatChanged } from '@simbathesailor/use-what-changed';
  1. Call it:
// (guarantee useEffect deps are in sync with useWhatChanged)
let deps = [a, b, c, d]

useWhatChanged(deps, 'a, b, c, d');
useEffect(() => {
  // your effect
}, deps);

Creates this nice chart in the console:

image loaded from github

There are two common culprits:

  1. Some Object being pass in like this:
// Being used like:
export function App() {
  return <MyComponent fetchOptions={{
    urlThing: '/foo',
    headerThing: 'FOO-BAR'
  })
}
export const MyComponent = ({fetchOptions}) => {
  const [someData, setSomeData] = useState()
  useEffect(() => {
    window.fetch(fetchOptions).then((data) => {
      setSomeData(data)
    })

  }, [fetchOptions])

  return <div>hello {someData.firstName}</div>
}

The fix in the object case, if you can, break-out a static object outside the component render:

const fetchSomeDataOptions = {
  urlThing: '/foo',
  headerThing: 'FOO-BAR'
}
export function App() {
  return <MyComponent fetchOptions={fetchSomeDataOptions} />
}

You can also wrap in useMemo:

export function App() {
  return <MyComponent fetchOptions={
    useMemo(
      () => {
        return {
          urlThing: '/foo',
          headerThing: 'FOO-BAR',
          variableThing: hash(someTimestamp)
        }
      },
      [hash, someTimestamp]
    )
  } />
}

The same concept applies to functions to an extent, except you can end up with stale closures.

about 3 years ago · Santiago Trujillo Report

0

UPDATE

After a little real-world use, I so far like the following solution which borrows some aspects of Retsam's solution:

const compareInputs = (inputKeys, oldInputs, newInputs) => {
  inputKeys.forEach(key => {
    const oldInput = oldInputs[key];
    const newInput = newInputs[key];
    if (oldInput !== newInput) {
      console.log("change detected", key, "old:", oldInput, "new:", newInput);
    }
  });
};
const useDependenciesDebugger = inputs => {
  const oldInputsRef = useRef(inputs);
  const inputValuesArray = Object.values(inputs);
  const inputKeysArray = Object.keys(inputs);
  useMemo(() => {
    const oldInputs = oldInputsRef.current;

    compareInputs(inputKeysArray, oldInputs, inputs);

    oldInputsRef.current = inputs;
  }, inputValuesArray); // eslint-disable-line react-hooks/exhaustive-deps
};

This can then be used by copying a dependency array literal and just changing it to be an object literal:

useDependenciesDebugger({ state1, state2 });

This allows the logging to know the names of the variables without any separate parameter for that purpose.

Edit useDependenciesDebugger

about 3 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Our process Sales
Legal
Terms and conditions Privacy policy
© 2025 PeakU Inc. All Rights Reserved.

Andres GPT

Recommend me some offers
I have an error