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 SubscribeInside RepLogs
, this form logic is starting to get pretty big: we have a handler function and most of the markup comes from the form! And this is only going to get more complex when we finish the form stuff and add future things like validation.
So, to reduce complexity, I think it's time to take this form and put it into its own new component. Remember: our app starts with RepLogApp
on top: it's our smart, good-looking, fun-loving, stateful component. Then, it renders a dumb, tries-its-best, presentation component that holds the markup. That setup is important, and in a small app, it may be all you need! But, if RepLogs
gets too big or confusing, you can choose to break things down even more.
Inside the RepLog
directory, create a new file called, how about, RepLogCreator.js
. This will be another dumb, presentation component. And so, it can be a function, like RepLogList
. Copy its import statements, paste, and export default function RepLogCreator
with the normal props
arg. Get things working by returning a hardcoded div!
I'm going to be a form when I grow up!
import React from 'react'; | |
import PropTypes from 'prop-types'; | |
export default function RepLogCreator(props) { | |
return ( | |
<div>I'm going to be a form when I grow up!</div> | |
); | |
} |
Love it! Back in RepLogs
, on top, import RepLogCreator
from ./RepLogCreator
. Then, down in render, above the form, use <RepLogCreator />
.
... lines 1 - 3 | |
import RepLogCreator from './RepLogCreator'; | |
... lines 5 - 16 | |
export default function RepLogs(props) { | |
... lines 18 - 33 | |
return ( | |
... lines 35 - 61 | |
<RepLogCreator/> | |
... lines 63 - 99 | |
); | |
} | |
... lines 102 - 110 |
Ok, let's go check it out! So far, so good.
Next, copy all of the form markup, delete it, go to RepLogCreator
and... paste!
... lines 1 - 3 | |
export default function RepLogCreator(props) { | |
return ( | |
<form className="form-inline" onSubmit={handleFormSubmit}> | |
... lines 7 - 39 | |
</form> | |
); | |
} |
That looks cool... but, come on. We know that nothing ever works on the first try. Try it - yep... we are rewarded with a nice big error!
handleFormSubmit
is not defined
coming from RepLogCreator
line 6: it's our onSubmit
! I totally forgot about that! Go grab it from RepLogs
- and, by the way - check out how small this component is looking - then, inside RepLogCreator
, paste.
... lines 1 - 4 | |
function handleFormSubmit(event) { | |
event.preventDefault(); | |
console.log('I love when a good form submits!'); | |
console.log(event.target.elements.namedItem('reps').value); | |
onNewItemSubmit('Big Fat Cat', event.target.elements.namedItem('reps').value); | |
} | |
... lines 13 - 52 |
The last missing piece is the onNewItemSubmit()
callback: this is passed from RepLogApp
to RepLogs
. And now, we need to pass it once again to RepLogCreator
. Because we need a new prop, define it first at the bottom in propTypes
: RepLogCreator.propTypes =
an object and... go steal this code from RepLogs
.
... lines 1 - 54 | |
RepLogCreator.propTypes = { | |
onNewItemSubmit: PropTypes.func.isRequired, | |
}; |
Excellent! Now that we are requiring this prop, head back up to render()
and destructure it: const { onNewItemSubmit } = props
.
... lines 1 - 3 | |
export default function RepLogCreator(props) { | |
const { onNewItemSubmit } = props; | |
... lines 6 - 52 | |
} | |
... lines 54 - 58 |
Cool! Finally, in RepLogs
, nice! PhpStorm is already telling us that we're missing a required prop: pass this onNewItemSubmit={onNewItemSubmit}
.
... lines 1 - 24 | |
return ( | |
... lines 26 - 52 | |
<RepLogCreator | |
onNewItemSubmit={onNewItemSubmit} | |
/> | |
... line 56 | |
); | |
... lines 58 - 67 |
And... we're done! Probably... Let's go find out: refresh. The form renders... we can select something and... it does print. Awesome!
And now.... it's time to start bending some of the nice rules that we've been talking about. RepLogCreator
is a "dumb" component. It's like a template: its main job is to render markup, not to contain state or a lot of logic. Because "dumb", presentation components only really render things, we usually create them as functions instead of a class... just because we can and we're lazy!
Well... that's a good rule, but it's not always true. Right now, our handler function uses the name attribute of the input element to get a reference to the underlying DOM element. Then, it reads its value. It turns out that React has its own system for allowing you to reference the corresponding DOM element for any of our React elements. It's called "refs".
There are a few ways to use this "refs" system. But, the recommended way requires your component to be a class. And, that's ok! There are some legitimate situations where a dumb component needs a class. Refs are one of those.
No big deal: let's change this to a class! First, we need to also import Component
from React. Then, export default class RepLogCreator extends Component
. And, of course, we now need to put all of this inside a render()
function. Let's indent everything one level, and close the function.
import React, { Component } from 'react'; | |
... lines 2 - 3 | |
export default class RepLogCreator extends Component { | |
render() { | |
const { onNewItemSubmit } = props; | |
... line 7 | |
function handleFormSubmit(event) { | |
... lines 9 - 14 | |
} | |
... line 16 | |
return ( | |
<form className="form-inline" onSubmit={handleFormSubmit}> | |
... lines 19 - 51 | |
</form> | |
); | |
} | |
} | |
... lines 56 - 60 |
Yep! Webpack is happy! Now that we have a proper class, we don't need to put handleFormSubmit()
inside of render()
anymore. Nope, we can access the props from anywhere as this.props
.
So, copy that function & the const
, paste it in the class, turn it into a property, and move the const
into the function. Oh, and try not to mess up the syntax like I just did.
... lines 1 - 5 | |
handleFormSubmit(event) { | |
event.preventDefault(); | |
const { onNewItemSubmit } = this.props; | |
console.log('I love when a good form submits!'); | |
console.log(event.target.elements.namedItem('reps').value); | |
onNewItemSubmit('Big Fat Cat', event.target.elements.namedItem('reps').value); | |
} | |
... lines 15 - 60 |
Better! Back in render()
, now we'll call this.handleFormSubmit
.
... lines 1 - 15 | |
render() { | |
return ( | |
<form className="form-inline" onSubmit={this.handleFormSubmit}> | |
... lines 19 - 52 | |
); | |
} | |
... lines 55 - 60 |
Let's go check it out! Head back to the browser refresh! It loads... and when you submit... error! Woh! And then the page reloaded! Oh no!
There are two evil problems working together against us! For the first, my bad! We need to make sure that event.preventDefault()
is always the first line in a handle function. You'll see why when we refresh and try the form again.
Ah yes: here is the real error:
Cannot read property props of undefined
coming from line 7. We know this problem: we forgot to bind our handler function to this
... so this
is not our RepLogCreator
instance. When the page refreshed, it was because this error killed our code even before we called event.preventDefault()
.
We know the fix: whenever you have a handler function that's a property on your class, we need to create a constructor, call super(props)
, then bind that function with this.handleFormSubmit = this.handleFormSubmit.bind(this)
.
... lines 1 - 4 | |
constructor(props) { | |
super(props); | |
this.handleFormSubmit = this.handleFormSubmit.bind(this); | |
} | |
... lines 10 - 65 |
That should do it! Move back, refresh, fill in the form and... yes! Our app logs correctly again. It's time to use refs
to finish our form logic so we can update the repLogs
state.
Hey Monia,
Thank you for your feedback! Haha, "creator" is just because it "creates" rep log, it? But it also handle the submit, so it's not just a form. But anyway, that's not too much important, you can make up whatever name you like ;)
Cheers!
// 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
}
}
Hello, thank you for the work done, it's very intuitive :).
But why is the component name "repLogCreator" and not "repLogForm"?
I am a bit confused with these names... maybe because I have a twig backroud :D :D