D Park
D Park

Reputation: 514

What is the advantage of passing function to setState() in React?

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()?

Upvotes: 10

Views: 10450

Answers (3)

ZenG
ZenG

Reputation: 169

1) why does it render components quicker?

  • the performance gain of second code is not come from functional setState()
  • you use useCallback() to memoize 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

Upvotes: 2

Raj
Raj

Reputation: 116

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.

Upvotes: 0

Shubham Khatri
Shubham Khatri

Reputation: 282160

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:

Upvotes: 2

Related Questions