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

New Component to Hold our Form

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

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

Creating RepLogCreator

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!

Introducing: Refs

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

Refactoring our Dumb Component to a Class

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.

Leave a comment!

2
Login or Register to join the conversation
Helmi Avatar

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

Reply

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!

1 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