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

Login to bookmark this video
Buy Access to Course
38.

...Object Rest Spread

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

When a user submit an invalid form, we get a nice error message... but our cool "lifting to the database" message stays! Totally confusing! It looks like we're still trying to save the new rep log. Let's fix that!

In RepLogApp, the state that controls this is called isSavingNewRepLog. When the AJAX call is successful, we set this back to false. We need to also set this back to false inside the catch. And yes, fixing this is as easy as just copying this key and pasting it below. Sure, this would duplicate that key in two places... but, that's super-minor duplication: no big deal.

Except... I want to learn a super-fun new language feature. To show that, let's fix this in a slightly fancier way. Above the AJAX call, set const newState = an object with savingNewRepLog set to false.

148 lines | assets/js/RepLog/RepLogApp.js
// ... lines 1 - 45
handleAddRepLog(item, reps) {
// ... lines 47 - 55
const newState = {
isSavingNewRepLog: false
};
// ... lines 59 - 82
}
// ... lines 84 - 148

This represents the new state that we want to apply in all situations: success or failure. In other words, we want to merge this state into whatever is being set in success and also down in catch.

How can you merge objects in JavaScript? We've seen it before: Object.assign(). Check it out: return Object.assign(). For the first argument, copy the new state and paste. For the second argument, use newState.

148 lines | assets/js/RepLog/RepLogApp.js
// ... lines 1 - 58
createRepLog(newRep)
.then(repLog => {
this.setState(prevState => {
// ... lines 62 - 63
return Object.assign({
repLogs: newRepLogs,
newRepLogValidationErrorMessage: '',
}, newState);
});
// ... lines 69 - 70
})
// ... lines 72 - 148

Object.assign() will merge the data from newState into the first object and return it. Perfect!

Repeat this in catch: add Object.assign(), then newState.

148 lines | assets/js/RepLog/RepLogApp.js
// ... lines 1 - 71
.catch(error => {
error.response.json().then(errorsData => {
// ... lines 74 - 76
this.setState(Object.assign({
newRepLogValidationErrorMessage: firstError
}, newState));
})
})
// ... lines 82 - 148

Let's go make sure this works: refresh, select our bad data and... cool. It shows for just a second, then disappears.

Installing & Configuring babel-plugin-transform-object-rest-spread

Object.assign() is really great. We also used it earlier to merge two objects without modifying the original object. That was super important.

The only problem with Object.assign() is that it's... kinda confusing to look at, especially if you need to use it to avoid mutation.

Ok, idea time: what if we could do this: remove the Object.assign(), return a normal object, but then, add ...newState.

150 lines | assets/js/RepLog/RepLogApp.js
// ... lines 1 - 59
.then(repLog => {
this.setState(prevState => {
// ... lines 62 - 63
return {
...newState,
// ... lines 66 - 67
}
});
// ... lines 70 - 71
})
// ... lines 73 - 150

That would be cool, right? I mean, we already do this for arrays! But... Webpack explodes: the "spread" syntax does not work for objects.

Or does it?! Google for "babel plugin transform object rest spread" and find the Babel documentation page. The feature we're "dreaming" about is called "object rest spread". It is not an official ECMAScript feature. But, it's currently a proposed, "draft" feature that's in a late stage. There's no promises, but that means it will likely become a real feature in a future ECMAScript version.

And, because the JS world is a bit nuts, you don't need to wait! We can teach Babel how to understand this syntax. Copy the package name, find your terminal and run:

yarn add @babel/plugin-proposal-object-rest-spread --dev

Oh, and as I mentioned before, most of these Babel plugins will have a slightly new name in the future: @babel/plugin-transform-object-rest-spread. But, it's really the same library.

When you work with Babel, you typically configure it with a .babelrc file. But, Encore does this for us! Open webpack.config.js: the configureBabel() function allows us to extend its configuration. Add babelConfig.plugins.push() and paste the name.

Tip

If you downloaded the code (and on newer projects) make sure you installed the newer library - @babel/plugin-proposal-object-rest-spread - and use that name here instead.

43 lines | webpack.config.js
// ... lines 1 - 3
Encore
// ... lines 5 - 29
.configureBabel((babelConfig) => {
// ... lines 31 - 36
babelConfig.plugins.push('transform-object-rest-spread');
})
// ... lines 39 - 43

In the future, if you download the new @babel/plugin-transform-object-rest-spread library, the plugin name will be the full library name, starting with the @babel part. Just follow the docs.

Head back to the tab that's running Encore. Yep, it's super angry. Stop and re-run this command:

yarn run encore dev-server

And... it works! That's awesome! Babel now understands this syntax.

But... PhpStorm is still angry: ESLint parsing error. No worries: we just need to tell ESLint that this syntax is cool with us. Open .eslintrc.js. Under ecmaFeatures, add experimentalObjectRestSpread set to true.

Tip

On ESLint version 6 or higher, you only need to change the ecmaVersion to 2018. You do not need to add the experimentalObjectRestSpread option because it's already enabled.

21 lines | .eslintrc.js
module.exports = {
// ... line 2
parserOptions: {
// ... lines 4 - 5
ecmaFeatures: {
// ... line 7
experimentalObjectRestSpread: true
}
},
// ... lines 11 - 19
};

Deep breath: go back to RepLogApp. And... sweet! The error is gone!

Using the Object Rest Spread

Let's finish this! Down in catch, remove Object.assign(), remove the second argument and add ...newState.

150 lines | assets/js/RepLog/RepLogApp.js
// ... lines 1 - 72
.catch(error => {
error.response.json().then(errorsData => {
// ... lines 75 - 77
this.setState({
...newState,
// ... line 80
});
})
})
// ... lines 84 - 150

And one more time: scroll down to handleDeleteRepLog(). We don't need this weird code anymore! Just return a new object with ...repLog then isDeleting: true.

150 lines | assets/js/RepLog/RepLogApp.js
// ... lines 1 - 92
handleDeleteRepLog(id) {
this.setState((prevState) => {
return {
repLogs: prevState.repLogs.map(repLog => {
// ... lines 97 - 100
return {...repLog, isDeleting: true};
})
};
});
// ... lines 105 - 117
}
// ... lines 119 - 150

I love that. And even better, when we refresh, it's not broken! We rock!