Reputation: 6349
React has a hook called useState, which is used when adding state to functional components.
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.
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 pass0
as initial state for our variable. (If we wanted to store two different values in state, we would calluseState()
twice.)
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.
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;
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?
Upvotes: 15
Views: 7326
Reputation: 14269
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> </> ); }
Upvotes: 16