Ok, here's a puzzler. I have a select input and I'm using Zustand for state. I'm seeing inconsistent state and not getting something I suspect.
It looks like this.
const handleSortChange = async (event) => {
// set a state variable
setSort(event.value)
// do a network call using sortBy and other things
// the call is wrong when using sortBy and I'll replace
// this bug with console.log statements below ...
}
There are two values for the select: "one" and "two".
If I console.log this stuff out, I see the problem and the bug. I can't use the state variable inside of this function. It doesn't await, resolve or behave the way I think it will.
So for my select, if I flip between one and two, I get this funny behavior:
const handleSortChange = async (event) => {
// set a state variable
setSort(event.value) // this sets sortBy
console.log(event.value)
console.log(sortBy) // this is the zustand state variable that is in scope
// I expect these would be the same, but they aren't! :O
}
The console.log output looks like this when switching from "one" to "two" on the select input.
two // event.value
one // the in-scope zustand variable sortBy as a read after the set
When switching to "two" on the select, I get the opposite but these variables aren't the same?
one // event.value
two // the set variable sortBy
When switching to "one" on the select. Because something isn't consistent or resolving like I think it is.
I thought that the zustand state variable would be consistent (especially when I add await and eslint is telling me that await does have effect for this function). This isn't an issue for me right now because I can use the parameter for everything I need. But I just feel like I'm missing something big here and I hope that Zustand isn't going to gotcha me when I need to rely on a state change or consistent store somewhere.
This seems to be the same issue and behavior that React has with setState. With setState in React you wouldn't do this even though this is a common trap. The value is not updated immediately, this way of thinking does not work for a concurrent GUI.
https://twitter.com/acemarke/status/1389376376508227592
In the case of Zustand, it might not even have a callback function to fire after set is called. In other words, this isn't going to work at this time.
Related
I've just started learning React and got to know about the useState hook. I came across two different ways for setting the state for boolean data. So are these two approaches identical and if not, which one is one should prefer?
const [isChanged, setIsChanged] = useState<boolean>(false)
const onClick = () => {
setIsChanged((prevState) => !prevState) // Approach 1
setIsChanged(!isChanged) // Approach 2
}
Since, as often in code, a simple example paints a thousand words, here's a simple CodeSandbox demo to illustrate the difference, and why, if you want an update based on the value of the state at the point of update, the "updater function" (Approach 1) is best:
https://codesandbox.io/s/stack-overflow-demo-nmjiy?file=/src/App.js
And here's the code in a self-contained snippet:
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.16.7/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
function App() {
const [count, setCount] = React.useState(0);
// this uses the "good way" but it doesn't really matter here
const incrementPlain = () => setCount((oldCount) => oldCount + 1);
const incrementWithTimeoutBad = () =>
setTimeout(() => setCount(count + 1), 3000);
const incrementWithTimeoutGood = () =>
setTimeout(() => setCount((oldCount) => oldCount + 1), 3000);
return (
<div>
<div>Current count: {count}</div>
<div>
<button onClick={incrementPlain}>
Increment (doesn't matter which way)
</button>
</div>
<div>
<button onClick={incrementWithTimeoutBad}>
Increment with delay (bugged)
</button>
<button onClick={incrementWithTimeoutGood}>
Increment with delay (good)
</button>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
Here we have a simple numeric "count" state which is displayed in the markup, together with 3 different buttons that all increment it.
The one on top just does a direct increment - I happen to have used the function form ("approach 1") here because I prefer this style for reasons that will hopefully become clear, but as my comment says, it doesn't actually matter here.
The two below use the two different approaches you outline in the question, and do so after a delay. I've done this with setTimeout here just to be simple - while this isn't particularly realistic, similar effects are commonly seen in real apps where actions call an API endpoint (and even though one hopes that doesn't normally take as long as 3 seconds, the same problems can always be observed with quicker requests - I've just slowed it down to be easier to trigger the bug).
To see the difference, try the following with each of the 2 bottom buttons:
click the button
click the button on top (to increment the count again) BEFORE the 3-second timeout is up
You should see a clear difference in behaviour:
with "approach 1" (button on the right, which I'm calling "good" here), the count increments a second time after the timeout is finished.
with "approach 2" (button on the left, which I've called "bugged"), there is no further increment from the value produced by the intermediate click on the top button, no matter how long you wait
(You can see this more dramatically if you click the bottom button multiple times quickly, then the top one once. And for an even more counterintuitive effect, try pressing the "bugged" bottom button one or more times, then clicking the top button more than once, all within the 3 second time interval.)
Why does this happen? Well, the "buggy" behaviour happens because the function inside the setTimeout is a closure over the outer variable count which is in the scope of the full component function. That means that when it's called with count + 1 as the argument, it will update the count to 1 more than whatever it was at the point the function was defined. Say you do the above sequence from first loading the component where count is 0, then the more detailed sequence of what happens is:
the bottom button click schedules a callback to happen in 3 seconds' time. Since count at that point is equal to 0, its argument, count + 1 is equal to 1.
the top button click rerenders the component, with count now equal to 1.
the callback set up at the first step later triggers, and sets the count to 1. This doesn't cause any noticeable chance, because the count was already 1. (If you tried clicking the top button multiple times, so it now shows 2 or more, this will actually decrement the counter, because as I'm explaining, it will always get set to 1.)
If you know a little bit about JS closures, you might wonder why the count that is accessed in the closure is still 0. Wasn't it previously updated to 1? No, it wasn't, and that's the bit that might be counterintuitive. Notice how count is declared with const? That's right - it never actually changes. The reason the UI updates is because setCount causes React to rerender your component, which means the whole outer function corresponding to the component is called again. This sets up a whole new environment, with a new count variable. React's internals ensure that the useState call now gives back 1 for the current count, which is therefore the value in that new "instance" of the component - but that's irrelevant from the point of view of the function that was put in the event queue to fire after 3 seconds. As far as it's concerned, the count variable - no longer in scope but "remembered" inside that callback as all closed-over variables are - has never changed from 0. The count that's equal to 1 is in a different scope entirely, and forever inaccessible to that first callback.
How does the function argument form - "approach 1" - get round this? Very easily. It doesn't hold any closure at all - the variable inside that function, which I've called oldCount here for the sake of both accuracy and to disambiguate from the outer count - has nothing to do with the count outside. It's the argument to a function that React itself will call internally. When React does call the function, it always supplies the "most up-to-date" state value it has. So you don't have to worry about "stale closures" or anything like that - you're saying "whatever the most recent value was, update the count to be one more than that", and React will take care of the rest.
I've called approach 2 "bugged" here because I think it's reasonable to expect an increment to happen after the timeout, if you've clicked a button that you've set up to do an increment. But this isn't always what you want. If you genuinely wanted the update to be based on the value at the point the button was first clicked, then of course you will prefer Approach 2, and Approach 1 will seem bugged. And in a sense that's more often the case. I highly recommend reading this post by Dan Abramov - one of the core React developers - that explains a crucial difference between class components and functions, that's based on many of the same arguments about closures that came into play here, where normally you do want the event handlers to reference values as they were at the time of render, not when they actually fire after an API request or timeout.
But that post doesn't have anything to do with the "approach 1" form of state-updating functions, which isn't even mentioned in the article. That's because it's irrelevant to the examples given - there'd be no (sensible) way to rewrite those examples to use it. But when you do want to update a state value based on its previous value - as could happen with negating a boolean value, as in the OP example, or incrementing a counter as in mine, I would argue that it's more natural that you always want that "previous value" to be up to date. There are 2 buttons which both should increment a value, albeit in different ways - I think it's reasonable to call it bugged if clicking both of them, depending on the timing, may only increment once in total.
But that's of course up to each individual component or application to decide. What I hope I've done here is explain what the difference is, and give you a basis to choose which might be best. But I do believe that 90+% of the time, if you have the option of using the function argument ("approach 1"), it will be better, unless you know it isn't.
the first approach
setIsChanged((prevState) => !prevState)
to make sure that you always have the last state before changing it.
The simple answer is this:
setIsChanged((prevState) => !prevState)
In this case the setter setIsChanged provided by the useState hook is passing it's own internal reference value, so it's always update, despite the whole async issue with useState.
In short, you're using the internal state value with prevState.
setIsChanged(!isChanged)
In this case, you're using the value provided by the useState hook to your COMPONENT, but not an internal ref. In this case you're working with data locally in your component that could be stale due to how components work asynchronously.
For most cases, the second approach works just fine and is by far the most common. You'll only use the second one if you're updating the state in several places at once or feel it could get out of sync.
You can read more about that here
State updates are asynchronous which means they are not updated immediately. They are scheduled. If your state depends on previous state then use 1st approach.
if your updatedState does not depend on previous state used 2nd approach.
Example:
Button Click to increase counter or toggle between states. approach 1
Resetting input field after submit button is clicked. approach 2
useState is async so sometimes this way does not work
setEditing(!prevState)
first the all we need the previous value, i think this way is best
setEditing((prevState) => !prevState)
you can try custom hooks
useToggle.js
import { useReducer } from 'react';
function toggler(currentValue, newValue) {
return typeof newValue === 'boolean' ? newValue : !currentValue;
}
function useToggle(initialValue = false) {
return useReducer(toggler, initialValue);
}
export default useToggle;
use like
import useToggle from './useToggle';
const App = () => {
const [isShown, toggle] = useToggle();
return <button>{isShown ? 'show' : 'hide'}</button>;
};
export default App;
Hi guys I have an issue with vueJS.
In the created hook I have this :
created() {
var allDocs = [];
for (var [key, value] of Object.entries(this.docs)) {
allDocs.push(value);
}
allDocs = allDocs.flat();
allDocs.forEach((doc)=>{
var data = {parent:doc.code}
axios.post('/api/v1/getconnumber', data)
.then((response)=>{
doc.datas.conventions = response.data
});
});
this.newDocs = allDocs;
}
And the property exists in the data :
But when I want to display the property in the template it shows nothing, I tired to log the result from the template it gives me undefined, I'm sure this is an async issue but I can't figure out how to fix it.
There are a few problems to address here.
The axios call is asynchronous, so at the point the created function finishes running the then callback will not have been called. The rest of the code will have run, so newDocs will have been assigned a value, it's just the conventions property that won't have been set.
Then the component renders. At this point conventions will be undefined, as you've observed. There's not much you can do to avoid this, you'll just need to write your template to handle the data being missing. There are various ways to shuffle the problem around so that it is handled in a different place but ultimately something has to handle it.
Next the axios calls will come back, one by one. As each call completes it will update the conventions property for that particular object.
In theory this will trigger the reactivity system and cause the component to re-render. However, for that to happen:
The datas objects must be reactive.
The property conventions must already exist within those objects. See https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats
It is likely that condition 1 is already satisfied. It's not made clear in the question exactly where the properties docs and newDocs are defined but my guess would be that docs is a prop and newDocs is a local data property. There's a lot of subtle nuance here when it comes to determining whether datas will be reactive but if you have newDocs declared inside your data function then you should be ok. You might get away with it even if you don't have it declared there but I would suggest including it anyway.
data () {
return {
newDocs: null
}
}
More likely is that your problem is due to the property conventions not existing when datas is initially made reactive. The simplest solution here is to use Vue.set or $set:
this.$set(doc.datas, 'conventions', response.data)
By using $set you'll not only set the property value but also make it reactive. This will allow it to trigger the template so that you see the new value.
All of that said, it seems a little peculiar to be updating the conventions property in this way. It seems likely it is updating an object that is owned by the parent component, which is generally discouraged. Alternative solutions include the use of a computed property or creating a lookup map for the conventions.
I had a similar issue with axios calls in a for-loop.
Try to put your assign " this.newDocs = allDocs; " after your requests are finished.
I did it with a watcher on a varible which counted up to the size of my max for-loop count.
Or you can try to put it into "then((response)=>{...}".
I have a strange error and I am not sure what's happening.
When I check an object, I can see that the $valid tag is set to true (and $invalid shows false). But when I print out just that tag (such as object.$valid) it prints false (and object.$invalid prints true).
$scope.$watch('ctrl.form', form => {
console.log('The form is set to: ', form);
console.log('The form is valid: ', form.$valid);
});
Does anyone know why this might be happening?
I suspect the issue is, the values are still updating in that function; it is watching for a change in the value, but still hasn't applied the changed values. This can make your code in that handler unpredictable.
Since you mention applying the scope throws the "in progress" error, that seems to be valid (the digest is still working). This code frag checks for an in-progress digest and sidesteps it if necessary.
if ( ! $scope.$$phase ) {
$scope.$apply ();
}
Note that "$$" vars in Angular were "private". You could still access them but you're not "supposed" to. This does however have the benefit of not appearing as randomly vague as a zero timeout.
Alternatively, as you've already seen, a 0 timeout can force an update. That's a hack, but it does work and I've seen it used a few times.
Lastly, one of the lifecycle events may be a better choice for these traces, such that you know you're tracing information in the natural flow of the component, where digests are predictable.
Regarding that lifecycle hook, if you're using 1.5 (the last paragraph seems to fit right here and is probably what you want):
New: $onChanges
This new hook is similar to ng2’s ngOnChanges. It is called whenever one way bindings are updated, with a hash containing the changes objects.
Prior to this hook you sometimes had to use a $watch in order to do some work whenever a value you’re bound to changes. Using this hook makes things clearer and removes the need to introduce a watch and a dependency on $scope.
I want to change the value for the hasSubmit key, like in the First Code section. I know this is not recommended. But the second code is asynchronous and I don't want to use the callback function of setState.
What is the difference of this.state and setState?
Is there any way to change state value hasSubmit immediately?
First Code:
this.state.hasSubmit = false
this.setState({})
//Code that will use `hasSubmit`.
Second code:
this.setState({
hasSubmit: false,
});
//Code that will use `hasSubmit`.
ADD:
The scenario is that:
hasSubmit set false in getInitialState().
hasSubmit will change to false when I click submit button.
hasSubmit will change to true when submitted.
First click submit has no problem and hasSubmit will be set to true.
But second click submit will be wrong using the Second asynchronous code, because the hasSubmit is still true, while the First Code can resolve the problem.
Here's what the React docs say:
NEVER mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate().
If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
It's always sensible to use APIs in the way they were designed. If the docs say don't mutate your state, then you'd better not mutate your state.
Whilst setState() might be technically asynchronous, it's certainly not slow in any noticeable way. The component's render() function will be called in pretty short order.
One drawback of setting state directly is that React's lifecycle methods - shouldComponentUpdate(), componentWillUpdate(), componentDidUpdate() - depend on state transitions being called with setState(). If you change the state directly and call setState() with an empty object, you can no longer implement those methods.
Another is that it's just bad programming style. You're doing in two statements what you could be doing in one.
Moreover, there's no actual benefit here. In both cases, render() is not going to be triggered until after setState() (or forceUpdate()) is called.
You claim a need to do this without actually explaining what that need is. Perhaps you'd like to detail your problem a little more. There's probably a better solution.
It's best to work with the framework rather than against it.
UPDATE
From the comments below:
The need is that I want use the changed hasSubmit in below.
OK, I understand now. If you need to immediately use the future state property, your best bet is just to store it in a local variable.
const hasSubmit = false;
this.setState({
hasSubmit: hasSubmit
});
if (hasSubmit) {
// Code that will use `hasSubmit` ...
If you want to change state and trigger a re-render by react:
Use the second code.
this.setState({
hasSubmit: false,
});
Problems/ errors with first code:
this.state.hasSubmit = false // Updates state directly:
// You are not supposed to do this
// except in ES6 constructors
this.setState({}) // passes an empty state to react.
// Triggers re-render without mutating state
this.setState maintains the react component's life cycle and doesn't seem like mutating variables (even though internally it does mutate state). So the one way flow in react cycle is maintained without any side effects.
The caveat is with using this.setState doesn't work with constructors in ES6 classes. We need to use this.state = pattern rather than this.setState in ES6 constructors
You should never ignore the documentation advice. At the time of writing, setState allow second argument which is a callback function when the setState and re-render had finished. Since you never provides us how your code gonna use hasSubmit value, I believe some other may find this useful when they want to make sure the hasSubmit had been changed.
You should use this.forceUpdate() in first example to force update the state. For example:
this.state.hasSubmit = false;
this.forceUpdate();
But it is better to use this.setState because it is init native check-state mecanizm of React engine which is better then force update.
If you just update any param of this.state directly without setState react render mecanizm will not know that some params of state is updated.
I have an application that runs in various states, for example state1, state2, and state3. Certain behavior of the application will depend on the state the application is in when e.g., a button is pressed, and state in turn will change based on button presses, etc.
I am wondering if there is a standard idiom or design pattern for keeping track of state changes in JavaScript. Perhaps my wording is misleading or inaccurate, so I will give an example. I might want to check state like this:
function foo() {
if (currentState === someState) {
doSomething();
}
}
But I would like to keep track of state in a way that I don't need to hardcode a string state in multiple places, i.e., I would like to avoid doing this:
function foo() {
if (currentState === 'state1') {
doSomething();
}
}
The first thing that came to mind is to create an appState object that has a property for each possible state:
var appState = {
state1: 'state1',
state2: 'state2',
state3: 'state3'
}
This way I could change the current app state like:
currentState = appState.state1;
and check state like:
if (currentState === appState.state1) {
...
}
However, something feels off about this solution; perhaps the repetition of property name and value.
I don't have enough experience reading or writing JavaScript to know the common patterns and best practices yet. I am wondering if the situation I have described above has a standard solution/idiom that is followed by the JavaScript community. Through researching this question, I have come across the State design pattern, and this nice post on how to apply the pattern to JavaScript. I have considered going this route, and have not completely discounted it, but that strategy seems like it might be unnecessarily complex for a simple application.
As javascript is quite dynamic, theres never a best solution. Its based on you what you like much or not. I would, to run a different function on an event, do sth like this:
var states={
dark:{
alert:function(){
alert("its night");
},
//more to come
},
bright:{
alert:function(){
alert("its day");
}
}
}//etc
Just put the functions in it, that change on every state, as you dont want to be repetetive. You could set a current state:
var currentstate=states.dark;
Now you can do, somewhere in your code:
currentstate.alert();
This will also work if state is set to bright. To check wich state is the current, you can do:
if(currentstate==states.dark)
But the states object isnt that senseless then in your example.
PS:
To improve the upper structure, you could use OOP to have substates( that behave similar to another state)
states.grey=Object.create(states.dark);
states.grey.alert=function(){
alert("its getting brighter...");
};
Also this will solve your typo problem:
state.dak //error: cannot read property of undefined.