If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeWe just rendered an array of React <tr>
element objects. And as a thank you, React has awarded us with a big ugly error! It says:
Warning: each child in an array should have a unique "key" prop.
Rude! Here we are, with a perfectly functional table, and React is ruining our magical moment. Well... ok, it's warning us for a good reason. And, PhpStorm was also trying to help.
Here's the deal: soon, we're going to use React to perform automagical updates to the UI when data changes. Eventually, we'll use this to add and remove rows to this table as we create and delete rep logs! But... when you have a collection of items like this, if we update the data on one of the rep logs, React isn't totally sure which row to update in the DOM. For example, if this rep log's weight suddenly changed to 150, it's possible that React would update the wrong row!
To help React, we need to give each row a unique key - kind of like how each row in a database table has a primary key. To do this, go to the outer-element of each item and literally add a key
prop. This needs to be something that is unique and won't change. So, basically, it should be the id
.
... lines 1 - 2 | |
export default class RepLogApp extends Component { | |
render() { | |
... lines 5 - 15 | |
const repLogElements = repLogs.map((repLog) => { | |
return ( | |
<tr key={repLog.id}> | |
... lines 19 - 23 | |
) | |
}); | |
... lines 26 - 54 | |
} | |
} |
Solved! This key
prop isn't a big deal, it's just a chore you need to handle each time you render a collection. But don't worry: if you forget, React will remind you!
Try it now: head over and refresh! The page still works, and the warning is gone!
There's one minor downside to this new setup. Up here, we use the map
function to create an array of repLogElements
. Down below, we render that.
What's the problem? Well, just that, if you're looking at render()
to see your markup, when you see {repLogElements}
, you need to scroll back up to see what this is. Whenever possible, it's better to keep all of your markup in one place.
And, we can do that... by being a bit clever. Copy the repLogs.map()
code, then delete the repLogElements
variable entirely. Back inside JSX, clear out the variable and... paste!
... lines 1 - 15 | |
return ( | |
... lines 17 - 19 | |
<table className="table table-striped"> | |
... lines 21 - 28 | |
<tbody> | |
{repLogs.map((repLog) => { | |
return ( | |
<tr key={repLog.id}> | |
<td>{repLog.itemLabel}</td> | |
<td>{repLog.reps}</td> | |
<td>{repLog.totalWeightLifted}</td> | |
<td>...</td> | |
</tr> | |
) | |
})} | |
</tbody> | |
... lines 41 - 48 | |
</table> | |
... lines 50 - 51 | |
); | |
... lines 53 - 55 |
That's it! It's really the same thing we had before! This loops over the repLogs
array, builds an array of "rep log" element objects, then... prints them!
Try it! Move over and, refresh! Yes!
At first, the syntax may look weird. But, it's now very obvious that inside the tbody
, we are printing a bunch of tr
elements.
And if this isn't fancy enough for you, well, you won't be disappointed. If you're still getting used to how the arrow functions look, you may not love this next change. But, if it's too weird, don't use it!
Because the arrow function is, um, a function, we usually surround the body of the function with curly braces. But, if the only line in your function is the return statement, you can remove the curly braces and just put the code for the return. Oops, I have one extra curly brace.
... lines 1 - 28 | |
<tbody> | |
{repLogs.map((repLog) => ( | |
<tr key={repLog.id}> | |
<td>{repLog.itemLabel}</td> | |
<td>{repLog.reps}</td> | |
<td>{repLog.totalWeightLifted}</td> | |
<td>...</td> | |
</tr> | |
))} | |
</tbody> | |
... lines 39 - 53 |
We now have a function with one argument that returns this JSX element.
Move over and try it! Woohoo! It works! These are the types of little things in React that you don't need to do. And, if it makes your head spin, keep it simple. But, I want you to at least know about these tricks. Because, often, it's these types of shortcuts that end up making React look hard.
Let's finish building the static version of our app next: by adding the form!
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": "^7.2.0",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^1.6", // 1.9.1
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.3
"doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
"doctrine/orm": "^2.5", // v2.7.2
"friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
"friendsofsymfony/user-bundle": "dev-master#4125505ba6eba82ddf944378a3d636081c06da0c", // dev-master
"sensio/framework-extra-bundle": "^5.1", // v5.2.0
"symfony/asset": "^4.0", // v4.1.4
"symfony/console": "^4.0", // v4.1.4
"symfony/flex": "^1.0", // v1.17.6
"symfony/form": "^4.0", // v4.1.4
"symfony/framework-bundle": "^4.0", // v4.1.4
"symfony/lts": "^4@dev", // dev-master
"symfony/monolog-bundle": "^3.1", // v3.3.0
"symfony/polyfill-apcu": "^1.0", // v1.9.0
"symfony/serializer-pack": "^1.0", // v1.0.1
"symfony/swiftmailer-bundle": "^3.1", // v3.2.3
"symfony/twig-bundle": "^4.0", // v4.1.4
"symfony/validator": "^4.0", // v4.1.4
"symfony/yaml": "^4.0", // v4.1.4
"twig/twig": "2.10.*" // v2.10.0
},
"require-dev": {
"symfony/debug-pack": "^1.0", // v1.0.6
"symfony/dotenv": "^4.0", // v4.1.4
"symfony/maker-bundle": "^1.5", // v1.5.0
"symfony/phpunit-bridge": "^4.0", // v4.1.4
"symfony/web-server-bundle": "^4.0" // v4.1.4
}
}
// package.json
{
"dependencies": {
"@babel/plugin-proposal-object-rest-spread": "^7.12.1" // 7.12.1
},
"devDependencies": {
"@babel/preset-react": "^7.0.0", // 7.12.5
"@symfony/webpack-encore": "^0.26.0", // 0.26.0
"babel-plugin-transform-object-rest-spread": "^6.26.0", // 6.26.0
"babel-plugin-transform-react-remove-prop-types": "^0.4.13", // 0.4.13
"bootstrap": "3", // 3.3.7
"copy-webpack-plugin": "^4.4.1", // 4.5.1
"core-js": "2", // 1.2.7
"eslint": "^4.19.1", // 4.19.1
"eslint-plugin-react": "^7.8.2", // 7.8.2
"font-awesome": "4", // 4.7.0
"jquery": "^3.3.1", // 3.3.1
"promise-polyfill": "^8.0.0", // 8.0.0
"prop-types": "^15.6.1", // 15.6.1
"react": "^16.3.2", // 16.4.0
"react-dom": "^16.3.2", // 16.4.0
"sass": "^1.29.0", // 1.29.0
"sass-loader": "^7.0.0", // 7.3.1
"sweetalert2": "^7.11.0", // 7.22.0
"uuid": "^3.2.1", // 3.4.0
"webpack-notifier": "^1.5.1", // 1.6.0
"whatwg-fetch": "^2.0.4" // 2.0.4
}
}