Reactjs - Using setState to update a single property on an object

I am working on the react recipe box challenge and have a question regarding setState and forms. In this challenge there are recipes which have both title and ingredients properties. I am representing these recipes as an array of objects in my main component’s state as such:

this.state = {
  recipes: [
    {
      title: "Mac and Cheese",
      ingredients: ["Noodles", "Cheese", "Milk"]
    }, 
   {
      title: "Tofu Stir Fry",
      ingredients: ["Veggies", "Soy Sauce", "Tofu"]
    } 
],
currentRecipe: {}
}

In order to edit an individual recipe I have created another property on the state of the main component called currentRecipe which holds one recipe object which the user has selected to edit. The Form component is then passed props for the main applications this.state.currentRecipe which then populates the forms fields:

<FormControl id="recipe-title" type="text" value={this.props.currentRecipe.title} onChange={this.props.updateCurrentTitle} placeholder="Enter Recipe Name"/>

<FormControl id="recipe-ingredients" componentClass="textarea" placeholder="Enter Ingredients separated by commas..." value={this.props.currentRecipe.ingredients} onChange={this.props.updateCurrentIngredients}>

As you can see I also have created 2 different functions updateCurrentTitle() and updateCurrentIngredients() in order to handle the changes on each of these fields (this is cumbersome, do I really need to make a new function every time I want a field to update?). But here is where the problem comes in, I am representing the currentRecipe as a single object, and I cannot use this.setState() to update only a single property on an object (or can I?). The solution is then to represent each property of the currentRecipe with a different property on the main applications state, but It seems cumbersome to have to represent each part of a recipe with a different property on the main applications state just because this is how react likes to handle changes. So the 2 questions are: do I really need to make a new function for every field to handle changes to that field? and 2: is it possible to use this.setState() to update only a property on an object?

1 Like

The way I handled a similar problem in one of those projects, was by first copying the state, next making the changes to the copy , and finally setting the state with the new copy , for example with your case:

let recipesCopy = JSON.parse(JSON.stringify(this.state.recipes))
   //make changes to ingredients
   recipesCopy[0].ingredients = //whatever new ingredients are
   this.setState({
      recipes:recipesCopy 
    }) 
8 Likes

I created quick example to answer your first question:

.https://stackblitz.com/edit/react-kmd8fb

2 Likes

Thank you both for the replies! They answered both of my questions and @jenovs you made me realize I need to learn more ES6; though I understand what you are doing, using the name property of the input element to update the state of the component.

Here is a really good reference on how this should be handled: https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b

EDIT: Also for complex state object immutability: https://github.com/kolodny/immutability-helper

1 Like

Another option which works well for me is to create a copy of the state object using Object.assign(), then adjust properties through dot notation and then set the created copy to the state:

let newState = Object.assign({}, this.state);
newState.recipes[1].title = "Tofu Stir Fry and other stuff";
this.setState(newState);
3 Likes

Thanks much, i was loooking for a solution for more than 2 hours. Fianlly it works with JSON parse

@Dereje1 Thanks a lot man, I was facing this problem in one of my project and your solution to copy the object worked beautifully and solved my problem.

The JSON solution by @Dereje1 worked like a charm. I just had to use a filter to generate a unique index to pass into the newStateArr

yeah @vic3king, I still use the JSON solution a lot too, but one needs to keep in mind that copying an object that way, while deep, will not copy any methods that are in the object, if it does have any methods that is.

1 Like

I like this one a lot. No need to overcomplicate it.

This one just saved my life. Thank you

Couple of follow up questions:
– would spread operator work as well as Dereje1 's JSON solution?
– any problems with accessing object property by name?
eg:
let noo = {…old};
noo[index].property = data;
or in different use case
noo[index].[“property_name”] = data;