1

Problem with child keys error but only when I refresh the browser. I am using reactjs and nextjs.

index.js:1 Warning: Each child in a list should have a unique "key" prop.

let cnt = 1;
  const counter = () => {
    return cnt++;
  };

  let pCnt = 1;
  const pCounter = () => {
    return pCnt++;
  };

  const renderInvoiceData = invoice => {
    let invoiceData = Object.values(invoice);
    let invoiceKeys = Object.keys(invoice);

    return invoiceKeys.map((invKey, index) => {
      return (
        <div className="col-3" key={counter()}>
          {invKey}
          <br />
          {invoiceData[index]}
        </div>
      );
    });
  };

  return (
    <div id="invoices" className="home-page">
      {data.map((invoice, index) => (
        <div className="card text-left mb-3">
          <div className="card-header">
            <Link href="/invoicerecord">
              <a>{invoice.invoiceNumber}</a>
            </Link>
          </div>
          <div className="card-body" key={pCounter()}>
            <div className="row">{renderInvoiceData(invoice)}</div>
          </div>
        </div>
      ))}
    </div>
  );
};

I am definitely providing unique keys as expected. This issue only happens when I manually refresh the browser using the reload button and repost the url

Troy Crowe
  • 123
  • 2
  • 12
  • for starters, dont use a counter like this. You should try to have the keys be as static as possible. React uses the key for rendering performance, if you render an input element in here for instance you will notice on each keypress the input will lose focus. thats because a random number like this tells react to unmount and remont elements. – John Ruddell Jul 11 '19 at 21:34
  • [More info related to hardening your keys](https://stackoverflow.com/a/56929737/2733506). Now the issue here is your key is not set inside the return. `{data.map((invoice, index) => (` the first `div` here should have a key – John Ruddell Jul 11 '19 at 21:41

1 Answers1

2

You aren't setting the key on your map (in the return of your render).

{ data.map( invoice => (
    <div key={invoice.id} className="card text-left mb-3">
    {/*  ^--------------^ */ }
      <div className="card-header">
        <Link href="/invoicerecord">
          <a>{invoice.invoiceNumber}</a>
        </Link>
      </div>
      <div className="card-body" key={pCounter()}>
        <div className="row">{renderInvoiceData(invoice)}</div>
      </div>
    </div>
  ));
}

You shouldn't be using a counter to track your keys, thats a really inefficient way to do that. Instead use related data to track the rendering. invoice.id assuming you have a unique id for your invoices would be a great example of a key to use in React :)

John Ruddell
  • 25,283
  • 6
  • 57
  • 86
  • The invoice is a collection. If you notice "renderInvoiceData" is one invoice and the second array map in the "renderInvoiceData" contains the fields. It is done that way do to the fact that the fields can be different from one company to another. – Troy Crowe Jul 12 '19 at 00:47
  • One more thing. I used the index parameter for the first set, which is used to create the row, at first. I think I will revert back to that. I however have tried that on the second set, for the columns, but they will not be unique which is why I use the counter. The solution I am using works great as long as I do not reload the browser with the same url. I think it actually has to do with how the data is cached to the page and when reloading it does not see any change. – Troy Crowe Jul 12 '19 at 00:51
  • No you're not completely correct there. You just don't notice a performance issue because the browser isn't doing too much work. You can use an index but that's still not as good as a unique ID from your database. If you have more than one item like you do with two for loops, you can concatenate a string with the id. Aka ```key={`invoice-data-${index}`}``` – John Ruddell Jul 12 '19 at 01:05
  • So I used the index value from "invoiceKeys.map((invKey, index)" for the second loop and I get the same error. I will try the "invoice-data-${index}" solution you provided and see if that works. I however think that the second invoice record will have the same set of key value as the first set however. Another words the second loop with generate key="invoice-data-0" and the second iteration of the second loop with generate same key="invoice-data-0" for its first column. Not sure what I am misunderstanding. – Troy Crowe Jul 12 '19 at 01:16
  • Hey, that was useful to get rid of the counter, but it did not fix the underlying issue. It still has a problem on page reload. It still get the keys error on reload. Actually I provided just the "index" and have no issue until reload. – Troy Crowe Jul 12 '19 at 01:21
  • Omg, don't put the same key for both. Name them differently lol (```key={`invoice-${index}`}``` and ```key={`invoice-data-${index}`}```). Plz look at my answer again where the comment is about the key. It's in the `return` where you didn't specify it – John Ruddell Jul 12 '19 at 01:29
  • I thank you very much for your involvement in helping me to find a solution, and I have changed my keys as you have suggested even though they do not render matching as I have them completely different and had already removed the counter before my previous couple of responses. Even so, the underlying issue of this post is not corrected. I only have a problem when I reload the page. This is done by selecting the browser reload icon and or highlighting the url and selecting enter which reloads the same path as the current page. Only when reloading the page I have the issue. – Troy Crowe Jul 12 '19 at 02:52
  • @TroyCrowe can you reproduce the issue on a codesandbox? I dont know what tech stack you are using. Maybe one of the things you're using is re-hydrating the UI with the same tree and then it tries to re-render? – John Ruddell Jul 12 '19 at 02:54
  • That is what I have been thinking which is why I was suggesting when reloading the page the data is the same and therefore it is reloading with the same set of keys. I am using strict reactjs and nextjs – Troy Crowe Jul 12 '19 at 03:02
  • "name": "captivebilling", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "next", "build": "next build", "start": "next start", "json-server": "json-server --watch db.json --port 4000 --delay 200" }, – Troy Crowe Jul 12 '19 at 03:03
  • "dependencies": { "@zeit/next-css": "^1.0.1", "axios": "^0.19.0", "bootstrap": "^4.3.1", "classnames": "^2.2.6", "clsx": "^1.0.4", "next": "^8.1.0", "react": "^16.8.6", "react-dom": "^16.8.6", "react-fontawesome": "^1.6.1", "react-toastify": "^5.3.1" }, "devDependencies": { "eslint": "^6.0.1", "eslint-plugin-react": "^7.14.2", "json-server": "^0.15.0", "react-hooks": "^1.0.1" } – Troy Crowe Jul 12 '19 at 03:04
  • I just posted all of the libraries I am using. I am working withing VS Code. – Troy Crowe Jul 12 '19 at 03:05
  • Sorry, one more thing. I am actually not using the json-server at this time. Also, my data is being fetched from a .net core web api via a custom Axios fetch hook. I have reused the "useAxiosFetch" from this project. https://github.com/pkellner/pluralsight-course-using-react-hooks – Troy Crowe Jul 12 '19 at 03:07