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 SubscribeIt turns out that a React component has two types of data: props, which we access with this.props
and something different called state. Understanding the difference between props and state is, well, just about the most important thing in React.
Once a component receives a prop, like, we pass true
for the withHeart
prop, that prop is constant. A component never changes a prop: it's just a value that it receives, reads and uses.
So, when you think of props, think of "data that doesn't change". We're going to slightly complicate that later... but for now, think: props are data that we never change, they're constant. Or, "immutable" to be more hipster.
The withHeart
prop is a really good example: once our app renders, the heart will be there or not be there. We don't need that to ever change based on some user interaction or some data changing. It will always be there... or it will always not be there.
But when you want to make things interactive, when you have data that needs to change while your app is alive, well, then you need to store that data somewhere else: state, which you can access with this.state
.
So... why exactly is there this distinction between props
that must be immutable and state
, which is allowed to change? We'll learn about that: it goes to the core of React's architecture.
But first, let's do some coding and add our first user interactivity! Here's the goal: when the user clicks a row in the table, I want to highlight that row.
From a data standpoint, this means that, somewhere in our RepLogApp
component, we need to keep track of which of these rep log rows is currently highlighted. We can do that by keeping track of the highlighted row id. Literally, we could store that row 1, 2 or 8 is highlighted.
But because this data will change throughout the lifecycle of our app, we're not going to store the id in props
. Nope, we're going to store it as state
.
Once you've decide that you have some data that needs to be stored as state, you need to initialize that value on your state. That's always done the same way: by overriding the constructor()
function. The constructor of React components receive a props
argument. And then, you're supposed to call super(props)
to execute the parent constructor. You'll see this pattern over and over again.
To set the initial state
, just set the property directly: this.state
equals an object, with, how about, a highlightedRowId
key set to null. Nothing will be highlighted at first.
... lines 1 - 2 | |
export default class RepLogApp extends Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
highlightedRowId: null | |
}; | |
} | |
... lines 11 - 100 | |
} |
Cool! Down in render, we can use this data just like we do with props. But, let's use object destructuring to get this value as a variable: const { highlightedRowId } = this.state
.
... lines 1 - 11 | |
render() { | |
const { highlightedRowId } = this.state; | |
... lines 14 - 99 | |
} | |
... lines 101 - 102 |
This is another common React pattern. Instead of referring to this.state.highlightedRowId
down below, we use destructuring so that we can be lazier later and use the shorter variable name.
I'll break the tr
onto multiple lines. If this row should be highlighted, we'll give it a special class: add className={}
and use the ternary syntax: if highlightedRowId ===
this repLog's id, then add an info
class. Otherwise, print no class. This info
class already exists in the CSS of our app.
... lines 1 - 11 | |
render() { | |
... lines 13 - 25 | |
return ( | |
... lines 27 - 39 | |
{repLogs.map((repLog) => ( | |
<tr | |
key={repLog.id} | |
className={highlightedRowId === repLog.id ? 'info' : ''} | |
> | |
... lines 45 - 48 | |
</tr> | |
))} | |
... lines 51 - 98 | |
); | |
} | |
... lines 101 - 102 |
Cool! If we try it now, we, of course, don't expect anything to be highlighted: we initialized the state to null
. And, yep! It works... probably: none of the rows have the class.
One of the most awesome things about developing with React is a tool called the "React Developer Tools". It's an extension for Chrome or Firefox, so you can install it from the Chrome Web Store or from Firefox Add-Ons.
After installing it, your browser's developer tools will have a new React tab! Ooooooo. Check this out: it shows your entire component and element hierarchy. And, you can click to see all the different props for every part.
Click on the RepLogApp
component on top. Woh! It shows us the withHeart
prop and the highlightedRowId
state! And, we can mess with it! Remember: the rep log ids are 1, 2 and 8. Change highlightedRowId
to 2. Boom! That row instantly updates to have the class! Change it to 1... and 8. Super fun!
This looks like magic... but really, it's just that React is really, really smart. Behind the scenes, whenever some "state" changes on a React component, React automatically re-executes the render()
method on that component. So, if we change this number from 8 back to 2, it calls the render()
method again and we return the new React element objects.
Because of this, you might think that all of this HTML is completely replaced each time we re-render. But actually, dang, nope! React is, yet again, too smart for that. Instead of replacing everything, React compares the React element objects from before the re-render to the new element objects after. Yep, it performs a "diff" to see what changed. And then, it only updates the parts of the DOM that need to change.
We can actually watch this happen! Open the table but collapse the rows so we can see them all. Then, I'll re-select the RepLogApp
component and scroll back down. Watch closely when we change the state: from 2 to 1. Did you see it highlight the two class attributes that changed in yellow? Watch again: 1 to 8.
That yellow highlight is my browser's way of telling us that these two attributes were literally the only thing that changed. In React, we re-render all of the elements. But in the DOM, React only updates the things it needs to.
The big takeaway is this: state
is allowed to change. And each time it does, React calls render()
on our component and updates the DOM as needed.
Of course, our ultimate goal is not just to update state via the cool React dev tools. Nope, we want to update the state when the user clicks a row.
Before we do that, there is one small housekeeping item. In addition to destructuring state, I like to do the same with props. Add const { withHeart } = this.props
. Then below, use if withHeart
.
... lines 1 - 11 | |
render() { | |
... line 13 | |
const { withHeart } = this.props; | |
... lines 15 - 16 | |
if (withHeart) { | |
... line 18 | |
} | |
... lines 20 - 100 | |
} | |
... lines 102 - 103 |
It's a small detail, but it's nice to setup all the variables right on top.
Now, let's add some click magic and update our state!
Hey Pascal A.
Thanks for sharing it with others - it's hard to keep up with all the new stuff, everything changes too quickly :)
Cheers!
Hi there
when i update the state in the react dveloper tools it doesn't highlight the row and the className is still ''"
But it works if i use just == instead of === in className
Hey miguelbarreiro85
I think something changed in react dev tools, and it doesn't convert value to int, but our ID is int, that's why ===
doesn't work. You can check it manually. Try to change highlightedRowId
in the constructor()
and you will see that ===
work like it should!
Cheers!
Not sure if this is supposed to happen, but right at 4:33, the screen goes black in the video and doesn't come back until 5:19
Hey Amy! Definitely not supposed to be like that, I've just updated the video and you should see the proper video in that spot. Thanks for letting me know!
// 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
}
}
HI there,
I'm learning React these days, better late than never:)
Thanks to this tutorial.
I notice one thing which could help some people. When you install the react developper tool under chrome. The tools doesn't appears anymore under the name of react under the chrome developper tools. The new name is Components.
Have a nice day.