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

Dumb Components with State

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

Our form works... but has no validation. Well, that's not completely true. In general, there are three types of validation. First, server-side validation, which we absolutely need and will talk about later. Second, client-side validation via JavaScript, which is optional, but a nice way to give quick feedback. And third, client-side HTML5 validation, which isn't flexible at all, but is super easy to add. In fact, we already have some: the required attributes on the form: these prevent the user from submitting the form empty.

But, right now, you can enter a negative number. And... that makes no sense! So let's add some client-side validation in our React app to prevent this. Oh, and by the way, the input field already disallows entering letters - that's another HTML5 validation feature thanks to this type="number".

Preventing the Form Submit

The form handling-logic lives in RepLogCreator.handleFormSubmit(). So, if we want to prevent the form from submitting or add an error message, that code needs to live here.

And the first half is easy! If quantityInput.value <= 0 then... somehow, we need to add an error message. And because we don't want to continue processing, just return.

... lines 1 - 20
handleFormSubmit(event) {
... lines 22 - 27
if (quantityInput.value <= 0) {
// TODO - print some validation error!
// don't submit, or clear the form
return;
}
... lines 34 - 41
}
... lines 43 - 88

Let's see how this looks so far. Try negative 10, select an item and... yep! The form does not clear.

Dumb Components with State

Great! Now, how can we add an error message? Think about it: sometimes our form will need an error message and sometimes it will not. The error is a piece of data that we need to change. In other words, it needs to be stored as state!

Ya! If we had an "error message" state, we could use that down in render(). And then, whenever the state changed, React would re-render this component and print out the new message.

But, hmm. Right now, all our state lives in the top-level component: RepLogApp. That's on purpose! RepLogApp is a smart, stateful component. And, because it holds all of the state, all the other components can be dumb, stateless components that render markup with little or no logic.

This is a good distinction. But, in the real world, there are some situations when a dumb, presentation component - like RepLogCreator - should hold some state. This is one of them!

Why? Well, RepLogApp's job is to be worried about the business logic of our app, independent of markup. So, it keeps track of things like the repLogs. But, a form validation error is not really business logic: it's state that just exists to support the form's user interface. Heck, as I keep mentioning, RepLogApp isn't even aware that our app has a form!

This was a hard distinction for me to fully understand. So, here's a different explanation, entirely stolen from our brave co-author Frank:

RepLogCreator is concerned about the creation process. It's like a bouncer at the club and the input field is the front door. The input only gets into the club if it meets certain criteria. By handling that logic in RepLogCreator, we allow the rest of our application to be unaware of this: it's taken care of for them. It also prevents RepLogApp - the manager of the club - from needing to know how RepLogCreator is doing its job.

Here's the point: I want your dumb components to, at first, not have state. But if you do need some state in order to power the user interface for that component, that's totally fine. And if you're totally confused, don't sweat it. If you do put your state in the wrong place, you'll either realize it eventually, or it'll just mean a bit more work for you. It's not the end of the world, nothing is permanent.

So, let's give RepLogCreator some state!

Leave a comment!

1
Login or Register to join the conversation
Nicholas B. Avatar
Nicholas B. Avatar Nicholas B. | posted 4 years ago

Like passing on responsibilities with......"I don't want to know how you will do it, just get it done, and do a clean job ... huh"

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