-
Notifications
You must be signed in to change notification settings - Fork 47.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SetStateAction returned from useState hook dose not accept a second callback argument #14174
Comments
Is there a reason you can't |
Since I don't want to trigger callback action every time after dispatching a SetStateAction. But 'useEffect ' might cause the effect running every time after the state changed. |
Use a useRef for setting a flag saying you want the effect to run, or just do the effect directly in the function where you're calling setState, as you shouldn't be doing it in the render phase to begin with. |
Can you describe a more specific use case please? |
@gaearon
Basically, i'm running a setState on a component that has been unmounted due to the page change. Ideally, i would like to update the context (or any callback function from parent component for example), then once that process is done, run the callback that will apply the page change. This could've been done easily with the setState(state, [callback]). Please advise, thanks for your time! |
I found the solution, we can actually await the set function 👍 |
@therayess Where is it documented that the set function of useState returns a promise? |
It doesn't, you can technically await anything in js, but that doesn't really mean anything in this case, it's not waiting until the state flushes to resolve |
You are right, apologies for that, it worked for another reason, is there a way to achieve a state set callback with useState ? |
Being able to pass a callback to
Right now, it's possible to implement a Redux-like architecture with an added "hook" for running effects AFTER the reducer update with the class API, Context, and this.setState's callback param, but to accomplish the same thing with react-hooks means needing to use Today, in Redux-land, people use middleware libraries like redux-saga, redux-observable, redux-loop, etc. to be able to declaratively describe async effects via dispatched actions, because Redux doesn't expose an API function for running effects after a reducer update, but it's not difficult to implement this sort of functionality with the class API & setState's callback param. We could do something similar with react hooks if This one-time, post-update effect feature could be implemented by offering different variations of "dispatch", like how Reason-React has Alternatively, instead of having different variations of |
Untested madness that I'd rather not touch...... but it's an idea, and I have no idea if it works. But it typechecks 🤷♀️ import React from 'react'
function useImperativeEffectsFactory(serialized = true) {
const [, internalSetEffect] = React.useState<Promise<void> | undefined>(undefined)
return React.useCallback<(effect: () => Promise<void> | void) => void>(
effect => {
internalSetEffect(current => {
const promise = (async () => {
try {
if (serialized) {
await current
await effect()
} else {
await Promise.all([current, effect()])
}
} finally {
internalSetEffect(current => (current === promise ? undefined : current))
}
})()
return promise
})
},
[internalSetEffect]
)
} const useImperativeEffect = useImperativeEffectsFactory() useImperativeEffect(async () => {
// ...
}) |
@Kovensky I just tried it out, and that code results in a Anyway, I'm not saying that there aren't other ways to implement the same idea with Hooks, but it does seem like a lot of hoops need to be jumped through in order to do the same thing that currently is possible with the class API's |
I think @joshburgess has a good point! |
I agree, i'd like a callback option. Or taking it one step further, I'd love to see a promise returned. That's one thing that's always irked me about setState. |
For whatever it's worth, it's probably based on what
https://github.com/lumihq/purescript-react-basic/blob/master/src/React/Basic.purs#L165-L169 |
Just posting an update here for others, as I hadn't seen this before until just now. It looks like they are already aware that people want this and have some ideas about how it might be possible. See the Missing APIs section here: reactjs/rfcs#68 (comment) Relevant section in a screenshot: |
@gaearon I have this use-case which perhaps matches the issue:
|
@bebbi Not arguing against a callback option as it would be a simpler solution, but I believe you could accomplish this by tracking extVal's state in the child. const Parent = props => {
let [extVal, setExtVal] = useState(0);
useEffect(() => {
setTimeout(() => {
setExtVal(prevVal => prevVal + 1);
}, 5000);
}, []);
return <Child extVal={extVal} />;
};
const Child = props => {
let [state, setState] = useState({
val: props.extVal,
extVal: props.extVal
});
useEffect(
() => {
setState({
val: props.extVal,
extVal: props.extVal
});
},
[props.extVal]
);
useEffect(
() => {
// We know state.val is also done setting
console.log(state);
},
[state.extVal]
);
return <div>{state.val}</div>;
}; |
@bebbi Sorry, it's not clear why you need it. A full codesandbox showing the full use case (including why a parent needs such a callback) would be useful. |
Hi @gaearon, following method example comes from render props class component. I'd like to refactor it with react hooks. All of the methods on this class component has a callback when on setState is called. It passes a currently affected state so that the end user could do something if that action takes place.
and here is the internalSetState function
It would be kinda hard to replicate all this on a codesandbox but if nescessary I could produce something? This is the component that is in the process of refactoring Thanks |
@gaearon Here is a fiddle example of what I was trying to simplify. |
Not necessarily recommending this approach as it's possibly an anti-pattern, but for the people wanting this functionality, I suggest something like this as a solution (https://codesandbox.io/s/qq672lp6xq): const useStateWithPromise = defaultVal => {
let [state, setState] = useState({
value: defaultVal,
resolve: () => {}
});
useEffect(
() => {
state.resolve(state.value);
},
[state]
);
return [
state.value,
updater => {
return new Promise(resolve => {
setState(prevState => {
let nextVal = updater;
if (typeof updater === "function") {
nextVal = updater(prevState.value);
}
return {
value: nextVal,
resolve
};
});
});
}
];
};
const App = props => {
let [state, setState] = useStateWithPromise(0);
const increment = () => {
setState(prevState => prevState + 1).then(window.alert);
};
return (
<div>
<p>{state}</p>
<button onClick={increment}>Increment</button>
</div>
);
}; |
Here's my solution import { useState } from 'react'
const useStateWithEffects = (props, onValueChanged) => {
let [value, setValue] = useState(props)
const setState = (newValue) => {
value = newValue
setValue(value)
onValueChanged(value)
}
return [value, setState]
}
export default useStateWithEffects Usage: const [value, setValue] = useStateWithEffects(10, async () => console.log('changed') ) |
So ... are there plans to add a callback-parameter (or even better returning a Promise) within React? |
Solutionimport React from 'react'
/* :: (any, ?Function) -> Array<any> */
export const useState = (initialState, callback = () => { }) => {
const [ state, setState ] = React.useState(initialState)
const totalCalls = React.useRef(0)
React.useEffect(() => {
if (totalCalls.current < 1) {
totalCalls.current += 1
return
}
callback(state)
}, [ state ])
return [ state, setState ]
} Usageconst App = () => {
const onCountChange = count => {
console.log('Count changed', count)
}
const [count, setCount] = useState(10, onCountChange)
return (
<>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count => count + 1)}>+</button>
</>
)
} |
@gaearon |
@Tahseenm your solution provides a "global" callback for all setCount() calls. I think some people need a solution where they can provide a custom callback for each and every setCount() call. Which allows greater flexibility |
|
@bebbi Thanks will check it out. Not sure if we should make it a mandatory pre-read for this thread |
Thanks @bebbi, but I think most of us legitimately just need a function to run after a state change so we can do something with the most recent state values. For example, how can I make an API call after updating state and guarantee that its the latest values? Before it was like the following.
Currently with useState that is not possible. In this scenario useEffect won't quite work. I don't want to make the API call on the initial render and I might not necessary want to make the call every time the name state changes, I just want to make this call in a very specific instance. |
@gaearon
Can you explain, why it's a better solution? Also why it is not implemented ? (as for my understanding, it should be just one call at the right moment, when the state is updated. At least we can do it when useEffect is triggered. Typically using a promise, which will be resolved at the right moment). I bet it's related to optimization, and maybe consistency. But what difference it make from handling it in useEffect). And what do you think about the statement 'having the callback (||promise). Add a lot of flexibility.' ? Thank you a lot. UPDATE |
Here a pattern to mimic the after update behavior. It's short and clean. (it use promises) class that help with that
use:
in hook
in some function where we use setState
What do you think ? |
https://codesandbox.io/s/objective-pare-42mn4 A callback for setState would be useful in this case because I don't want the calendar field to be set every time the text input field update. |
What's wrong with this:
|
My solution import React, { useState } from "react";
const CallBackTest = () => {
const [trueFalse, setTrueFalse] = useState(false);
const callBacksetState = async (data, callBackFunc) => {
await setTrueFalse(data);
callBackFunc(data);
};
const func = data => {
alert(data + " " + trueFalse);
};
//pass function func to callBacksetState function
return <button onClick={() => callBacksetState(true, func)}>Click Me!</button>;
};
export default CallBackTest; |
import React from 'react';
export const DeleteFromArray = ({ deletionIndex }) => {
const [ emails, setEmails ] = React.useState([]);
const handleDelete = () => {
setEmails((emails) => emails.filter((email, index) => index !== deletionIndex));
};
return <button onClick={handleDelete}>Delete</button>;
}; |
@jhamPac
|
How do I use generics? |
Good reference |
@gaearon our company ran into this issue today attempting to upgrade (to hooks) a library that employed the callback on setState. After reading this thread we looked through our code for every instance where we were using it. We concluded that in nearly every case we were simply (and likely incorrectly) using this to ensure the form only submitted once and could not submit again until after an API call was complete. Our solution to this problem looks similar to the solution @Tahseenm submitted but we gave it a more appropriate name and usage. This might be useful to build right in to react. The entire solution follows:
|
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! |
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you! |
|
Is there a solution for this yet? |
Unfortunately, you should use |
Do you want to request a feature or report a bug?
Question
What is the current behavior?
The SetStateAction returned from useState hook dose not accept a second callback argument. It cannot works like Class Componet's 'setState' method, which receives a callback param, and can perform the callback after this setState action updates;
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:
https://codesandbox.io/s/93mkr1ywpr
What is the expected behavior?
Hopes the SetStateAction function can receive a second callback argument, and can used like 'setState' method callback.
I have read the official note:
If instead it's working as intended, how can I perform special action after this SetStateAction called ?
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
The text was updated successfully, but these errors were encountered: