[Feature Request]: distinguish "what" and "when" dependencies in useEffect · Issue #176 · reactjs/rfcs (original) (raw)
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
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
Detailed behavior
My understanding is that this proposal would not be a breaking change and is 100% retrocompatible with current implementation.
One argument
The behavior is identical to current implementation. The effect is executed after each render cycle.
Two arguments
The behavior is identical to current implementation. The second argument conflates whatDeps and whenDeps.
Empty second argument
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.