React - When deleting a Bootstrap 'Panel', How do I prevent the next panel opening?

Hi,

I have a series of closed panels in the React Recipe Box project, one panel for each recipe. The panels are part of a Panel Group Accordion.

Actual behaviour: When clicking on a panel heading the panel opens to reveal the recipe and a Delete button. When the delete button is pressed, the recipe panel is deleted from the app, however the next recipe panel immediately opens. I think this opening next recipe behaviour would be confusing for the user.

Desired behaviour: What I would like to happen is on delete, the panel would delete and the list of remaining closed panels should be visible, i.e. the next panel should not open.

The panel (github link) is in RecipeList.jsx line 61 and is rendered on line 106.

I have tried to refactor using several techniques from React-Bootstrap demos, and whilst I now know a few more things that can be done with Panels I’m none the wiser on how to prevent the next panel from opening when a panel is deleted. :frowning:

Anyone know what I’m doing wrong?

Thanks,
Tim.

1 Like

Hi @TimHandy

I have a feeling the issue is related to how React handles dynamic lists. Basically, it needs the key prop to be a unique identifier for that specific list item otherwise when things get changed, added, removed from the list strange behavior can occur.

Because your using the index property of the array as the key, when a recipe is deleted, that index can be associated with a different recipe, so it isn’t unique entirely.

On line 64, try using the recipes name, or maybe even a combination of the recipes name and Date.now() to ensure that the key is completely unique to that element.

I’m not 100% sure this is the problem, however, I have run into strange issues with list rendering (not unlike this) which was down to a non-unique key.

Facebooks docs goes into a little more detail on why a unique key is necessary https://facebook.github.io/react/docs/lists-and-keys.html#keys

Thanks Joe, I just tried that, but no luck, the same behaviour. The React docs state that the key has to be unique among the group, and that if you use the index it’s not as fast to render, but does say you can use index.

Thanks anyway :slight_smile:

I suspect you tried something like idx + recipe, didn’t you. The problem is recipe is an object. In order to coerce an object into a string you go through… well, you go through different rules depending on how you try to coerce it. idx + recipe results in 0[object Object]. When you delete it the newly rendered list happens to also be in possession of 0[object Object], which in turn means that it is active and therefore open.

So how do you fix it?

  1. You could, for example, say idx + recipe.ingredients[0] and it will work most of the time. Except, the next recipe may actually have the same first ingredient.

  2. You could try to do idx + recipe.ingredients. That would work but you may get to have two recipes with the exact same ingredients.

  3. You could also try idx+ recipe.ingredients + recipe.name but that too could be a duplicate.

So what should you actually do? The simplest solution is to use symbols for your eventKey such as Symbol(recipe.ingredients). A better solution would be to ban the creation of a recipe with the same name as an already existing recipe and use recipe.name

PS Check this out


var obj = {};

console.log(String(obj)) // "[object Object]"
console.log('' + obj)    // "[object Object]"

obj.valueOf = () => '42'
obj.toString = () => '21'

console.log(String(obj))// "21"
console.log('' + obj)   // "42"

Hi mkarabashev,

No, I didn’t. I had actually tried adding one or two additional items when Joe above mentioned it. The effect was that each key was unique (I checked in React dev tools that the key was being applied correctly and the props were being accessed and coerced correctly. eg key=".$0FlourCake" and key=".$1Hot waterCoffee"). I am assuming the .$ part is a React thing. So unless I’m misunderstanding something fundamental I think your advice is not relevant?

I do appreciate your time making the suggestion though, thanks. :slight_smile:

I wondered if it could be Bootstrap thing so I created a fork and rebuilt the fundamentals without Bootstrap and still get the same issue. :frowning:

I think there was a fair bit of miscommunication here (it was probably me - I posted this at 2+AM)

I didn’t mean that you should change the React keys although you really should in order to avoid having the issues you are experiencing in this new version. I meant that you should change the bootstrap-react eventKeys on line 63 of the original code. I know it works because I cloned it and tested it before I posted my initial reply.

eventKey={idx} should be eventKey={Symbol(recipe)} or something similarly unique

Ahh, thought it was worth a try, just in case :smiley:

I do have another idea; I’ve had a look at the panel classes, when its open it has an extra class in:

// Closed
<div class="panel-collapse collapse">

// Open
<div class="panel-collapse collapse in">

So when you delete a recipe, select all the panel-collapse divs and remove the in class

document
  .querySelectorAll('.panel-collapse')
  .forEach(panel => {
    panel.classList.remove('in')
  })

EDIT: Or @mkarabashev idea because it’s much better :grin:

Ah ha! Thanks! mkarabashev, yes, that works. I think I understand why it needs to be unique now and how the idx would not work. Lesson learned. Thanks for your help, appreciated!

Thanks Joe, mkarabashev’s worked, though you had the right idea, it was just the wrong key, one line above for the bootstrap, not the react key :slight_smile: Guess I’ll try to use completely unique indexes for such things in future! Maybe I’ll create an id when generating elements so that I have something to target, of course when I use a database I’ll get the id anyway.

Thanks again.