sample state in the next line to setState in reactjs - javascript

I have a react js question where I am unsure if I can sample a state right after it has been set. Example code below
const [state, setState] = useState<boolean>(false);
setState(val => !val);
axios.post(val);
I think the setState is a call which schedules the state change and then the react engine will eventually change it. So after executing the setState on the next line if I sample 'val' it will be 'false'.
If the above is true, I have a usecase wherein in a single execution flow i need to process and change a state and send the processed state to the backend.
Now, to bend around the above issue I see 2 paths.
Sample the state in a local var. process the var and setState the var and send the var
var stateL = !state;
setState(stateL);
axios.post(stateL);
I use a new state variable trigger and do the axios call on the useEffect of this new state. (i am not using the original state in the useEffect as there are many other changes possible for this state which doesnt need an axios call)
const [triggerBackend, setTriggerBackend] = useState(false)
setState(val => !val);
setTriggerBackend(val => !val);
useEffect(() => {
axios.post(state)
}, [triggerBackend])
I am not sure if '2' is race free as well, so leaning more on '1'.
So my question is, is the way a problem like this can be solved or are there better solutions.
Thanks in advance.

Related

UseEffect dosen't trigger setState when using sessionStorage

Hey I've go a problem with this code.
const [itemsCart, setCart] = useState([]);
useEffect(() => {
(async function (){
const buffer = await JSON.parse(window.sessionStorage.getItem("itemsCart"))
setCart(buffer);
console.log(buffer);
console.log(itemsCart);
})()
}, []);
useEffect(() => {
window.sessionStorage.setItem("itemsCart", JSON.stringify(itemsCart));
}, [itemsCart]);
The buffer gets the data, the state variable dosen't. I assume there must be a problem with synchronization however I'm not able to fix that.
The output:
here
This happens because react will wait until all script in useEffect is called and after that, setState will trigger rerender. Because there can be multiple setStates and we want to rerender it only once. That means, you are logging old value in console.log(itemsCart) before its actually there after rerender.
You can logi it with second useEffect before updating sessionStorage and you will see, that state is changed. Or you can create new useEffect for this
useEffect(()=>{
console.log(itemsCart)
},[itemsCart]);
this works:
const [itemsCart, setCart] = useState(JSON.parse(window.localStorage.getItem("itemsCart")));
useEffect(() => {
console.log(itemsCart);
window.localStorage.setItem("itemsCart", JSON.stringify(itemsCart));
}, [itemsCart]);
To run the second useEffect(), itemsCart needs to be modified before via useState(). I can't see in your first useEffect() when you call setItemsCart().
This question is not correct and the approach to solve the problem is not correct as well(whatever problem you are trying to solve).
React has different design.
You are trying to get the items and then set them once you get it using useEffect.
The best approach would be to pass your array as a prop from higher order component and then use your useEffect once it has been triggered by dependencies(passed prop)
Make useEffect hook run before rendering the component

React useState doesn't update even with useEffect added

Probably it is a classic issue with useState which is not updating.
So there is a tree with some checkboxes, some of them are already checked as they map some data from an endpoint.
The user has the possibility to check/uncheck them. There is a "cancel" button that should reset them to the original form.
Here is the code:
const [originalValues, setOriginalValues] = useState<string[]>([]);
...
const handleCancel = () => {
const originalValues = myData || []; //myData is the original data stored in a const
setOriginalValues(() => [...myData]);
};
...
useEffect(() => {
setOriginalValues(originalValues);
}, [originalValues]);
However, it is not working, the tree is not updating as it should. Is it something wrong here?
Just do the following, no need for ()=> the state will update inside the hook if called, plus change the constant it will cause confusion inside your code and protentional name clash later on, with the current state variable name, and also make sure your data are there and you are not injection empty array !!!! which could be the case as well !.
// Make sure data are available
console.log(myData)
// Then change the state
setOriginalValues([...myData]);

Asynchronous way to get the latest change in State with React Hooks

I have started learning React and developing an application using ReactJS. Recently i have moved to React Hooks. I know that in Class Component we can get the latest data from the state with the 2nd argument which is present in setState() like
state = {name: ''};
this.setState({name: name}, () => {console.log(this.state)});
I wanted to know if there is any arguments in React Hooks with which we can get the latest data from it.
I am using React Hooks in the below mentioned way, but upon console logging it always return the previous state
Hooks Eg:
const [socialData, setSocialData] = useState([
{ id: new Date().getTime().toString(), ...newItem }
]);
const onChangeCallback = (index, type, data) => {
let newSocialData = [...socialData];
newSocialData[index] = { ...newSocialData[index], [type]: data };
setSocialData(newSocialData);
onInputChange(newSocialData, formKey);
console.log(newSocialData);
};
The this.setState() second argument is not exactly to get the latest data from the state, but to run some code after the state has been effectively changed.
Remember that setting the state is an asynchronous operation. It is because React needs to wait for other potential state change requests so it can optimize changes and perform them in a single DOM update.
With this.setState() you can pass a function as the first argument, which will receive the latest known state value and should return the new state value.
this.setState((previousState) => {
const newState = previousState + 1;
return newState;
}, () => {
console.log('State has been updated!')
});
With that being said, there are special and rare cases when you need to know exactly when the state change has taken place. In many years of working with React I only faced this scenario once and I consider it a desperate attempt to make things work.
Usually, during a callback execution, like your onChangeCallback you want to change the state as the last thing your function does. If you already know the new state value, why do you want to wait for the real state to change to use it?
The new state value should be a problem to be handled during the next render.
If you want to run some code only when that particular state value changes you can do something like this:
import React, {useState, useEffect, useCallback} from 'react';
function MyComponent() {
const [value, setValue] = useState(false);
const onChangeHandler = useCallback((e) => {
setValue(!!e.target.checked);
}, []);
useEffect(() => {
// THIS WILL RUN ONLY WHEN value CHANGES. VERY SIMILAR TO WHAT YOU ARE TRYING TO DO WITH THE this.setState SECOND ARGUMENT.
}, [value]);
return (
<input type='checkbox' onChange={onChangeHandler} />
);
}
There is also a way to create a custom useState hook, to allow you passing a second argument to setValue and mimic the behavior of this.setState, but internally it would do exactly what I did in the above component. Please let me know if you have any doubt.

I want to understand why this works the way it does

I've been working with React for a while now buh I always have this problem when I'm trying to update a state in either redux or react useState.
const [files, setFiles] = useState(null);
const handleFileUpload = e => {
setFiles(e.target.files[0]);
setTimeout(() => {
console.log(files);
}, 5000);
}
I ran the code without setTimeout initially but apparently the logged value is null in the first instance but the state gets updated after console.log runs so I use setTimeout as a fallback to test the bit of code as setTimeout is asynchronous and hence non blocking but still the state is only updated after setTimeout runs why please? What am I doing wrong?
Note: I only assume the state is been updated because I set the value of the input file to be file.name so as to make state the single source of truth

React "can't perform state update on unmounted component" with timeout

Questions about this warning have been asked and answered countless times on the Internet, yet - maybe actually because of that - I'm having difficulty finding a comment which touches on my own situation.
I'm working on an autosave feature, whereby when you start typing into the component's form it starts a timer. On completion, a) it dispatches an action (which is working fine) and b) it clears the timer state so that next time the user types it knows it can start a new one.
The issue comes when I unmount the component before the timer is complete: when it does expire I get the Can't perform a React state update on an unmounted component warning as I try to clear the timer state.
Now, just about all the solutions I've found online for this suggest I should create an isMounted state variable and check it before running the relevant setAutosave(null) state call. Except that - as far as I'm aware - the nature of Javascript timers means that the values available to the setTimeout callback (or Promise callback, for that matter) are those when the timer was started - when of course, the component was mounted.
Effectively, I'm stuck between a) the autosave feature requiring a state reset if the component is mounted, b) React demanding that the state reset cannot occur if the component isn't mounted, and c) the timer preventing any checking (that I can think of) of whether the component is or isn't mounted. Any ideas?
const { dispatch } = useContext(MyContext)
const [autosave, setAutosave] = useState(null)
const save = () => {
clearTimeout(autosave) // in case you manually submit the form
setAutosave(null)
dispatch({ type: "SAVE" }) // this line works fine
}
const onChange = () => {
if (!autosave) {
const timeoutId = setTimeout(save, 30000)
setAutosave(timeoutId)
}
}
<form onChange={onChange} onSubmit={save}>
...
I am not sure though, Try change your state using componentWillUnmount if your component is class-based component and useEffect functional components

Categories

Resources