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 SubscribeI want to do something when the user clicks the tr
element. In React, how can we attach event listeners? What is the React version of selecting an element in jQuery and adding an on click
function?
Oh, you're going to love... or maybe hate the answer. I love it, because it's simple! To add a click handler to this tr
add... onClick
and pass this a function. I'll use an arrow function and, for now, just console.log('OMG - an onClick!')
.
... lines 1 - 11 | |
render() { | |
... lines 13 - 26 | |
return ( | |
... lines 28 - 40 | |
{repLogs.map((repLog) => ( | |
<tr | |
... lines 43 - 44 | |
onClick={() => console.log('OMG an onClick!') } | |
> | |
... lines 47 - 51 | |
))} | |
... lines 53 - 100 | |
); | |
} | |
... lines 103 - 104 |
Move over, refresh, click, and... find the terminal. Boom!
Cool. Let's review our goal: to highlight a row when we click on it. So... hmm... onClick: if we could update the highlightedRowId
state to the correct id, React would re-render and take care of the rest! Easy! Inside the arrow function, update the state with this.setState()
. Pass this an object with the state key or keys that you want to change. For us, highlightedRowId
set to the id of this rep log: repLog.id
.
... lines 1 - 40 | |
{repLogs.map((repLog) => ( | |
<tr | |
... lines 43 - 44 | |
onClick={() => this.setState({highlightedRowId: repLog.id}) } | |
> | |
... lines 47 - 51 | |
))} | |
... lines 53 - 104 |
Coolio! But, an important note! In the constructor, we initialized the state by setting the this.state
property directly. This is the only place, ever, that you will change or set the state property directly. Everywhere else, always, you need to call this.setState()
. If you don't, puppies will stare at you with sad eyes.
And, more important, if you modify the state property directly, React won't re-render. The reason is simple: this is what React uses to know that you changed the state and so, to start the re-rendering.
Bah, let's go try it already! Refresh! And... click! Woohoo! We just added our first bit of interactivity. In the React dev tools, if you click on RepLogApp
, you can watch the highlightedRowId
state change as we click the rows. Pretty freaking cool.
Just like with jQuery or plain JavaScript, when you add an event callback, your function is passed an event object. We don't need the event in this case, but it contains all the same information you're used to having. Actually, this isn't a native DOM "event" object. React passes you what's called a "SyntheticEvent": an event object that wraps the normal event, has all the same methods and properties, but adds a few things to make life easier.
... lines 1 - 41 | |
<tr | |
... lines 43 - 44 | |
onClick={(event) => this.setState({highlightedRowId: repLog.id}) } | |
> | |
... lines 47 - 104 |
Putting all this logic inline is fine... but it can become hard to read. So, instead, I like to make the handler a property on my class. Start by adding a new method: handleRowClick
that will accept the repLogId
that was just clicked and also the event object itself... just to show that we can we pass this:
Next, steal the state-setting code and paste it here, but with highlightedRowId
set to repLogId
. And... we should probably close the method so Webpack isn't so mad at me!
... lines 1 - 11 | |
handleRowClick(repLogId, event) { | |
this.setState({highlightedRowId: repLogId}); | |
} | |
... lines 15 - 108 |
Below, call it: this.handleRowClick()
with repLog.id
and event
.
... lines 1 - 15 | |
render() { | |
... lines 17 - 30 | |
return ( | |
... lines 32 - 45 | |
<tr | |
... lines 47 - 48 | |
onClick={(event) => this.handleRowClick(repLog.id, event)} | |
> | |
... lines 51 - 104 | |
); | |
} | |
... lines 107 - 108 |
I like it! Let's make sure we didn't bork our cool app: back to the browser! Refresh! Yea! It still works!
This is the power of React! It doesn't care how many different things in your UI need to change when some state changes, it takes care of everything.
And now, it's time to talk about organization. RepLogApp is big, and when things get too big, they get confusing. Let's move some code into a new child component.
"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
}
}