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

Displaying Server Validation Errors

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

When server-side validation fails, the API returns a 400 status code with the details of the error in the response. And thanks to the change we just made, fetch() now throws an error, which we can handle!

Open RepLogApp: inside of handleAddRepLog, if the call to createRepLog fails, we need to grab the validation error messages and put them on the screen. And now that our Promise can fail, we can do this with a .catch(). Pass this an arrow function. For now, just console.log(error.response).

... lines 1 - 44
handleAddRepLog(item, reps) {
... lines 46 - 54
createRepLog(newRep)
.then(repLog => {
... lines 57 - 66
})
.catch(error => {
console.log(error.response);
})
... line 71
}
... lines 73 - 137

Let's see what that looks like: refresh, try dark matter again and... cool! We have the Response object!

Let's decode this just like we did before with success: error.response.json(). This returns another Promise, so add .then with an errorsData argument for the next arrow function. Log that... then let's go see what it looks like: dark matter, 10 times... perfect!

... lines 1 - 67
.catch(error => {
error.response.json().then(errorsData => {
console.log(errorsData);
})
})
... lines 73 - 139

It has an errors key, and a list of errors below where the key tells us which field this is for. So, how can we print this onto the screen? Well... it depends on how fancy you want to get. You could use the key of each error to find the field it belongs to, and render the error in that place. Or, you could print all of the errors on top of the form. Or, you could be even lazier like me and just print the first error above the form.

Adding the Error State

To do that, we need new state inside of RepLogApp to keep track of the current "rep log validation" error message. Add one called newRepLogValidationErrorMessage. set to empty quotes.

... lines 1 - 7
constructor(props) {
... lines 9 - 10
this.state = {
... lines 12 - 17
newRepLogValidationErrorMessage: ''
};
... lines 20 - 25
}
... lines 27 - 146

But wait, this is interesting. When we added client-side validation, we stored it in RepLogCreator and it handled all of that logic. But because RepLogApp is the only component that's aware of the server, this is state that it needs to handle. And, it's not really form validation logic: remember RepLogApp doesn't even know our app uses a form. Nope, it's really a business logic validation failure: something, somehow tried to save a rep log with invalid data.

Copy that name and go down to handleAddRepLog(). First, if we're successful, in case there was already a validation message on the screen, we need to set it back to empty quotes to remove it.

... lines 1 - 55
createRepLog(newRep)
.then(repLog => {
this.setState(prevState => {
... lines 59 - 60
return {
... lines 62 - 63
newRepLogValidationErrorMessage: '',
};
});
... lines 67 - 68
})
... lines 70 - 146

Down in catch(), add const errors = errorsData.errors. Then, to get just the first error... um... it's actually a bit tricky. Silly JavaScript! Use const firstError = errors[Object.keys(errors)[0]].

Wow! We need to do this because errors isn't an array, it's an object with keys. Use this in the setState() call: newRepLogValidationErrorMessage set to firstError.

... lines 1 - 69
.catch(error => {
error.response.json().then(errorsData => {
const errors = errorsData.errors;
const firstError = errors[Object.keys(errors)[0]];
this.setState({
newRepLogValidationErrorMessage: firstError
});
})
})
... lines 80 - 146

Passing & Using the Error State

Done! As you know, the new state is instantly passed down to the child as a prop. But we need to use this state in RepLogCreator: I want to put the message right above the form.

Ok! Time to pass some props around! Step 1: define the new prop type as a required string. Step 2: destructure this to a new variable. And step 3, pass to RepLogCreator. But wait! Let's change the name validationErrorMessage= then the variable.

... lines 1 - 17
export default function RepLogs(props) {
const {
... lines 20 - 30
newRepLogValidationErrorMessage
} = props;
... lines 33 - 38
return (
... lines 40 - 87
<RepLogCreator
... line 89
validationErrorMessage={newRepLogValidationErrorMessage}
/>
... lines 92 - 94
);
}
... line 97
RepLogs.propTypes = {
... lines 99 - 109
newRepLogValidationErrorMessage: PropTypes.string.isRequired,
};

Why the name change? Well... if you think about it, even though we called this component RepLogCreator, there's nothing about the component that's specific to creating rep logs. We could easily reuse it later for editing existing rep logs... which is awesome!

All RepLogCreator cares about is that we're passing it the validation error message: it doesn't care that it's the result of creating a new rep log versus editing an existing one.

Anyways, let's go use this: add the prop type: validationErrorMessage as a string that's required. Then, destructure it. Oh, we don't have any props destructuring yet. No problem - use const {} = this.props and then add validationErrorMessage. I typed that a bit backwards so PhpStorm would auto-complete the variable name.

... lines 1 - 3
export default class RepLogCreator extends Component {
... lines 5 - 53
render() {
... line 55
const { validationErrorMessage } = this.props;
... lines 57 - 100
}
}
... line 103
RepLogCreator.propTypes = {
... line 105
validationErrorMessage: PropTypes.string.isRequired,
};

Finally, just inside the form, use our trick: validationErrorMessage &&, some parenthesis and a div with className="alert alert-danger" and the message inside.

... lines 1 - 53
render() {
... lines 55 - 57
return (
<form onSubmit={this.handleFormSubmit}>
{validationErrorMessage && (
<div className="alert alert-danger">
{validationErrorMessage}
</div>
)}
... lines 65 - 98
</form>
);
}
... lines 102 - 108

We're only printing the first message. But if you wanted to print a list instead, it's no big deal: just use the map() function like normal to render all of them.

Let's see if it works! Move over and make sure everything is refreshed. Select dark matter, 10, and... yes! We got it! Hmm, except, because we're not printing the error next to the field... it's not super obvious what I need to fix! If you're going to be lazy like us, you need to make sure the errors are descriptive.

Go into src/Form/Type/RepLogType.php. Most validation comes from our RepLog entity. But the form fields themselves can add some "sanity" validation. To customize the message, add an option: invalid_message set to "Please lift something that is understood by our scientists."

... lines 1 - 13
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
... line 17
->add('item', ChoiceType::class, array(
... lines 19 - 20
'invalid_message' => 'Please lift something that is understood by our scientists.'
))
... line 23
}
... lines 25 - 32

Much easier to understand! Try that: refresh, choose dark matter and... got it!

Except... hmm: when you get a validation error, the "Lifting to the database" loading message is still there! It... shouldn't be. Let's fix that and learn about a super-useful language feature called "object rest spread".

Leave a comment!

6
Login or Register to join the conversation

"Please lift something that is understood by our scientists."
🤣

1 Reply
Graymath technology Avatar
Graymath technology Avatar Graymath technology | posted 4 years ago

If I consoe.log(error). It just prints the statusText but doesn't show any response related information even though we are setting error.response. Just wondering if you have any idea why it doesn't get added as a property.

Reply
Graymath technology Avatar
Graymath technology Avatar Graymath technology | Graymath technology | posted 4 years ago

I tried the console.log(Object.keys(error)) and I can see the response object there though

1 Reply

Hey Graymath technology

That's interesting. If error is an object, then console.log() should print its keys and values. On which browser are you running?

Cheers!

Reply
Graymath technology Avatar
Graymath technology Avatar Graymath technology | MolloKhan | posted 4 years ago

I checked on firefox and chromium both in ubuntu 16.04

Reply

Hmm, it doesn't happen to me but I'm on Windows (yes, yes, buuh to me :p). If you console.log any other object do you see the same behavior?

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