setState in map function of react - reactjs

My Requirement is to update the state value in map function of componentWillReceiveProps.
In console log all I am getting is 1s but sub.subscribed contain 0s and 1s
Reference of console window: http://prntscr.com/jqifiz
constructor(props) {
super(props);
this.state = {
regionAll: [],
};
}
componentWillReceiveProps(nextProps){
if(nextProps.apiData !== false ){
nextProps.apiData.data.datacenter.category.map((sub)=> {
console.log(sub.subscribed,'sub.subscribed');
this.setState({
regionAll: [
...this.state.regionAll,
sub.subscribed
]
},()=>{
console.log(this.state.regionAll,'sub');
})
})
}
Is this a correct way to update state in reactjs?

setState is async.In Array#map, it called multiple time.Only last value is added in array regionAll and not all because of async setState call with multiple value.
You can collect all sub.subscribed value in single array with Array#reducer then perform state update.
if (nextProps.apiData !== false) {
let sub = nextProps
.apiData
.data
.datacenter
.category
.reduce((accum, sub) => [
...accum,
sub.subscribed
], [])
this.setState({
regionAll: [...sub]
}, () => {
console.log(this.state.regionAll, 'sub');
})
}

The problem arises because setState calls are batched and you are updated React state based on prevState, you should instead use functional state for such cases
componentWillReceiveProps(nextProps){
if(nextProps.apiData !== false ){
nextProps.apiData.data.datacenter.category.map((sub)=> {
console.log(sub.subscribed,'sub.subscribed');
this.setState(prevState => ({
regionAll: [
...prevState.regionAll,
sub.subscribed
]
}),()=>{
console.log(this.state.regionAll,'sub');
})
})
}
However its a bad idea to call setState in a map, you can instead get the data from map and call setState just once like
componentWillReceiveProps(nextProps){
if(nextProps.apiData !== false ){
const subscribed = nextProps.apiData.data.datacenter.category.map((sub)=> {
console.log(sub.subscribed,'sub.subscribed');
return sub.subscribed;
})
this.setState(prevState => ({
regionAll: [
...this.state.regionAll,
...subscribed
]
}),()=>{
console.log(this.state.regionAll,'sub');
})
}

Related

React - Render happening before data is returned and not updating component

I can't get this to work correctly after several hours.
When creating a component that needs data from Firebase to display, the data is returning after all actions have taken place so my component isn't showing until pressing the button again which renders again and shows correctly.
Currently my function is finishing before setState, and setState is happening before the data returns.
I can get setState to happen when the data is returned by using the callback on setState but the component would have already rendered.
How do i get the component to render after the data has returned?
Or what would the correct approach be?
class CoffeeList extends Component {
constructor(props) {
super(props);
this.state = {
coffeeList: [],
}
}
componentDidMount() {
this.GetCoffeeList()
}
GetCoffeeList() {
var cups = []
coffeeCollection.get().then((querySnapshot) => {
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
})
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
}
render() {
const coffeeCups = this.state.coffeeList;
console.log("Rendering component")
return (
<div className="coffee">
<p> This is the Coffee Component</p>
{coffeeCups.map((c) => {
return (
<CoffeeBox name={c.name} />
)
})}
</div >
)
}
}
Thanks
The problem is that you set the state before the promise is resolved. Change the code in the following way:
GetCoffeeList() {
coffeeCollection.get().then((querySnapshot) => {
const cups = []
querySnapshot.forEach(function (doc) {
cups.push({ name: doc.id})
});
console.log('Updating state')
console.log(cups)
this.setState({ coffeeList: cups })
console.log('End GetCoffeeList')
})
}

Pass setState as a callback to another setState

Is it the right way to call setState as a callback in another one? Here a piece of my code.
This is my initial state:
state = {
resumeCollection: [],
filters: {
educationLevels: [],
educationFields: [],
jobAdvertisementId: []
}
};
And this a componentDidMount section:
componentDidMount() {
this.setState(prevState =>( {filters : {
jobAdvertisementId : [...prevState.filters.jobAdvertisementId, {value: this.props.router.query.value, id: this.props.router.query.id}]}
}
)
, () => this.setState(state => {
return({
resumeCollection : state.resumeCollection.filter(resume => resume.jobAdvertisementId == state.filters.jobAdvertisementId[0].id )
});
}
)
)
}
I would advice against doing 2 state updates like that as it will introduce a useless second render.
If you need to set portion of the state based on another portion of the state, you can calculate it and store it outside the return statement inside a variable and used it where you need it.
In your case it might look something like this:
componentDidMount() {
this.setState(prevState => {
const nextJobAdvertisementId = [
...prevState.filters.jobAdvertisementId,
{
value: this.props.router.query.value,
id: this.props.router.query.id
}
];
return {
filters: {
jobAdvertisementId: nextJobAdvertisementId
},
resumeCollection: prevState.resumeCollection.filter(
resume => resume.jobAdvertisementId === nextJobAdvertisementId[0].id
)
};
});
}
Yes you can add another setState as a part of callback to the first setState(), when you want to set another state based on first.
In the below example I set 'b' state based on state 'a':
e.g
const setB = () => {
if(this.state.a)
this.setState({b:"Success"})
else
this.setState({b:"failure"})
}
this.setState({a:true},this.setB)

What is the better way to update other state variable which is dependent on another state variable while re-rendering?

The scenario is, after mounting the component, in event listener I am setting a state variable and other state variables are being set by making a rest call from backend.
so far what I did is I am using componentWillUpdate and making rest call and setting all the required states.
I tried using componentWillUpdate method to calculate and set other state variables. But its re-rendering multiple times. I guess I am definitely doing something wrong here.
export default class Person extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
name: this.props.name,
age: "",
country: ""
};
}
componentDidMount() {
this.setDerivedStates();
}
componentWillUpdate() {
this.setDerivedStates();
}
attachListner() {
document.addEventListner("customEvent", () => {
this.setState({ name: something });
});
}
setDerivedStates() {
FetchService.get("url1" + this.state.name).then(response =>
this.setState({ age: response.age})
);
FetchService.get("url2" + this.state.name).then(response =>
this.setState({ country: response.country })
);
}
render() {
return (
<div>
<p>{this.state.name}</p>
<p>{this.state.age}</p>
<p>{this.state.country}</p>
</div>
);
}
}
I want to re-render the component once with all the new state variables.
Please suggest how should I do it. which lifecycle method and how should I use to set all these states?
You can use Promise.all to batch the two fetches so you only have call this.setState once -
const [resp1, resp2] = await Promise.all([
FetchService.get("url1" + this.state.name);
FetchService.get("url2" + this.state.name);
]);
this.setState({ age: resp1.age, country: resp2.country });
Also, componentWillUpdate is considered unsafe and will be deprecated in the future. I would suggest using componentDidUpdate instead -
componentDidUpdate = (prevProps, prevState) => {
if (prevState.name !== this.state.name) {
this.setDerviedStates();
}
}
You can wrap both fetches in Promise.all which will wait for both Promises to resolve, if one fails you will have no access to any Promise/s that resolves successfully and the operation will throw an error.
Add componentDidUpdate to check if the name state has changed, if so refetch.
componentDidUpdate(prevProps, prevState) {
if (prevState.name !== this.state.name) {
this.setDerivedStates();
}
}
async setDerivedStates() {
const url = `url${this.state.name}`;
try {
const [age, country] = await Promise.all([
FetchService.getAge(url),
FetchService.getCountry(url),
]);
this.setState({ age, country });
} catch (e) {
console.log('something went wrong', e);
}
}

Why do i end up with an empty array in my state?

I am setting my array's state in the componentDidMount but can't understand why it shows up as empty on the mount.
constructor(props){
super(props);
this.state = {
currentGroup: this.props.currentGroup,
eventHold: [],
idHold: []
}
}
componentDidMount(){
const groupRef = firebase.database().ref('groups').child(this.state.currentGroup).child('events');
var tempIdHold =[];
groupRef.on('value', snapshot => {
snapshot.forEach(function(snap) {
tempIdHold.push(snap.key)
})
this.setState({
idHold: tempIdHold
});
console.log(tempIdHold)
console.log(this.state.idHold)
})
this.loadGroupEvents(this.state.idHold);
}
The first console.log shows a populated tempId array but the second console.log right underneath it shows an empty state.id array. Why?
Because this.setState is a async function.
So, you can use the callback in setState function
this.setState({
idHold: tempIdHold
}, () => {
console.log(tempIdHold)
console.log(this.state.idHold)
});
this.setState is asynchronous which takes a callback that will invoke after the operation is finished try adding it and see the result
this.setState(
{ idHold: tempIdHold },
// our updated state will be available in our callback
() => console.log(this.state.idHold)
);

In react, insert response array into setState

class Search extends React.Component {
constructor() {
super();
this.state = {
searchResult: {
"sr": []
}
}
this.handleChange = this.handleChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.setState = this.setState.bind(this)
}
onSubmit = formProps => {
console.log(formProps.searchItem);
cryptoSearch.searchNames(formProps.searchItem)
.then((names) =>
console.log(names),
this.setState({
sr: { names }
}),
console.log(this.state.sr)
) // [ 'BTC Lite', 'BTCMoon' ]
.catch(err => console.log(err))
};
handleChange(e) {
this.setState({ errorMessage: '' });
}
I want to use onsubmit function to display the search result from an api. I declared an array called sr in constructor, and onSubmit function, when I use a package(similar to axios) to get a names(response) array object, how can I insert the names array into sr array??
You can simply set in state while I am assuming names from the response is an array.
this.setState({
sr: names
},()=>{ console.log(this.state.sr);});
You can then verify in callback of setState as setState is an asynchronous type.
If names is an array and you want to update your sr property with this array you can use ES6 spread operator and set your state like that:
this.setState(prevState => ( {
searchResult: { ...prevState.searchResult, sr: names }
} )
);
If you set your state directly with sr like in your code you will loose other properties of searchResult and your state shape will change also something like this:
this.state = {
"sr": []
}
One other point is since setState is asynchronous if you console.log your state immediately after setting your state, you can't get healthy results. Instead use a callback for this:
this.setState(prevState => ( {
searchResult: { ...prevState.searchResult, sr: names }
} ), () => console.log(this.state.searchResult.sr)
);
or do not bother logging here with a callback and do it in your render method for logging purposes:
render(){
console.log(this.state.searchResult.sr);
return( .... )
}
If names is an array then you can set it to state as
onSubmit = formProps => {
console.log(formProps.searchItem);
cryptoSearch.searchNames(formProps.searchItem)
.then((names) =>
this.setState({
sr: names
}),
)
.catch(err => console.log(err))
};

Resources