简体   繁体   中英

Changing state is causing props to change

I am making a web app where people can click on a cocktail and change the recipe to have more or less of each ingredient. I am doing this by first loading the database of drinks from an API endpoint, then for each drink in the database i create a child with a property "drink" set to the JSON object defining the drink name and recipe.

Each child has a button to pop up a modal which has an buttongroup to adjust the amount for each ingredient. I am doing this by passing the drinks recipe as a property to the modal and then copying that recipe from props to state.

Then when someone adjust the drink recipe (they can choose 'Low', 'Regular', or 'High' for each ingredient I call a class function on the modal which is passed the index of the ingredient in the recipe list and a constant to multiply the original recipe.

In the function i set a temporary recipe equal to the props.recipe and then i multiply the factor by the ingredient's original amount. Then i copy the temporary recipe which has been modified by the factor to the state by calling this.State

But what is happening is that it seems to be changing the props.recipe as well as the state.recipe.

Why is this happening? I didn't think props were allowed to be changed.

Secondly, is there a better way to do this? I am new to react and this is my first ever web app. In it's simplest form i am just trying to limit how much each ingredient can be reduced/increased but still give the end user the option to change it.

Here is a screenshot with the console open so you can see how both 'before' and 'after' it is changed it is both 6, even though the original amount is 3. I hit the 'High' button which doubles the original amount of the ingredient, but if i hit it again it doubles it again instead of staying at 6 (which is 2 times the original ingredient amount stored in props).

and here is the code for the modal:

import React from 'react';
import ReactDOM from 'react-dom';

import {Modal, ButtonGroup, Button, Table} from 'react-bootstrap';

export default class DrinkInfo extends React.Component {

  constructor(props){
    super(props);

    this.state = {
      recipe: props.drink.recipe,
    };

    console.log('Drink Info Initial Recipe is');
    console.log(this.props);
  }

  adjustAmount(ingredient, level){

    console.log('before change')

    var tempRecipe = this.props.drink.recipe;

    console.log(tempRecipe)

    tempRecipe[ingredient].amount = level * tempRecipe[ingredient].amount;
    //tempRecipe[ingredient].amount = level * this.props.drink.recipe[ingredient].amount;

    console.log('after change')
    console.log(tempRecipe)


    this.setState({ recipe: tempRecipe});
  }

  render(){

    const drinkName = this.props.drink.drink;
    //console.log(this.state.recipe)

    const recipeTableBody = this.state.recipe.map((di, value) => {
      //console.log(di.ingredient);
      return( 
        <tr key = {di.ingredient}>
          <td>{value}</td>
          <td>{di.ingredient}</td>
          <td>{di.amount}</td>
          <td>
            <ButtonGroup>
              <Button onClick={() => this.adjustAmount(value, 0.5)}> Low </Button>
              <Button onClick={() => this.adjustAmount(value, 1)}> Regular </Button>
              <Button onClick={() => this.adjustAmount(value, 2)}> High </Button>
            </ButtonGroup>
          </td>
        </tr>
      );
    });

    //{console.log('Creating Info Modal for: ' + this.props.drink.drink)}


    const recipeTable = (
      <Table striped bordered condensed hover>
        <thead>
          <tr>
            <th>#</th>
            <th>Ingredient</th>
            <th>Amount</th>
            <th>Adjust</th>
          </tr>
        </thead>
      <tbody>
        {recipeTableBody}
      </tbody>
      </Table>
    )

    return(
      <div>
        <Modal show={this.props.show} onHide={this.props.onHide} bsSize="large" aria-labelledby="contained-modal-title-sm">
          <Modal.Header closeButton>
            <Modal.Title id="contained-modal-title-sm"> {this.props.drink.drink} </Modal.Title>
          </Modal.Header>
          <Modal.Body>

            <h4> {this.props.drink.drink} Recipe </h4>

            {recipeTable}

          </Modal.Body>
          <Modal.Footer>
            <Button onClick={this.props.onHide}>Close</Button>
          </Modal.Footer>
        </Modal>

      </div>
    );
  }

}

As someone mentioned, try to keep future questions a bit shorter and more concise. It makes it easier to lend a hand.

That said, I think I spotted your problem in your adjustAmount() function.

Take a look:

adjustAmount(ingredient, level){
  var tempRecipe = this.props.drink.recipe;
  tempRecipe[ingredient].amount = level * tempRecipe[ingredient].amount;
  this.setState({ recipe: tempRecipe});
}

You're taking this.props.drink.recipe and setting it equal to tempRecipe . Since recipe is an object, this basically create a reference to that object at tempRecipe . In other words, tempRecipe === recipe .

That means, when you change tempRecipe , you are also changing recipe . While strictly speaking React isn't supposed to let you change props, it doesn't have anything that freezes them. And JavaScript is JavaScript, so it lets you.

What you should do instead is clone this.props.drink.recipe , and then use the clone.

adjustAmount(ingredient, level){
  var tempRecipe = Object.assign({}, this.props.drink.recipe);
  tempRecipe[ingredient].amount = level * tempRecipe[ingredient].amount;
  this.setState({ recipe: tempRecipe});
}

This will assign() all of the properties from recipe and assign them to a new object, effectively cloning it.

Important note : Object.assign({}, someObj) will only create a shallow clone. If your object is multiple levels deep, which it appears to be, instead of Object.assign() , you should get or create a deep clone object.

Just get one of them and then do something like:

adjustAmount(ingredient, level){
  var tempRecipe = deepClone(this.props.drink.recipe);
  tempRecipe[ingredient].amount = level * tempRecipe[ingredient].amount;
  this.setState({ recipe: tempRecipe});
}

There are a number of them out there, so I won't re-implement them here. A couple libraries that have them are lodash, underscore, or there are some standalone available in NPM.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM