How do we partially update a state in react? - reactjs

I was wondering what is the best way to partially update a state of a component in React/React-Native. Other than the fact that I can make a function which takes the current state and creates a new state and merges the new {key:value} and the previous state. For example:
{
dataStream:[//having data here],
formData: {
'first_name': 'Richard',
'last_name' : 'Barbieri',
}
}
I want to update last_name to another value. When I call
this.setState(formData:{{'last_name':newValue}}), it resets the formData dictionary to just last name: new Value. Is there a way to this efficiently?

I think there are two things you could try:
Spread operator
liks so
this.setState({
formData: {
...this.state.formData,
"last_name" : newValue
}
});
or
Take current state's first_name and reapply it:
like so
this.setState({
formData: {
"first_name": this.state.formData.first_name,
"last_name" : newValue
}
})
I'm not too sure about the first one, but I think the second one should work.

What happens is normal because you reassign the whole forData.
If you want to add something to the existing form data do something like that (there are plenty of other solutions ^^)
this.setState({
formData: Object.assign(this.state.formData, { 'last_name': newValue }
})

Related

Mapping over function that sets state in react

I use the function changeCheck to check and uncheck specific components.
When I use the function, it works correctly.
this.props.team is a list of all of the teams.
The goal of changeAllTeams is to be able to check and uncheck all of the teams that have a specific league.
In this example I want to change all of the teams that have a league acronym of NFL:
this.state = {
checked: [],
checkedTeams: [],
teamObject: [],
queryString: [],
accordionStatus: [true, true, true]
}
changeAllTeams = (leagueType) => {
this.props.team.map(
(v, i) => {
if(v.league.acronym === 'NFL'){
this.changeCheck(i, v.team_name, v)
}
}
)
}
componentDidUpdate(){
console.log('checked', this.state.checked)
console.log('team object', this.state.teamObject)
console.log('props team object', this.props.teamObject)
this.props.changeLeagues(this.props.league, this.props.checkedLeagues, this.state.checkedTeams, this.state.queryString, this.state.teamObject, this.state.checked)
}
changeCheck = (index, name, teamObject) => {
//updates checked team state
if(!this.state.checkedTeams.includes(name)){
this.state.checkedTeams[this.state.checkedTeams.length] = name
this.setState({ checkedTeams: [...this.state.checkedTeams] })
//sets team object with new team object
this.state.teamObject[this.state.teamObject.length] = teamObject
this.setState({ teamObject: this.state.teamObject })
} else {
console.log(name)
newChecked = this.state.checkedTeams.filter(v => { return v !== name})
this.setState({ checkedTeams: newChecked })
//removes team object and sets new state
newObjectChecked = this.state.teamObject.filter(v => { return v.team_name !== teamObject.team_name})
this.setState({ teamObject: newObjectChecked })
}
//updates checkbox for specific space
this.state.checked[index] = !this.state.checked[index]
this.setState({ checked: this.state.checked })
this.forceUpdate()
}
When I map over the array in changeAllTeams, only the last object in the array takes effect.
The state for checked updates for everything, but the state for checkedTeams and teamObject does not.
This video may help to understand further:
https://streamable.com/q4mqc
Edit:
This is the structure of the objects in this.props.team:
I don't have your code but I'm pretty sure that the problem is that you didn't provide a unique id for each item (remember that it's most of the time a bad idea to use map index for your items). The thing that you should do is to give each item a unique key and call the function based on that id.
There are a few places where you mutate the contents of this.state. That could cause React to be unable to detect changes in the state because the new and old state are referencing the same object. I would recommend that you don't mutate any state and instead create clones of the data object before passing the new data to setState()

How to update nested object in redux

I have an object:
{ //birthdaysObject
'2000':{
'January':
[{
name: 'Jerry'
},
{
name: 'Chris'
}]
},
'2001':{
'February':
[{
name: 'John'
}]
}
When I go to update the redux store it is replacing the entire year (eg. '2000') object with the new one that I send to my reducer.
How can I push the the nested array of objects without replacing the entire year object?
My reducer currently looks like:
return Object.assign({}, state, {
...state,
birthdays: Object.assign({}, state.birthdays, {
...state.birthdays,
...birthdays
})
});
Where ...birthdays would be another object in the same format as the first code snippet.
I am also open to suggestions about the structure of my data, normalizing, etc.
Any help would be appreciated.
The object keys in the birthdaysObject are unknown and are assigned when iterating through a separate object. I've tried kolodny/immutability-helper however the $merge function is returning the same results as what my reducer is already doing.
I had the same problem some time ago.
Follow the way I done it.
You have an object, but I think you should have an array of objects.
I also have different names on variables, but this should not be a problem to understand the logic
//do a copy of the array first
let newSubscriptions = state.customer.master.subscriptions.slice();
//for the value you want to change, find it's position in the array first
const indexInSubscriptions = newSubscriptions.map(function(item) {
return item.id;
}).indexOf( action.id);
//get the child you want to edit and keep it in a new variable
const under_edit_subscription = state.customer.master.subscriptions[indexInSubscriptions];
//go again over the array and where is the value at the index find above, replace the value
newSubscriptions = newSubscriptions.map((item, i) =>
i === indexInSubscriptions ? under_edit_subscription : item
)
//add the whole array into the state
return {
...state,
customer: {
...state.customer,
master: {
...state.customer.master,
subscriptions : newSubscriptions
}
}
}

Adding objects fetched from local restify server into object in state

So I'm taking a course in web programming and in it we've gotten this assignment to design some simple front end for ordering salads, to get all the components etc. it was previously stored in a .js file in the following fashion
let inventory = {
Sallad: {price: 10, foundation: true, vegan: true},
Pasta: {price: 10, foundation: true, gluten: true},
'Salad + Pasta': {price: 10, foundation: true, gluten: true},
'Salad + Matvete': {price: 10, foundation: true, vegan: true, gluten: true},
'Kycklingfilé': {price: 10, protein: true},
'Rökt kalkonfilé': {price: 10, protein: true},
'Böngroddar': {price: 5, extra: true, vegan: true},
'Chèvreost': {price: 15, extra: true, lactose: true},
Honungsdijon: {price: 5, dressing: true, vegan: true},
Kimchimayo: {price: 5, dressing: true},
.
.
.
};
export default inventory;
This is then imported into my App.js that was created when creating the react project and sent as a prop to another component that took care of the composing of a salad that was eventually sent back to a function also sent with as a prop.
So what we're supposed to do now is to get this inventory from a local rest(?) server instead. So if I go to
http://localhost:8080/proteins
it will open a page that just displays an array with all the different choices of proteins
["Kycklingfilé","Rökt kalkonfilé","Norsk fjordlax","Handskalade räkor från Smögen","Pulled beef från Sverige","Marinerad bönmix"]
And then going to
http://localhost:8080/proteins/Kycklingfilé
Will give you another page with the properties of that ingredient
{"price":10,"protein":true}
And my attempt at recreating that inventory object with all the ingredients as properties inside state is this
class App extends Component {
constructor(props) {
super(props);
this.state = {
salads: [],
inventory: {
}
};
}
componentDidMount() {
const base = "http://localhost:8080/";
const pURL = base + "proteins/";
const fURL = base + "foundations/";
const eURL = base + "extras/";
const dURL = base + "dressings/";
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
fetch(fURL + e).then(response => response.json()).then(data => {
Object.assign(this.state.inventory, {e : data})
})
})
});
fetch(pURL).then(response => response.json()).then(data => this.setState({data}));
fetch(eURL).then(response => response.json()).then(data => this.setState({data}));
fetch(dURL).then(response => response.json()).then(data => this.setState({data}));
}
I've been using
{JSON.stringify(this.state)}
to try and look at whats going on and with this code it comes out as this
{"salads":[],"inventory":{},"data":["Ceasardressing","Dillmayo","Honungsdijon","Kimchimayo","Pesto","Rhodeisland","Rostad aioli","Soyavinägrett","Örtvinägrett"]}
So the fetch works fine for getting all the ingredients of a certain type, I guess it's only the dressings since it overwrites data each time on those last three fetches. But the problem is that inventory is completely empty.
If I instead write it like this
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
Object.assign(this.state.inventory, {e: fetch(fURL + e).then(response => response.json().then())})
})
});
The output becomes
{"salads":[],"inventory":{"e":{}},"data":["Ceasardressing","Dillmayo","Honungsdijon","Kimchimayo","Pesto","Rhodeisland","Rostad aioli","Soyavinägrett","Örtvinägrett"]}
So it adds the 'e' object, which is another problem since I want it to be the value of the current element, but it's completely empty, and I dont know how to get the data from that seconds fetch when I write it like that. So that's why it now looks like it does in the first code snippet, where it doesn't even get an empty 'e' inside inventory.
Finally, if I write it like that second example but just e: e like this
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
Object.assign(this.state.inventory, {e: e})
})
});
The output becomes
{"salads":[],"inventory":{"e":"Salad + Quinoa"},"data":["Ceasardressing","Dillmayo","Honungsdijon","Kimchimayo","Pesto","Rhodeisland","Rostad aioli","Soyavinägrett","Örtvinägrett"]}
So it seems like everything is working up until the .forEach on the array of strings that represents a certain type of ingredient since it manages to put that into 'e' inside inventory with one of the array elements as it's value. It's only the last one in the list though but I guess that stems from the problem that it just makes the object 'e' and not the value of the current element and overwrites it for every item.
Sorry if all the rambling made the problem unclear, but what I'm trying to achieve is inventory {} inside state that looks like it did when it was in a seperate file, so that when we create the component we can send this.state.inventory instead of the imported inventory as prop. And to create that using what we can fetch from the different pages.
When you write
{e : data}
you create a new Object with a single entry. That sets the value of the key 'e' as the current value of the variable 'data'. A variable named 'e' is not involved:
const e = 'hello';
console.log(e); // "hello"
console.log({ asd: e }); // { asd: "hello" }
console.log({ e: "asd" }); // { e: "asd" }
console.log({ e: asd }); // ReferenceError: asd is not defined
What you are trying to do is using the value of the variable e as the key that you want to set. In javascript this is done using [ and ] like so:
const e = 'hello';
console.log({ [e]: "world" }); // { hello: "world" }
// this is necessery whenever you want a key that is not a simple word
console.log({ ["key with spaces"]: "world" }); // { "key with spaces": "world" }
console.log({ [e + e]: "world" }); // { hellohello: "world" }
EDIT:
there is another issue with your code above that you might encounter sooner or later:
In React you should never ever modify this.state directly. Always go through this.setState()!
https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly
In your case this is a bit more difficult, since you are making multiple requests which each affect the same key in your state (inventory).
Because you cannot know in what order the requests arrive, and whether React will actually do the setState each time new data comes, or do them all at the same time, you cannot simply use this.setState({ inventory: newInventory }). Instead you should use the function version as described here. Unfortunately this can be a bit complex to grasp in the beginning :(
in your case I would solve it like this:
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
fetch(fURL + e)
.then(response => response.json())
.then(data => this.setState((prevState) => ({
inventory: Object.assign({}, prevState.inventory, {[e]: data}),
})));
})
})
});
A couple of things to note here:
note the ({ in (prevState) => ({ ... }): this is an arrow function that returns an object
we are passing a function to this.setState (see the link above for details). This function receives the current state as an argument (prevState) and should return the new State. (although it can omit keys of the old state that remain unchanged). This is better than directly passing the new state to this.setState because when multiple setState happen at the same time, React can apply the functions you pass in the right order so that all changes happen, but if you passed objects it has to decide on one of them to 'win' so changes can get lost.
In Object.assign({}, prevState.inventory, {[e]: data}), instead of modifying prevState.inventory we create a new object that contains the updated inventory. You should never modify the old state, even in this.setState.
Hope this helps :)
So with #sol's advice to use [e] to create the objects for each ingredient, this code
fetch(fURL).then(response => response.json()).then(data => {
data.forEach(e => {
fetch(fURL + [e]).then(response => response.json()).then(data => {
Object.assign(this.state.inventory, {[e] : data})
})
})
});
now works. I think why it didn't look successful with my "troubleshooting" of just printing that JSON.stringify of the entire state in render was that is just didn't render properly when react refreshed after saving the code. Updating the page makes it all blank, but clicking onto another page through a link and then back fixes it. Dont know why, but I'll take it.

ReactJS: Update a specific Object from an array of Objects in the state

So, I have a a state of users which gets filled up with an array of Objects from the backend. Say, I want to update the details of a specific User Object from that array and then update the current state with the updated details. How do I go about doing that?
What I currently do is force a refetch from the server once the update request is successful to update the state again but I was wondering if there's a better way to do it without refetching from the server again.
For example, on the code below. I wanted to update PersonTwo's age.
state = {
users: [
{
name: PersonOne,
age: 1
},
{
name: PersonTwo,
age: 1
}
]
}
Let's say you have id field in your object. You can use map function to return new array and save it to your state.
updateUser(userId){
const updatedUsers = this.state.users.map( (user) => {
if(userId !== user.id) {
return user;
}
return {
...user,
//write some updated info here, for example:
age: 40
}
}):
this.setState({
users: updatedUsers
});
});
The best way is to do as you are doing right now that first send the values to the server and then fetch the latest data from the database because if you update the view before successful update on the server you might end up showing which does not exist actually. There is a possibility that your server did not accept that change but somehow you updated the view on user request. It is not a good thing, but if you still want to do that follow the steps below:
step-1: check the array index on which the update of user data is done and then
const newArray = Array.from(this.state.oldArray);
newArray[i] = 'test';
step-2: assign this new array to the old one:
this.setState({oldArray: newArray})
You can use functional setState as a success callback of POST/PUT call.
this.setState(prevState => {
users: ... // operation using prevState.users
})

Update nth item in array in state in parent from child in React?

In my top level component I have a function to update state. I pass this down to different child elements so that they can update my main state.
In my top level component:
updateValue(item, value) {
this.setState({[item]: parseInt(value)});
}
This has worked so far however now I need to update the nth item in an array.
My top level state is like this:
this.state = {
chosenExercises: [
'Bench press',
'Squat',
'Pull up'
]
};
And in my child component Im trying to do something like:
this.props.updateValue('chosenExercises'[1], 'New exercise');
So that my state would then be:
this.state = {
chosenExercises: [
'Bench press',
'New exercise',
'Pull up'
]
};
Am I going about this the correct way? Or should my state be key value pairs?
this.state = {
chosenExercises: {
0: 'Bench press',
1: 'New exercise',
2: 'Pull up'
}
};
This would potentially solve some of my problems of making the exercises easier to target but Im not sure which is best practice.
Since the chosenExercises can be multiple it makes sense to make it as an array, however you need to update your state differently. Instead of passing the index of the array element to update, you should actually make a copy of the array, update it in the child element and then send the updated array to the parent.
You could do something like:
In Child:
updateValue = (item, index, value) => {
let newValue = [...this.props[item].slice(0, index), value, ...this.props[item].slice(index + 1)];
this.props.updateValue(item, newValue);
}
The thing with this is that your state has to remain immutable so you have to provide a new Array to update in your state. So you'll end up with something like:
this.updateValue('chosenExercises', 1, 'New exercise');
updateValue(item, index, value) {
const newArray = this.state[item].slice();
newArray[index] = value;
this.setState({ [item]: newArray });
}
The array.slice() function creates a new Array, in which you update the value by its index. Afterwards you update your component state with the new array.
If you happen to do this more often, React created an immutability helper for these things. You can read more about it here. This would let you do something like:
import update from 'react-addons-update';
this.setState({
[item]: update(this.state[item], {[index]: {$set: value } })
});
It can be done with this in the top level component:
updateValue(item, value, options) {
if (options.isChosenExercises === true) {
this.setState((prevState) => {
let newchosenExercises = prevState.chosenExercises.slice();
newchosenExercises[item] = value;
return {chosenExercises: newchosenExercises};
});
} else {
this.setState({[item]: parseInt(value)});
}
}
For normal uses pass an empty object as the last parameter:
this.props.updateValue('setLength', e.target.value, {})}
But when you want to update the exercise array pass an object with isExercises set to true.
chooseThisExercise() {
this.props.updateValue(numberInTheArrayToChange, newExercise, {isChosenExercises: true});
}

Resources