Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).

State: For Magic Updating Good Times

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

It 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.

Props vs State

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.

Adding highlightRowId State

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
}

Using State in render()

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.

React Developer Tools

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!

How React Re-Renders Things

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.

But... who Updates State?

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!

Leave a comment!

6
Login or Register to join the conversation
Pascal A. Avatar
Pascal A. Avatar Pascal A. | posted 1 year ago

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.

2 Reply

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!

Reply

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

Reply

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!

Reply
Amy anuszewski Avatar
Amy anuszewski Avatar Amy anuszewski | posted 5 years ago

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

Reply

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!

10 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

While the concepts of this course are still largely applicable, it's built using an older version of Symfony (4) and React (16).

What PHP libraries does this tutorial use?

// 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
    }
}

What JavaScript libraries does this tutorial use?

// 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
    }
}
userVoice