Documentation defines usePrevious without a dependency array, why?

Asked
Active3 hr before
Viewed126 times

8 Answers

arraywithoutusepreviousdefinesdocumentation
90%

Don't be that account: buying and selling reputation and bounties ,Since useEffect is called without a dependency array, its function argument will be called for every render. Is it safe to limit this to only when value changes by adding value to the useEffect dependency array, like this:,I'm wondering if there is some scenario where ref.current must updated every render, which would explain the omission of [value] from the documentation.

Pretag
 Pretag team - issue, fix, solve, resolve
88%

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.,Why am I seeing stale props or state inside my function?,The first common use case is when creating the initial state is expensive:

function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
load more v
72%

Support custom comparator as third argument looks not bad:,The problem is it only compare array items with ===, it there any way to compare complex object ?,But obviously if hooks supported a custom comparator like React.memo as parameter, things will be a lot better/simpler.

useCallback(() => {
   doSth(a, b)
}, [a, b]) // how to do deep equal if a is an object ?
load more v
65%

Here's the crux of the issue: useEffect is not a lifecycle hook. It's a mechanism for synchronizing side effects with the state of your app.,One of the things I love about useEffect over lifecycles is it allows me to really separate concerns. Here's a quick example:,I've seen some people create a monster useEffect hook that does all the things:

Here's a class component implementation of that DogInfo component:

class DogInfo extends React.Component {  controller = null  state = {dog: null}  // we'll ignore error/loading states for brevity  fetchDog() {    this.controller?.abort()    this.controller = new AbortController()    getDog(this.props.dogId, {signal: this.controller.signal}).then(      (dog) => {        this.setState({dog})      },      (error) => {        // handle the error      },    )  }  componentDidMount() {    this.fetchDog()  }  componentDidUpdate(prevProps) {    // handle the dogId change    if (prevProps.dogId !== this.props.dogId) {      this.fetchDog()    }  }  componentWillUnmount() {    // cancel the request on unmount    this.controller?.abort()  }  render() {    return <div>{/* render dog's info */}</div>  }}
load more v
75%

Pretag
 Pretag team - issue, fix, solve, resolve
40%

Mixins and the class shares the same object. This means that if two mixins define a variable under the same name, the result may vary between compilation fails to unknown behavior.,For more explanation of how they are implemented, here's a great article about how they did it in React: https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e,The following defines a custom hook that creates a variable and logs its value on the console whenever the value changes:

StatefulWidget suffers from a big problem: it is very difficult to reuse the logic of say initState or dispose. An obvious example is AnimationController:

class Example extends StatefulWidget {
  final Duration duration;

  const Example({Key key, required this.duration})
      : super(key: key);

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  AnimationController? _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
  }

  @override
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.duration != oldWidget.duration) {
      _controller!.duration = widget.duration;
    }
  }

  @override
  void dispose() {
    _controller!.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

This library proposes a third solution:

class Example extends HookWidget {
   const Example({
      Key key,
      required this.duration
   }): super(key: key);

   final Duration duration;

   @override
   Widget build(BuildContext context) {
      final controller = useAnimationController(duration: duration);
      return Container();
   }
}

The same hook is reusable an infinite number of times The following code defines two independent AnimationController, and they are correctly preserved when the widget rebuild.

Widget build(BuildContext context) {
   final controller = useAnimationController();
   final controller2 = useAnimationController();
   return Container();
}

If this is still unclear, a naive implementation of hooks is the following:

class HookElement extends Element {
  List<HookState> _hooks;
  int _hookIndex;

  T use<T>(Hook<T> hook) => _hooks[_hookIndex++].build(this);

  @override
  performRebuild() {
    _hookIndex = 0;
    super.performRebuild();
  }
}
Widget build(BuildContext context) {
   // starts with `use`, good name
   useMyHook();
   // doesn't start with `use`, could confuse people into thinking that this isn't a hook
   myHook();
   // ....
}
Widget build(BuildContext context) {
   useMyHook();
   // ....
}
Widget build(BuildContext context) {
   if (condition) {
      useMyHook();
   }
   // ....
}

Consider the following list of hooks:

useA();
useB(0);
useC();

Then consider that after a hot-reload, we edited the parameter of HookB:

useA();
useB(42);
useC();

Now consider that we removed HookB. We now have:

useA();
useC();

The following defines a custom hook that creates a variable and logs its value on the console whenever the value changes:

ValueNotifier<T> useLoggedState<T>(BuildContext context, [T initialData]) {
  final result = useState<T>(initialData);
  useValueChanged(result.value, (_, __) {
    print(result.value);
  });
  return result;
}

When a hook becomes too complex, it is possible to convert it into a class that extends Hook, which can then be used using Hook.use.
As a class, the hook will look very similar to a State and have access to life-cycles and methods such as initHook, dispose and setState It is usually a good practice to hide the class under a function as such:

Result useMyHook(BuildContext context) {
   return use(const _TimeAlive());
}

The following defines a hook that prints the time a State has been alive.

class _TimeAlive extends Hook<void> {
  const _TimeAlive();

  @override
  _TimeAliveState createState() => _TimeAliveState();
}

class _TimeAliveState extends HookState<void, _TimeAlive> {
  DateTime start;

  @override
  void initHook() {
    super.initHook();
    start = DateTime.now();
  }

  @override
  void build(BuildContext context) {}

  @override
  void dispose() {
    print(DateTime.now().difference(start));
    super.dispose();
  }
}
load more v
22%

Pretag
 Pretag team - issue, fix, solve, resolve
60%

As we will see later, the useEffect Hook fosters separation of concerns and reduces code duplication. For example, the official React docs show that you can avoid the duplicated code that results from lifecycle methods with one useEffect statement.,After execution of every effect, scheduling of new effects occurs based on every effect’s dependencies. If an effect does not specify a dependency array at all, it means that this effect is executed after every render cycle.,In addition, it helps you to provide a correct dependency array for effects in order to prevent bugs.

Pretag
 Pretag team - issue, fix, solve, resolve

Other "array-without" queries related to "Documentation defines usePrevious without a dependency array, why?"