reactjs / rfcs

RFCs for changes to React

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Feature Request]: distinguish "what" and "when" dependencies in useEffect

jsamr opened this issue · comments

EDIT: closed in favor of facebook/react#19820.

Feature

A new overloading for useEffect (Typescript syntax):

interface useEffect {
  /**
   * @param what - what this side effect does?
   * @param whatDeps - which variables modify “what” the side effect does?
   * These dependencies must match all live variables explicitly referenced
   * in the body of the “what” callback.
   * @param whenDeps - which variables modify “when” the side effect takes place?
   * When and only when at least one of those dependencies change, the “what”
   * callback should be executed.
   */
  (what: (...args: any[]) => any, whatDeps: any[], whenDeps: any[]): void;
  /**
   * @param what - what this side effect does?
   * @param deps - an array of values that the effect depends on.
   *
   */
  (what: (...args: any[]) => any, deps?: any[]): void;
}

Motivations

In the current implementation, the second argument of useEffect, “deps”, is described as such:

The array of values that the effect depends on.

This definition does not account for an important nuance between two kind of dependencies:

  • what dependencies, those which require the effect callback to be recomputed;
  • when dependencies, those which require the effect callback to be rerun.

The community seems to be in need of a solution, see https://stackoverflow.com/q/55724642/2779871.

Use case

I want to scroll to top of a component when the content changes (first dependency), but this effect also depends on a variable padding top (second dependency).

With the current implementation of useEffect, this is what I would do:

function MyComponent(props) {
  const { paddingTop, content } = props;
  const ref = React.useRef();
  React.useEffect(() => {
    // scroll to paddingTop when content changes?
    ref.current.scrollTo(0, paddingTop);
  }, [paddingTop, content]);
 return <div ref={ref}>...</div>
}

There is an undesired behavior: the hook is executed on paddingTop changes. Moreover, content is not, semantically, a dependency of the callback, but rather a dependency of when this side effect should take place. So I could use a ref, store the previous value of paddingTop, and compare the two. But that is cumbersome.

What I would like to do, is express the when this side-effect should take place dependencies declaratively:

function MyComponent(props) {
  const { paddingTop, content } = props;
  const ref = React.useRef();
  React.useEffect(() => {
    // scroll to paddingTop when content changes.
    ref.current.scrollTo(0, paddingTop);
  }, [paddingTop], [content]);
 return <div ref={ref}>...</div>
}

Detailed behavior

My understanding is that this proposal would not be a breaking change and is 100% retrocompatible with current implementation.

One argument

useEffect(what);

The behavior is identical to current implementation. The effect is executed after each render cycle.

Two arguments

useEffect(what, deps);

The behavior is identical to current implementation. The second argument conflates whatDeps and whenDeps.

Empty second argument

useEffect(what, []);

The behavior is identical to current implementation. The callback is executed only once.

Empty third argument

useEffect(what, whatDeps, []);

The callback is executed only once, regardless of the changes in whatDeps.

Three arguments

useEffect(what, whatDeps, whenDeps);

The callback is executed when and only when at least one variable in whenDeps array changes, regardless of the changes in whatDeps.

commented

If you don't want to write an RFC, please start a discussion in the React repo. We don't monitor "issues" in this repository, it's solely for pull requests.

@gaearon All right I'll do that. I honestly don't know if my proposition is realistic (the "drawbacks" section in the RFC template) and I would rather engage in a discussion prior to writing a RFC.