Unsquashable React Bug - Recipe Box

Unsquashable React Bug - Recipe Box
0.0 0

#1

Hi FCC,

I have a strange bug that I just CANT figure out in my recipe box app despite trying all day. Let me describe it. Say I have recipe 1 with ingredients (a,b,c) and recipe 2 with ingredients (d,e,f). If I delete recipe 1 before recipe 2, recipe 2 will take on the state of recipe 1 and end up with recipe 2 (a,b,c) instead of the correct recipe 2 (d,e,f) format.

my deleting logic and ingredients state is as follows:

//in App.js splice the recipe (just a string) out of the array
deleteCard(i){
  let removedCard = this.state.recipeCards;
  removedCard.splice(i, 1);
  this.setState({
    recipeCards: removedCard
  });
}

//in Recipecard.js render a <CardBody /> instance and pass it this.state.ingredients as a prop
class RecipeCard extends Component {
  constructor(props){
    super(props);
    this.state = {
      ingredients: [],
      hidden: false
    };
  }

render(){
  if (!this.state.hidden){
    return (
      {this.props.children}
      <RecipeHeader reportClick={this.listenForHeaderClick} title={this.props.title}/>
      <CardBody ingredients={this.state.ingredients} />
    );

  //in CardBody.js build the recipe ingredients in a list and render them
  
  class CardBody extends Component {
    render(){
      let ingredients = this.props.ingredients.map((ingredient, i) => {
        return (<li key={i}>{ingredient}</li>);
      });
      return (
        <ul className="ingredient-list">
          {ingredients}
        </ul>
      );
    }
  }

Could anyone lend any advice at all? I’ve been trying to fix this bug all day and I feel like I’m at wit’s end. My next step might be to rebuild the entire project from scratch :frowning:

If you’re curious the full repo is here https://github.com/Swoodend/recipe_cards

Thank you for any and all advice, really appreciate it


#2

Let’s set up some hypothetical situation:

recipeCards: [foo, bar]

RecipeCards
    foo
        key: 1 // pay attention to the key
        ingredients: [a, b, c]
    bar
        key: 2
        ingredients: [d, e, f]

After you’ve deleted the first item in recipeCards

recipeCards: [bar]

RecipeCards
    bar
        key: 1
        ingredients: [a, b, c]

Do you see the problem? Since the key is consistent and deleteCard does not change the state of RecipeCard, the ingredients state in RecipeCard is preserved. As a result, React only updates RecipeHeader but not CardBody. I’d suggest that you minimize the stateful components. (In my opinion, your app is hard to reason about considering the scale because of too many stateful components)

I’ve also successfully replicated the bug, so I’m pretty confident fixing the addressed problem will solve the issue.


#3

Adding on to what @gunhoo93 mentioned you need to minimize the components that are managing state. In general your state should flow down from a top level component such as your main App Component. Then your stateless components should fire actions to modify the state in your top level component.

Take a look at your recipe card component and see if you could instead manage this state in your app component.


#4

@gunhoo93, thank you for taking the time to look at this. I truly appreciate it. I think 1 major problem is that I never really took the time to understand what keys are used for – I only used them to make my linter calm down :sweat_smile:. I will definitley be reading up about them in the future.

With regards to the number of stateful components: One of the things I find most challenging about react is the applicaiton architecture. I agree that there is SO MUCH state in my app that it is very hard to follow what is flowing where. Are there any tips/tricks you can recommend on how to structure the state in your app? Is it most preferable to have one parent component with most (if not all) of the state?

I am not sure I understand how to fix this bug yet you mentioned that

key is consistent and deleteCard does not change the state of RecipeCard

So to confirm my understanding: is it true that when react “diffs” only the changes are rendered, and because nothing changes in RecipeCard the state stays the same? Would changing the key affect this behaviour?

Again - thank you very much for your reply


#5

thank you @nrandecker! I’ve been having a hard time thinking about how to organize the state in my React apps. I was worried if I end up with all the state in one component it will become to large but it seems like this is actually preferable! I think I will get to work on refactoring all the state to the highest level component. Is there any rules or hueristics that a beginner can follow to determine what components should be stateful and what components should be stateless?

Thanks


#6

“So much state” isn’t really a problem but the problem is, like @nrandecker said, spread handling of state among multiple componets.
For me, Redux takes care of many issues related with consistent state management; there is also Flux but I’ve never got the hang of it.

To monkey-patch the problem, try changing the key to name of the recipe or hashed value that represents unique recipe. You can also mess around with componentShouldUpdate.

To fix the bug I’d suggest to redesign your components, maybe you can bundle the recipeTitle and recipeIngredient together.

I don’t exactly know ins-and-outs of React’s diffing algorithm–in fact, I thought your RecipeCard would re-render because it received new prop. However, I’m pretty sure combination of key, state, and componentShouldUpdate affects component re-rendering. You can search for React Reconciliation to learn more.


#7

Hi guys, just wanted to thank you for the help you gave. I refactored my code to store all the state in an array at the App.js level. I now have an array recipeCards that stores recipeCard objects that look like this:

{
  title: "Spaghetti",
  key: "AHJKGSdww7834HJKDGFJDSgw678er5t",
  ingredients: ["noodles", "sauce", "meatballs"]
}

This cleaned up my code a lot and made nearly all of my functions several lines smaller and even removed some of them completely. All I had to add was a helper function to generate the key for each recipe.

Thanks so much for your assitance @gunhoo93 @nrandecker - I really learned a lot :slight_smile: