Skip to content

RFC: [Feature Request] Introduce useEffectAll hook for collective/transactional dependency updates #8466

@madhan-g-p

Description

@madhan-g-p

[Feature Request] Introduce useEffectAll hook for collective/transactional dependency updates

Summary

We propose a new built-in hook, useEffectAll, that changes the dependency evaluation model from OR to AND. Instead of executing the side-effect when any dependency changes, useEffectAll fires its callback strictly when every single dependency in the array has mutated compared to its state in the previous render cycle.

Motivation

In complex enterprise applications, data streams or unified configuration states often update asynchronously or incrementally. Currently, useEffect executes if a single element in the dependency array changes. This introduces performance bottlenecks when a high-overhead side-effect (like resetting a WebWorker pool, wiping an IndexedDB cache, or triggering heavy analytical underwriting computations) should only occur during a global paradigm shift meaning a transaction where all watched parameters are updated simultaneously.

Currently, developers must write boilerplate code using multiple useRef containers to preserve past states and manually loop through them to verify unanimous mutation. Providing a native, specialized primitive standardizes this architectural requirement.

Detailed Design

The API signature mirrors useEffect exactly, ensuring zero friction for adoption:

import { useEffectAll } from 'react';

useEffectAll(() => {
  // Heavy re-initialization or sync logic here
  console.log('All parameters updated simultaneously.');
  
  return () => {
    // Teardown logic
  };
}, [paramA, paramB, paramC]);

Core Behavior Matrix:

  • Initial Render: Establishes the reference baseline; the effect callback does not execute.
  • Partial State Update (e.g., paramA changes, paramB stays identical): The hook evaluates the mutation array, identifies that the change is not unanimous, updates internal references, and safely bypasses the callback execution.
  • Transactional State Update (e.g., paramA, paramB, and paramC all change): The hook confirms that 100% of the dependency indices have broken equality with their immediate previous states and triggers the effect callback.

Implementation Considerations & Equality

To match React’s core architecture, the default implementation should utilize Object.is for shallow equality comparisons.

However, since enterprise applications frequently pass objects or arrays as subset properties (e.g., [state.metrics, state.rules]), the documentation should encourage pairing this hook with identity-stable values, memoized primitives, or an explicit structural deep-comparison configuration if handling deeply nested data mutations.

How in Real world developers are implementing it now?

Mimicking real world full watch custom use hook

import { useEffect, useRef } from 'react';

// Native, optimized deep equality checker
function isDeepEqual(a, b) {
  if (Object.is(a, b)) return true;
  if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) return false;
  
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) return false;
    return a.every((val, index) => isDeepEqual(val, b[index]));
  }

  const keysA = Object.keys(a);
  const keysB = Object.keys(b);
  if (keysA.length !== keysB.length) return false;

  return keysA.every(key => Object.prototype.hasOwnProperty.call(b, key) && isDeepEqual(a[key], b[key]));
}

/**
 * useEffectAll
 * Strictly executes the callback ONLY when every single item in the deps array has changed.
 */
export function useEffectAll(effect, deps) {
  const prevDepsRef = useRef(null);

  useEffect(() => {
    // 1. Establish the baseline on initial mount
    if (prevDepsRef.current === null) {
      prevDepsRef.current = deps;
      return;
    }

    // 2. Strict evaluation: Has every single item broken deep equality?
    const allChanged = deps.every((currentDep, index) => {
      return !isDeepEqual(currentDep, prevDepsRef.current[index]);
    });

    // 3. Keep memory fresh for the next render loop
    prevDepsRef.current = deps;

    // 4. Fire the callback only if condition is completely met
    if (allChanged) {
      return effect();
    }
  }, deps);
}

Now this might be wrong , fundamentally may vary with React's principle. But I propose this to be have a steady adaptation among global engineers.

Drawbacks & Alternatives of Current Implementation

  • Performance Footgun: If developers pass unstable references that mutate on every render loop, the hook could trigger unexpectedly or execute unnecessary calculations. This risk is equivalent to standard useEffect misconfigurations.
  • Alternative: Developers can continue to build user-land custom hooks utilizing a collection of useRef instances and an array map loop. However, native implementation allows the React reconciler to optimize dependency checking directly within the fiber lifecycle.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions