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 SubscribeWhen the user tries to submit a negative quantity, we need to show an error! On a React level, this means that we'll need new state to store that message. And, like we just discussed, because this state is really tied to our form and is meant to power the UI, the best place for it is inside RepLogCreator
.
To add state, we always start the same way: initialize it in the constructor: this.state =
an object, and call it quantityInputError
set to empty quotes.
... lines 1 - 4 | |
constructor(props) { | |
... lines 6 - 7 | |
this.state = { | |
quantityInputError: '' | |
}; | |
... lines 11 - 22 | |
} | |
... lines 24 - 99 |
Below, remove the todo and add this.setState()
with quantityInputError
set to Please enter a value greater than zero
.
... lines 1 - 24 | |
handleFormSubmit(event) { | |
... lines 26 - 31 | |
if (quantityInput.value <= 0) { | |
this.setState({ | |
quantityInputError: 'Please enter a value greater than 0' | |
}); | |
... lines 36 - 38 | |
} | |
... lines 40 - 50 | |
} | |
... lines 52 - 100 |
And when the form submit is successful, we need to make sure any existing error is removed. Copy the setState()
line and set it to an empty string.
... lines 1 - 24 | |
handleFormSubmit(event) { | |
... lines 26 - 47 | |
this.setState({ | |
quantityInputError: '' | |
}); | |
} | |
... lines 52 - 100 |
State, check! Down in render, as always, start by destructuring the variable: const { quantityInputError } = this.state
. Let's use this to do two things: add a class to make the form element look red and print the message.
... lines 1 - 52 | |
render() { | |
const { quantityInputError } = this.state; | |
... lines 55 - 93 | |
} | |
... lines 95 - 100 |
Because we're using Bootstrap, to make the field red, the form-group
div needs a new has-error
class. Empty out className
, enter into JavaScript mode and use "ticks" to use a "template literal". This makes using multiple classes with logic a bit easier: re-add form-group
, then type ${}
to do "string interpolation". Oooo. Inside, if quantityInputError
then print has-error
, else print nothing.
... lines 1 - 55 | |
return ( | |
... lines 57 - 75 | |
<div className={`form-group ${quantityInputError ? 'has-error' : ''}`}> | |
... lines 77 - 86 | |
</div> | |
... lines 88 - 92 | |
); | |
... lines 94 - 100 |
And to actually print the message, go down after the input. Here, I don't just want to print the error, I want to surround it in a red div, but only if there actually is an error. We could use the ternary syntax here... and use some inline JSX. That's fine.
But instead, I want to show you a cool, but weird, shortcut syntax that we can use whenever we want to print a string only when that string is not empty. Here it is: quantityInputError &&
and then the JSX: <span className="help-block">
, print quantityInputError
, close the tag, and exit from JavaScript mode.
... lines 1 - 75 | |
<div className={`form-group ${quantityInputError ? 'has-error' : ''}`}> | |
... lines 77 - 85 | |
{quantityInputError && <span className="help-block">{quantityInputError}</span>} | |
</div> | |
... lines 88 - 100 |
Woh. Before we talk about this, try it! Move over, make sure the page is fully refreshed, select an item, be annoying and use a negative number and... there it is! Oh, it's ugly: we'll fix that soon.
But first, about this syntax! It's weird because this bit of code works different in JavaScript versus PHP! In JavaScript, if quantityInputError
is empty, or "falsey", this return false and we print nothing. But if quantityInputError
has some text in it, so, it's "truthy", then JavaScript returns the second part of the expression: our JSX. So, this entire block will either return false
or this JSX. In PHP, this would always return false or true.
So... yes, this is another fancy syntax. If you love it, nice! If you hate it, use the ternary syntax instead.
Before we move on, oof, we need to make this look less ugly. Go back into RepLogs
and scroll down. Add a few divs: className="row"
and another className="col-md-6"
. Move RepLogCreator
inside.
... lines 1 - 30 | |
return ( | |
... lines 32 - 58 | |
<div className="row"> | |
<div className="col-md-6"> | |
... lines 61 - 63 | |
</div> | |
</div> | |
... line 66 | |
); | |
... lines 68 - 77 |
Then, back in RepLogCreator
, find the form element and... remove the form-inline
class.
... lines 1 - 55 | |
return ( | |
<form onSubmit={this.handleFormSubmit}> | |
... lines 58 - 92 | |
); | |
... lines 94 - 100 |
Give your browser a nice refresh. This puts the form elements onto their own lines. And that makes our validation error look much nicer.
We'll handle server-side validation later. But right now, let's talk about a totally different way of handling form data: controlled components.
"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
}
}