Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the 'useState' hook invoke the initial state when it's a function reference?

React has a hook called useState, which is used when adding state to functional components.

The Hooks API Reference states:

useState:

const [state, setState] = useState(initialState);

Returns a stateful value, and a function to update it.

During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).

The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.

The React Documentation states:

What do we pass to useState as an argument?

The only argument to the useState() Hook is the initial state. Unlike with classes, the state doesn’t have to be an object. We can keep a number or a string if that’s all we need. In our example, we just want a number for how many times the user clicked, so pass 0 as initial state for our variable. (If we wanted to store two different values in state, we would call useState()twice.)

Unexpected behaviour:

However, I've noticed some strange, seemingly undocumented, behaviour.

If I try to use the useState hook to store a function as state, react will invoke the function reference. e.g.

const arbitraryFunction = () => {
    console.log("I have been invoked!");
    return 100;
};

const MyComponent = () => {

    // Trying to store a string - works as expected:
    const [website, setWebsite] = useState("stackoverflow"); // Stores the string
    console.log(typeof website);                             // Prints "string"
    console.log(website);                                    // Prints "stackoverflow"

    // Trying to store a function - doesn't work as expected:
    const [fn, setFn] = useState(arbitraryFunction);         // Prints "I have been invoked!"
    console.log(typeof fn);                                  // Prints "number" (expecting "function")
    console.log(fn);                                         // Prints "100"

    return null; // Don't need to render anything for this example...
};

When we call useState(arbitraryFunction), react will invoke arbitraryFunction and use its return value as the state.

As a workaround:

We can store functions as state by wrapping our function reference in another function. e.g.

const [fn, setFn] = useState(() => arbitraryFunction)

I haven't yet come across any real-world reasons to store functions as state, but it seems weird that somebody made the explicit choice to treat function arguments differently.

This choice can be seen in multiple places throughout the React codebase:

initialState = typeof initialArg === 'function' ? initialArg() : initialArg;

Why does this seemingly undocumented feature exist?

I can't think of any good reasons why anybody would want/expect their function reference to be invoked, but perhaps you can.

If this is documented, where is it documented?

like image 314
byxor Avatar asked Jan 30 '20 10:01

byxor


People also ask

Can we initialize state from function React useState?

With the useState hook, you can pass a function as an argument to initialize the state lazily. As discussed, the initial value is needed only once at the first render. There is no point in performing this heavy computation on the subsequent renders.

How do I change my initial state using useState?

To set a conditional initial value for useState in React:Pass a function to the useState hook. Use a condition to determine the correct initial value for the state variable. The function will only be invoked on the initial render.

Does useState accept a function?

We initialize our state by calling useState in our function component. useState accepts an initial state and returns two values: The current state. A function that updates the state.

What is the purpose of useState when and why will you use it or manage state?

The useState() is a Hook that allows you to have state variables in functional components . so basically useState is the ability to encapsulate local state in a functional component.


1 Answers

This is documented here:

Lazy initial state

The initialState argument is the state used during the initial render. In subsequent renders, it is disregarded. If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

Passing a callback to setState also calls the callback, but for a different reason:

Functional updates

If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value. Here’s an example of a counter component that uses both forms of setState:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}
like image 167
tkausl Avatar answered Nov 15 '22 13:11

tkausl