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

Smart Components & Spread Attributes

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

RepLogApp is a "smart", or "container" component: it holds state & logic... but no markup. Instead, smart components wrap themselves around a dumb component - like RepLogs - and that component renders all the elements.

Thanks to this pattern... a funny thing happens in smart components: you pretty much always want to pass all of your state and props into your child so that it can actually use them to render!

But, this can become tedious: each new piece of state needs to be initialized, destructured into a variable, then passed as a prop... with the same name. Lame! Let's use a shortcut.

The Attribute Spread Operator

Delete all of the variables we destructured from state and props. Then, delete all of the props we're passing to RepLogs, except for the callback. Instead, here's the awesome part, use the spread operator: {...this.props} and {...this.state}.

... lines 1 - 24
render() {
return (
<RepLogs
{...this.props}
{...this.state}
onRowClick={this.handleRowClick}
/>
)
}
... lines 34 - 38

That's it! Every prop and state is now being passed to the child as a prop. The only things we need to pass by hand are any callbacks we need.

Move over and refresh. No problems. Our smart component is getting simpler and simpler: it's all logic.

Calculating the Total Weight Lifted

While we're here, it's time to finish one of our TODO's: fill in the total weight lifted column. This is great! I usually end up forgetting about my TODO's until I accidentally find them years later.

The value in this column will need to change whenever the repLogs state changes. But... the total weight should not be stored in state! Why? Simple! We can already calculate it by adding up all of the total weight values for the rep logs. No need to introduce more state: that would be duplication.

Go to RepLogs: there's the TODO! And here's the plan: loop over the repLogs and add up the weights. So, we need just a little bit of logic. If this component were a class, I'd probably add a new method to the class and put that logic there. But, darn! It's just a function! No problem: just go above the function and add... another function! Call it calculateTotalWeightLifted() with a repLogs argument: we will need to pass that in.

To do the calculation, I'll paste in some boooooring code: it loops over the repLogs, adds up the totalWeightLifted for each and... returns.

... lines 1 - 4
function calculateTotalWeightLifted(repLogs) {
let total = 0;
for (let repLog of repLogs) {
total += repLog.totalWeightLifted;
}
return total;
}
... lines 14 - 96

Copy the function name, move down to the TODO, and call it! {calculateTotalWeightLifted()} passing it repLogs. The repLogs live inside props, but we already destructured that into a variable.

... lines 1 - 26
<table className="table table-striped">
... lines 28 - 41
<tr>
... lines 43 - 44
<th>{calculateTotalWeightLifted(repLogs)}</th>
... line 46
</tr>
... line 48
</table>
... lines 50 - 96

Moment of truth: refresh! Boom! We have a total! Let's mess with the state, like change this to 200. Yes! It updates! The state change on RepLogApp causes both RepLogApp and RepLogList to re-render. When that happens, our code uses the new weights to calculate the new total. It's all very awesome.

Super-Fancy (Confusing?) ES6 Syntax

This works fine. But, I'm going to write a new function that does this same calculation... but with some crazy, fancy syntax. Check this out:

const calculateTotalWeightFancier = then repLogs => repLogs.reduce(). Pass this another arrow function with two arguments: total and log. That callback will return total + log.totalWeightLifted. Start the reduce function with a 0 value.

... lines 1 - 13
const calculateTotalWeightFancier = repLogs => repLogs.reduce((total, log) => total + log.totalWeightLifted, 0);
... lines 15 - 97

Phew! Before we understand this madness, copy the new function name, move down, and paste! Find your browser - ah, the page is already reloading... and... it works!

... lines 1 - 42
<tr>
... lines 44 - 45
<th>{calculateTotalWeightFancier(repLogs)}</th>
... line 47
</tr>
... lines 49 - 97

This fancier function doesn't contain anything new. But wow, even for me, this is hard to understand. So, why are we doing this? Because, in the React world, you will see syntax like this. And I want you to be at least comfortable reading it.

Let's walk through it.

This creates a variable that is set to a function that accepts one argument: repLogs. Because the function doesn't have curly braces, it means the function returns the result of repLogs.reduce(). The reduce() function - which you may not be familiar with - itself accepts a callback function with two arguments. Once again, because that function doesn't have curly braces, it means that it returns total + log.totalWeightLifted.

If this makes your head spin, me too! Honestly, to me, this looks much more complex than the original. But, when you see things like this in blog posts or documentation, just break it down piece by piece: it's just boring code, dressed up in a different style. And if you like this syntax, cool! Go nuts.

Leave a comment!

2
Login or Register to join the conversation
GDIBass Avatar
GDIBass Avatar GDIBass | posted 5 years ago | edited

Could make this one a one liner!


function calculateTotalWeightLifted(repLogs) {
    return repLogs.reduce((total, repLog) => total + repLog.totalWeightLifted, 0);
}

Edit: Nvm j ust got to the end of the video lol

Reply
Craig R. Avatar

Hey Matt,

Haha, good thinking ahead ;)

Cheers!

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