ProgramGuru

Effect Dependencies: Stay Synchronized with Current Values

Effect Dependencies: Stay Synchronized with Current Values

An attendance page switches classes, but a title Effect keeps using 7A. The code reads the changing class while claiming that no changing dependency matters.

An Effect dependency is a reactive value used by its synchronization: a prop, state value or value created in the component body. Dependencies tell React when the synchronization may need to run again; they are determined by the code, not chosen to achieve a preferred call count.

  1. Understand

    Identify values read by an Effect.

  2. Build

    Keep a title current when its class changes.

  3. Check

    Refactor unstable dependencies instead of suppressing warnings.

Values used by synchronization define its inputs

  1. Read

    The Effect reads className.

  2. Compare

    React compares dependencies with Object.is.

  3. Re-synchronize

    A changed class triggers cleanup and setup.

Read this relationship alongside the complete code. This diagram explains the flow; it does not execute React.

The array describes the Effect’s changing inputs

With no dependency array, an Effect runs after each commit. An empty array says it has no reactive dependencies. An array such as [className] makes React compare that value with the previous committed dependency using Object.is. An unchanged primitive can skip another setup; a newly created object has a different identity even if its fields look equal.

The dependency linter helps find values your Effect reads. Removing className from the array does not stop className from changing; it stops the Effect from following that change. The resulting closure can keep the value from an earlier render, which is a synchronization bug.

Make the class an explicit dependency

The title-setting work reads className, so className belongs in the dependency array. The React state setter is stable and does not need an added dependency in this Effect. Clicking Choose 7B changes the rendered class and the tab title.

React · JavaScript with JSX

import { useEffect, useState } from 'react';
export default function App() {
  const [className, setClassName] = useState('7A');
  useEffect(() => {
    const oldTitle = document.title;
    document.title = 'Class ' + className;
    return () => { document.title = oldTitle; };
  }, [className]);
  return <main><h1>Current class</h1><p>{className}</p>
    <button onClick={() => setClassName('7B')}>Choose 7B</button></main>;
}

Expected screen and behavior

The page and tab title follow 7A, then 7B after the button click.

Follow the dependency across two commits

React · Make the class an explicit dependency

import { useEffect, useState } from 'react';
export default function App() {
  const [className, setClassName] = useState('7A');
  useEffect(() => {
    const oldTitle = document.title;
    document.title = 'Class ' + className;
    return () => { document.title = oldTitle; };
  }, [className]);
  return <main><h1>Current class</h1><p>{className}</p>
    <button onClick={() => setClassName('7B')}>Choose 7B</button></main>;
}

The first committed Effect sees 7A. After the event schedules 7B, the next render creates an Effect closure using 7B and an array containing 7B. React compares 7A and 7B, then runs the previous cleanup and the new setup.

If the array were empty, the new class would still appear in JSX, but the synchronization would not be requested for that changed class. A dependency is not a parameter passed into your callback: the callback reads its surrounding bindings from that render.

Remove unnecessary object dependencies through structure

Suppose an Effect needs an options object built from className. Creating that object in the component gives it a new identity on every render. If it is only used by the Effect, create it inside the Effect and depend on className itself. This expresses the actual input without adding an object solely to stabilize it.

For state updates that depend on previous state, a functional updater can avoid reading that state in a callback when appropriate. Do not blindly remove a dependency: first change the code so the Effect genuinely no longer reads the reactive value. Use memoization only when it is justified, not as a substitute for understanding.

Try the school task

React · starting example

import { useEffect, useState } from 'react';
export default function App() {
  const [className, setClassName] = useState('7A');
  useEffect(() => {
    const oldTitle = document.title;
    document.title = 'Class ' + className;
    return () => { document.title = oldTitle; };
  }, [className]);
  return <main><h1>Current class</h1><p>{className}</p>
    <button onClick={() => setClassName('7B')}>Choose 7B</button></main>;
}

Change the title to include the selected student and a report kind. Both values can change, so include both dependencies. Use separate buttons to observe each change.

Worked answer

The Effect reads student and kind and lists both. Either changed string causes the title to follow the current report. The displayed summary is still calculated directly from the same values.

React · JavaScript with JSX

import { useEffect, useState } from 'react';
export default function App() {
  const [student, setStudent] = useState('Maya');
  const [kind, setKind] = useState('Marks');
  useEffect(() => {
    const oldTitle = document.title;
    document.title = kind + ': ' + student;
    return () => { document.title = oldTitle; };
  }, [student, kind]);
  return <main><h1>Report inputs</h1><p>{kind}: {student}</p>
    <button onClick={() => setStudent('Ravi')}>Choose Ravi</button>
    <button onClick={() => setKind('Attendance')}>Attendance report</button></main>;
}

Expected screen and behavior

Choosing Ravi changes Marks: Maya to Marks: Ravi. Attendance report then changes the summary to Attendance: Ravi.

Mistakes and boundaries

Suppressing a dependency warning can hide stale values. Fix the underlying relationship, or explain a carefully supported nonreactive design rather than adding a disabling comment by default.

An Effect that updates a dependency on every setup can produce an update loop. Ask whether the state is derived or whether a separate event should perform the update.

Dependency arrays have a fixed number of entries written inline. They do not select which branch of a Hook runs; the Hook itself must follow the Rules of Hooks.

Check your reasoning

React · Make the class an explicit dependency

import { useEffect, useState } from 'react';
export default function App() {
  const [className, setClassName] = useState('7A');
  useEffect(() => {
    const oldTitle = document.title;
    document.title = 'Class ' + className;
    return () => { document.title = oldTitle; };
  }, [className]);
  return <main><h1>Current class</h1><p>{className}</p>
    <button onClick={() => setClassName('7B')}>Choose 7B</button></main>;
}

Choose the answer that follows the data and the interaction, then read the explanation for each alternative.

An Effect reads a changing student prop. What is wrong with an empty dependency array?
Choose one answer. You can try again.

Answer and explanation

It declares no reactive inputs even though this Effect uses one.

Correct. The declaration and actual dependency do not agree.

Carry the idea forward

Dependencies describe the values used by synchronization. Refactor the work when the inputs are wrong rather than silencing the linter. Review render snapshots and cleanup before the next setup.

Read the primary reference: React: removing Effect dependencies.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Effect Cleanup: Undo Subscriptions and Pending Work →