Handle callback function after setState in hooks - reactjs

I want to ask if my approach to using callback functions below is correct. I feel like the callback function I switched from Class to Hooks is not working as expected.
in Class
componentWillReceiveProps(nextProps) {
if (typeof nextProps.checked != 'undefined' && nextProps.checked != this.state.checked) {
this.setState({checked: nextProps.checked});
}
}
onClick = () => {
var self=this;
this.setState({checked: !this.state.checked}, function () {
self.props.onChange(self.state.checked);
});
};
in Hooks
const { checked,onChange } = props;
const [state, setState] = useState({
checked: !!checked
});
useEffect(() => {
setState({ ...state, checked: checked });
}, [checked]);
useEffect(() => {
onChange(state.checked)
}, [state.checked]);
const onClick = () => {
setState({ checked: !state.checked });
}

if the upper props.onChange handle the checked status and pass it down to this component, then you dont need to handle the state internally ?

I checked your code and I feel this is working as expected.
You can check here:
https://codesandbox.io/s/misty-firefly-2l4tt?file=/src/App.js
But, seeing your code, I feel you want to pass the status of the checked/unchecked field in the child component to the parent component. In that case, you can directly call onChange props from the child component onClick/onChange event and manage the state independently in the parent component.
Probably, the code of the parent component will help me in better understanding if you could share.

Related

Get new HOC state in child component after updating HOC parent state in child component [duplicate]

So I have this:
let total = newDealersDeckTotal.reduce(function(a, b) {
return a + b;
},
0);
console.log(total, 'tittal'); //outputs correct total
setTimeout(() => {
this.setState({ dealersOverallTotal: total });
}, 10);
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1'); //outputs incorrect total
newDealersDeckTotal is just an array of numbers [1, 5, 9] e.g.
however this.state.dealersOverallTotal does not give the correct total but total does? I even put in a timeout delay to see if this solved the problem.
any obvious or should I post more code?
setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet. Try putting the log in the callback of the setState() method. It is executed after the state change is complete:
this.setState({ dealersOverallTotal: total }, () => {
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
In case of hooks, you should use useEffect hook.
const [fruit, setFruit] = useState('');
setFruit('Apple');
useEffect(() => {
console.log('Fruit', fruit);
}, [fruit])
setState is asynchronous. You can use callback method to get updated state.
changeHandler(event) {
this.setState({ yourName: event.target.value }, () =>
console.log(this.state.yourName));
}
Using async/await
async changeHandler(event) {
await this.setState({ yourName: event.target.value });
console.log(this.state.yourName);
}
The setState is asynchronous in react, so to see the updated state in console use the callback as shown below (Callback function will execute after the setState update)
this.setState({ email: 'test#example.com' }, () => {
console.log(this.state.email)
)}
I had an issue when setting react state multiple times (it always used default state). Following this react/github issue worked for me
const [state, setState] = useState({
foo: "abc",
bar: 123
});
// Do this!
setState(prevState => {
return {
...prevState,
foo: "def"
};
});
setState(prevState => {
return {
...prevState,
bar: 456
};
});
The setState() operation is asynchronous and hence your console.log() will be executed before the setState() mutates the values and hence you see the result.
To solve it, log the value in the callback function of setState(), like:
setTimeout(() => {
this.setState({dealersOverallTotal: total},
function(){
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
}, 10)
If you work with funcions you need to use UseEffect to deal with setState's asynchrony (you can't use the callback as you did when working with classes). An example:
import { useState, useEffect } from "react";
export default function App() {
const [animal, setAnimal] = useState(null);
function changeAnimal(newAnimal) {
setAnimal(newAnimal);
// here 'animal' is not what you would expect
console.log("1", animal);
}
useEffect(() => {
if (animal) {
console.log("2", animal);
}
}, [animal]);
return (
<div className="App">
<button onClick={() => changeAnimal("dog")} />
</div>
);
}
First console.log returns null, and the second one returns 'dog'
just add componentDidUpdate(){} method in your code, and it will work.
you can check the life cycle of react native here:
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
As well as noting the asynchronous nature of setState, be aware that you may have competing event handlers, one doing the state change you want and the other immediately undoing it again. For example onClick on a component whose parent also handles the onClick. Check by adding trace. Prevent this by using e.stopPropagation.
I had the same situation with some convoluted code, and nothing from the existing suggestions worked for me.
My problem was that setState was happening from callback func, issued by one of the components. And my suspicious is that the call was occurring synchronously, which prevented setState from setting state at all.
Simply put I have something like this:
render() {
<Control
ref={_ => this.control = _}
onChange={this.handleChange}
onUpdated={this.handleUpdate} />
}
handleChange() {
this.control.doUpdate();
}
handleUpdate() {
this.setState({...});
}
The way I had to "fix" it was to put doUpdate() into setTimeout like this:
handleChange() {
setTimeout(() => { this.control.doUpdate(); }, 10);
}
Not ideal, but otherwise it would be a significant refactoring.

Value is not stroing in State React [duplicate]

So I have this:
let total = newDealersDeckTotal.reduce(function(a, b) {
return a + b;
},
0);
console.log(total, 'tittal'); //outputs correct total
setTimeout(() => {
this.setState({ dealersOverallTotal: total });
}, 10);
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1'); //outputs incorrect total
newDealersDeckTotal is just an array of numbers [1, 5, 9] e.g.
however this.state.dealersOverallTotal does not give the correct total but total does? I even put in a timeout delay to see if this solved the problem.
any obvious or should I post more code?
setState() is usually asynchronous, which means that at the time you console.log the state, it's not updated yet. Try putting the log in the callback of the setState() method. It is executed after the state change is complete:
this.setState({ dealersOverallTotal: total }, () => {
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
In case of hooks, you should use useEffect hook.
const [fruit, setFruit] = useState('');
setFruit('Apple');
useEffect(() => {
console.log('Fruit', fruit);
}, [fruit])
setState is asynchronous. You can use callback method to get updated state.
changeHandler(event) {
this.setState({ yourName: event.target.value }, () =>
console.log(this.state.yourName));
}
Using async/await
async changeHandler(event) {
await this.setState({ yourName: event.target.value });
console.log(this.state.yourName);
}
The setState is asynchronous in react, so to see the updated state in console use the callback as shown below (Callback function will execute after the setState update)
this.setState({ email: 'test#example.com' }, () => {
console.log(this.state.email)
)}
I had an issue when setting react state multiple times (it always used default state). Following this react/github issue worked for me
const [state, setState] = useState({
foo: "abc",
bar: 123
});
// Do this!
setState(prevState => {
return {
...prevState,
foo: "def"
};
});
setState(prevState => {
return {
...prevState,
bar: 456
};
});
The setState() operation is asynchronous and hence your console.log() will be executed before the setState() mutates the values and hence you see the result.
To solve it, log the value in the callback function of setState(), like:
setTimeout(() => {
this.setState({dealersOverallTotal: total},
function(){
console.log(this.state.dealersOverallTotal, 'dealersOverallTotal1');
});
}, 10)
If you work with funcions you need to use UseEffect to deal with setState's asynchrony (you can't use the callback as you did when working with classes). An example:
import { useState, useEffect } from "react";
export default function App() {
const [animal, setAnimal] = useState(null);
function changeAnimal(newAnimal) {
setAnimal(newAnimal);
// here 'animal' is not what you would expect
console.log("1", animal);
}
useEffect(() => {
if (animal) {
console.log("2", animal);
}
}, [animal]);
return (
<div className="App">
<button onClick={() => changeAnimal("dog")} />
</div>
);
}
First console.log returns null, and the second one returns 'dog'
just add componentDidUpdate(){} method in your code, and it will work.
you can check the life cycle of react native here:
https://images.app.goo.gl/BVRAi4ea2P4LchqJ8
As well as noting the asynchronous nature of setState, be aware that you may have competing event handlers, one doing the state change you want and the other immediately undoing it again. For example onClick on a component whose parent also handles the onClick. Check by adding trace. Prevent this by using e.stopPropagation.
I had the same situation with some convoluted code, and nothing from the existing suggestions worked for me.
My problem was that setState was happening from callback func, issued by one of the components. And my suspicious is that the call was occurring synchronously, which prevented setState from setting state at all.
Simply put I have something like this:
render() {
<Control
ref={_ => this.control = _}
onChange={this.handleChange}
onUpdated={this.handleUpdate} />
}
handleChange() {
this.control.doUpdate();
}
handleUpdate() {
this.setState({...});
}
The way I had to "fix" it was to put doUpdate() into setTimeout like this:
handleChange() {
setTimeout(() => { this.control.doUpdate(); }, 10);
}
Not ideal, but otherwise it would be a significant refactoring.

how to get state right after state change in react hooks

Hi Everyone I am unable to get current state after setting up the state. I am calling a function Where I am passing that state but I am always getting default state in that function.
this is my State-
const [currentValue , setCurrentValue] = useState(one[0]); // where one[0] is string "Test"
this is my onchange function -
const getTest = useCallback((newValue:string) => {
setCurrentValue({
label:newValue,
value:newValue
});
getCurrentValue(currentValue.value);
getSortOrder()
}, [])
this is my getSortOrder function -
const getSortOrder =() => {
console.log(currentValue);
}
I am not getting updated state in getSortOrder function & getCurrentValue function. But yes If I am trying to console the state in render then it is showing me updated state but in function it is not updating. How can I achieve the updated state in both functions ?
If I get your question correctly then this might help:
const getTest = useCallback((newValue:string) => {
setCurrentValue({
label:newValue,
value:newValue
});
//try this code doing in useEffect
}, [])
useEffect(() => {
getCurrentValue(currentValue.value);
getSortOrder()
},[currentValue]); //Everytime currentValue changes it will give new state
This is happening because the 'setCurrentValue' function is an asynchronous function and the state is updated after both the functions 'getCurrentValue' and 'getSortOrder' have finished their execution.
One easy solution will be to pass the parameter 'newValue' to both the functions like this :
const getTest = useCallback((newValue:string) => {
setCurrentValue({
label:newValue,
value:newValue
});
getCurrentValue(newValue);
getSortOrder(newValue)
}, [])
const getSortOrder =(newValue) => {
console.log(newValue);
}
Or you can use the 'useEffect' functionality to trigger the functions after the value of the state has been updated like this :
const getTest = useCallback((newValue:string) => {
setCurrentValue({
label:newValue,
value:newValue
});
}, [])
useEffect(() => {
getCurrentValue();
getSortOrder()
},[currentValue]);

React hook for component "componentWillUnmount" lifecycle method

I have an object that is a copy for a property props.Job and I change it with:
const [jobData, setJobData] = React.useState<IjobData>();
const handleJobChange = (name, value) => {
setJobData({
...jobData, [name]: value
});
};
Now I wanted to save the value of jobData before the component unmounts
So:
React.useEffect(() => () => {
if (jobData !== props.Job) {
Save(jobData) // jobData is undefined
}
}, []);
No Good can't get the current state of jobData
So trying:
React.useEffect(() => () => {
if (jobData !== props.Job) {
Save(jobData) // jobData value is missing the last run of handleJobChange
}
}, [jobData]);
Also no good: jobData value is missing the last run of handleJobChange and also it is running before every run of handleJobChange and not only the "componentWillUnmount" event
Anyone can recommend a better way to figure the right event the will have the state so I can save it?
React.useEffect returns a function that can be used for clean up. i.e execute some code before unmounting/updating the component.
So for your use case, you can modify your useEffect like the following:
React.useEffect(() => () => {
return(()=>{
if (jobData !== props.Job) {
Save(jobData) // jobData is undefined
}
})
}, []);
The return statement will be executed before the unmounting of your component. For more details, you can check react's documentation

Should you check if an input is empty before using setState to clear it?

Assume input has value={this.state.searchValue}.
onClose = () => {
this.setState({searchValue: ''}, () => {
this.search();
});
}
Is this a waste if for example, the container is closed while the string is already empty?
Should checks like this be done?
onClose = () => {
if (!isEmpty(this.state.searchValue)) {
this.setState({searchValue: ''}, () => {
this.search();
});
}
}
What if this were Redux state?
onClose = () => {
if (this.props.inputValue) {
this.props.clearInput();
}
}
I think Redux is a little smarter and does a shouldComponentUpdate for you, so it would realize the this.props.inputValue === '' and nextProps.inputValue === '' and save a render?
React will render the component whenever setState() is called. If you want to prevent this behaviour, your component could extend React.PureComponent and it will compare the previous state value in order to decide to render or not.
When you wrap a component with Redux.connect, by default it also applies a shallow comparison in shouldComponentUpdate.

Resources