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 SubscribeCommunication always flows down in React: data lives in one component and is passed down to its children as props. Actually, both data and callbacks are passed from parent to child: child components use callbacks to communicate back up to the parent when something happens. For example, RepLogs
passes an onRowClick
to RepLogList
. It uses that to tell its parent when that interaction occurs.
So, parents pass data to their children. But, parents do not, ask children for information. Well, it's technically possible, but it's not the normal flow.
For example, RepLogApp
passes the highlightedRowId
to RepLogs
. But, RepLogApp
does not ever ask RepLogs
to give it any data that lives inside RepLogs
. Information only flows down.
For that reason, in general, we need the state
of our application to live as high up the component hierarchy as possible. Why? Because we can pass a piece of state down to all of the components that need to use it. When that state changes, that change naturally flows down and everything updates beautifully.
But, imagine if a piece of state lived in a child component, but we wanted to use it in the render()
method of a parent. Well, that just won't work! The parent can't ask the child for that data: information does not flow up.
This is the reason why we will put all of our state in the top level component: RepLogApp
. Again, this is not an absolute rule, but it's a great rule to follow for now. We'll talk later about when it's ok to move state lower, into a child component.
Anyways, the most important piece of data in our app is the rep logs themselves. And, these will need to change dynamically as the user adds new rep logs and deletes old ones. That means, rep logs need to be stored as state.
To get the static version of our app up and running, we just hardcoded these inside RepLogList
. Time to move this to state! Copy the dummy rep log data and go to RepLogApp
. Whenever we have new state, we need to initialize it in the constructor. Add a new repLogs
key to this array and paste!
... lines 1 - 5 | |
constructor(props) { | |
... lines 7 - 8 | |
this.state = { | |
highlightedRowId: null, | |
repLogs: [ | |
{ id: 1, reps: 25, itemLabel: 'My Laptop', totalWeightLifted: 112.5 }, | |
{ id: 2, reps: 10, itemLabel: 'Big Fat Cat', totalWeightLifted: 180 }, | |
{ id: 8, reps: 4, itemLabel: 'Big Fat Cat', totalWeightLifted: 72 } | |
] | |
}; | |
... lines 17 - 18 | |
} | |
... lines 20 - 42 |
Yea, eventually the repLogs
state will start empty, and we'll then populate it by making an AJAX call to the server for the existing rep logs. But, until then, the dummy data makes building things easier.
The new state lives in the top-level component. But... we need to use it down in RepLogList
. No problem! We just need to pass this down our tree. Fetch the repLogs
out of state, then pass this as a prop to RepLogs
.
... lines 1 - 24 | |
render() { | |
... lines 26 - 28 | |
return ( | |
<RepLogs | |
... lines 31 - 33 | |
repLogs={repLogs} | |
/> | |
) | |
} | |
... lines 38 - 42 |
In RepLogs
, before using the new prop, head down to the bottom: we want to define all props in propTypes
. Add repLogs
set to PropTypes.array.isRequired
.
... lines 1 - 79 | |
RepLogs.propTypes = { | |
... lines 81 - 83 | |
repLogs: PropTypes.array.isRequired | |
}; |
Copy that, because, RepLogList
will receive the same prop.
... lines 1 - 24 | |
RepLogList.propTypes = { | |
... lines 26 - 27 | |
repLogs: PropTypes.array.isRequired, | |
}; |
Ok! We are passing the repLogs
prop to the RepLogs
component. At the top of render()
, read repLogs
out of props. And then, do the prop-passing dance: send this straight into RepLogList
.
... lines 1 - 4 | |
export default function RepLogs(props) { | |
const { withHeart, highlightedRowId, onRowClick, repLogs } = props; | |
... lines 7 - 12 | |
return ( | |
... lines 14 - 25 | |
<RepLogList | |
... lines 27 - 28 | |
repLogs={repLogs} | |
/> | |
... lines 31 - 76 | |
); | |
} | |
... lines 79 - 86 |
Finally, in that component, get repLogs
out of props and... delete the hardcoded stuff.
... lines 1 - 3 | |
export default function RepLogList(props) { | |
const { highlightedRowId, onRowClick, repLogs } = props; | |
... lines 6 - 22 | |
} | |
... lines 24 - 30 |
This is sweet! Move back to your browser and refresh! Hey hey! It's not broken! Check out the React dev tools, and look at the top RepLogApp
component. Yep! You can see the repLogs
state. Now... mess with it! Change the reps from 25 to 50.... boom! The UI on the child component updates instantly!
But, look back at RepLogApp
, it has two pieces of state & one prop. And... it's passing all of that into its child as props. With a trick, we can be lazier, and do this automatically.
"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
}
}