10

I have a function called onRemove that is written like this:

const [todos, setTodos] = useState(todoData);

const onRemove = useCallback(
    (id) => {
      setTodos(todos.filter((todo) => todo.id !== id));
    },
    [todos]
  );

Then I noticed that changing it functional resulted in shorter rendering time.

const onRemove = useCallback(
    (id) => {
      setTodos(todos => todos.filter((todo) => todo.id !== id));
    },
    []
  );

My question is:

  1. Why does it render components quicker?
  2. What are the other advantages of using functional setState()?
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
D Park
  • 504
  • 10
  • 19
  • What is the rendering time if you add `todos` back into the dependency array of the second version? Perhaps to better test the performance of the state update you should unwrap any memoization that is occurring. – Drew Reese Apr 25 '20 at 04:17

3 Answers3

2

In the first case when you add todos as a dependency to useCallback, the function will be recreated again each time you call it since it is setting todos state itself and hence will not be optimized for memoization

In the second case, you are using the callback version os state updater which essentially will provide you the previous state as an argument to callback and the function will be only created once.

This should be the preferred approach

To know more about the advantages of functional setState, please check the linked post:

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
2

1) why does it render components quicker?

  • the performance gain of second code is not come from functional setState()
  • you use useCallback() to memorize the function onRemove(), so it won't be created every time the component re-render.
  • you pass no dependencies, so the function onRemove() will be created only once - when the component mounts

2) What are the other advantages of using functional setState()?

functional setState() is used to escape closure

  • use static state
import React, { useState, useEffect } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  console.log('render');

  useEffect(() => {
    console.log('componentDidUpdate')

    const id = setInterval(() => {
      setCount(count)  // count is always 0 because the variable count is closured
    }, 1000);

    return () => {
      clearInterval(id);
      console.log('clean');
    }
  }, []); // run only once when the component mounts

  return (
    <h1>
      {count}
    </h1>
  )
}

export default Counter;
  • use functional setState() to read fresh state
import React, { useState, useEffect } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);
  console.log('render');

  useEffect(() => {
    console.log('componentDidUpdate')

    const id = setInterval(() => {
      setCount(count => count + 1); // read fresh value of count
    }, 1000);

    return () => {
      clearInterval(id);
      console.log('clean');
    }
  }, []); // run only once when the component mounts

  return (
    <h1>
      {count}
    </h1>
  )
}

export default Counter;

Reference

ZenG
  • 139
  • 1
  • 3
0

When using hooks, you have to define an array of dependencies which when changes re-runs the function inside the hook.

In your first case its todos because you are using it to filter. Now what happens is when you setTodos with a new array your dependency todos changes which again re-runs and setTodos sets new todos. It actually runs indefinitely which is not what you want. You shouldn't define dependency to a hook whose value is being set inside it.

In your second case there aren't any dependency so even if you setTodos it won't re-run. The setTodos hook provides its current value in its callback parameter which you can use to avoid adding dependency. Also, this way is the right way to do it.

Raj
  • 116
  • 6