Do you want to request a feature or report a bug?
Bug
What is the current behavior?
Cannot set functions in state using React Hooks.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
Using React Hooks (doesn't work): https://codesandbox.io/s/2zo9y8wmyj
Using this.state: https://codesandbox.io/s/j4rq63xn89
What is the expected behavior?
Not sure if this is the expected behavior, but ideally, one should be able to set whatever they want in state. The above example done using this.state: https://codesandbox.io/s/j4rq63xn89
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
16.7.0-alpha.0
@wawhal You have a typo in the sandbox.
@edgesoft my bad. I have fixed the typo. It still doesn't work.
@edgesoft yes that works but that is a workaround, not the expected behavior.
The following also works:
const [testFunc, setTestFunc] = useState(() => oldFunc);
However, it would be good to know if it is the intended behavior and if so, why.
@wawhal The reason for this unexpected behavior is the lazy evaluation.
https://reactjs.org/docs/hooks-reference.html#lazy-initialization
When you pass a function to useState
, it will be called, and the return value will be used as the initial state.
And when passing a function to the updater (in your case setTestFunc
), it will be called with the current value, and you need to return the new state from that.
This is not a bug but a weird behavior especially for new users who might not have read the API Docs
as thorough.
Ah. Got it. So if I want to set a function in the state, I need to provide a function that returns a function. It is really weird though, and not intuitive. I mean, I could call the function myself if I had to set the returned value. Is there a reason why I can't just directly set what I want to set?
Thanks for your input @HenriBeck
I think the lazy initialization makes sense, but if this is something you plan on doing often, why not write a custom hook! WOOO!
import { useState } from 'react';
export default function useFunctionAsState(fn) {
const [val, setVal] = useState(() => fn);
function setFunc(fn) {
setVal(() => fn);
}
return [val, setFunc];
}
Then wherever you want a state value that's always going to be a function:
import useFunctionAsState from '.hooks/useFunctionAsState';
function myComponent(props) {
const [myFunc, setMyFunc] = useFunctionAsState(() => console.log('woo! custom hooks!'));
}
The reason for the lazy initialization makes sense because otherwise, you would compute something on every render which can become a performance problem. I would advise making everything lazy evaluated in the initial state unless it's a primitive value like numbers, booleans, and strings, etc.
This is unfortunately intended to make other cases in the API simpler, though I admit it is a bit of a pain.
A cute "solution" to this is to wrap it in an array:
const [[filter], setFilter] = useState([x => x])
// elsewhere...
setFilter([x => !x])
I think the lazy initialization makes sense, but if this is something you plan on doing often, why not write a custom hook! WOOO!
import { useState } from 'react'; export default function useFunctionAsState(fn) { const [val, setVal] = useState(() => fn); function setFunc(fn) { setVal(() => fn); } return [val, setFunc]; }
Then wherever you want a state value that's always going to be a function:
import useFunctionAsState from '.hooks/useFunctionAsState'; function myComponent(props) { const [myFunc, setMyFunc] = useFunctionAsState(() => console.log('woo! custom hooks!')); }
minor tweak:
import { useState, useCallback } from 'react';
export default function useFunctionAsState(fn) {
const [val, setVal] = useState(() => fn);
const setFunc = useCallback((fn) => {
setVal(() => fn);
}, [])
return [val, setFunc];
}
just for useEffects to dont go crazy ^^
I also encountered this, as it was returning an array instead of a function and I couldn't figure out why. In the back of my head, I knew about useState() setters taking functions, but I didn't put the two together until I Googled.
I understand the purpose of using function, but it is surprising (principle of least surprise). I use functions often for sorting, for example.
Most helpful comment
I think the lazy initialization makes sense, but if this is something you plan on doing often, why not write a custom hook! WOOO!
Then wherever you want a state value that's always going to be a function: