1

The following code seems to work as expected: (fiddle)

const headers = [
    {
        id: 'section-1',
        header: 'section 1',
    },
    {
        id: 'section-2',
        header: 'section 2',
    },
    {
        id: 'section-3',
        header: 'section 3',
        children: [
            {
                id: 'section-31',
                header: 'section 31',
                children: [
                    {
                        id: 'section-311',
                        header: 'section 311',
                    },
                    {
                        id: 'section-312',
                        header: 'section 312',
                    },
                ],
            },
            {
                id: 'section-32',
                header: 'section 32',
            },
        ],
    },
    {
        id: 'section-4',
        header: 'section 4',
    },
];

const Nav = ({ headers, className }) =>
    <ul className={className}>
        {headers.map((header) => [
            <li className="list-group-item">
                {header.header}
            </li>,
            (header.children && header.children.length) ? (
                <Nav className="list-group" headers={header.children} />
            ) : null,
        ])}
    </ul>

const Toc = () =>
    <div id="my-super-interesting-nav">
        <Nav className="list-group nav-root" headers={headers} />
    </div>

I the future the headers array will be dynamic. I have three questions:

Why doesn't React complain that I haven't set the key prop on the child of the headers.map() call in Nav?

How bad will this confuse the reconciliation algorithm, given that the second assumption in the React reconciliation algorithm no longer holds?

If I wanted to specify a key in this case, how would I do that?

Lars Nyström
  • 5,786
  • 3
  • 32
  • 33

2 Answers2

1

The mapping function you use returns an array of elements, and not a react element, so key property is not expected thus no warning are being given.

I would advise you to refactor your code for mapping function to return react element like so:

{headers.map((header) =>
    <li className="list-group-item" key={header.id}>
        {header.header}
        {
            (header.children && header.children.length) 
            ? <Nav className="list-group" headers={header.children} /> 
            : null
        }
    </li>
)}

Note, that now we are able to use the key property and it's benefits.

https://jsfiddle.net/opm3Lsg8/2/

Konstantin Vitkovsky
  • 1,198
  • 11
  • 15
  • I used to think the key prop was needed to avoid "bugs" when the data changed, but it's starting to dawn on me that maybe it's just less efficient to not use them? Anyway, I can't refactor the markup the way you want me to because it would break my CSS. – Lars Nyström Oct 21 '16 at 13:06
  • yes, following the documentation keys are generally needed to provide optimization during re-rendering, also you can read [this explanation on toptal](https://www.toptal.com/react/interview-questions#iquestion-91681) (click "view the answer") to better understand the topic – Konstantin Vitkovsky Oct 21 '16 at 13:08
  • ok, but note, that this approach (putting nested list inside li-element, instead of alongside) is more preferable, [more info here](http://stackoverflow.com/a/5899394/7049654) – Konstantin Vitkovsky Oct 21 '16 at 13:54
  • Ah, yes you're probably right. I read the [mdn article](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul) on `
      ` but I think the section on permitted content there needs to be updated. But the main reason I'm putting them alongside is because that's how [bootstrap list groups](http://getbootstrap.com/components/#list-group) works because I saw some example on how to nest bootstrap list groups that way.
    – Lars Nyström Oct 21 '16 at 14:19
0

Since you are mapping an object in ReactJS, you will always have to add a key to it.

From the moment a loop or any other form of iteration is used this is required.

Just adding a key with the same value as id will fix it:

key={id}

Let me know if you need more information.

headers.map((header, index) => {
   <div key={index}>
      header
   </div>
}
mitchken
  • 790
  • 9
  • 26
  • But React isn't printing any warnings in the console? And how do I add the key prop when the map function returns an array of elements and not just one element? – Lars Nyström Oct 21 '16 at 09:26
  • if it returns an array you can also get the key/index from them. I added it to my answer – mitchken Oct 21 '16 at 11:42