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 SubscribeHmm. Refresh the page and watch closely. See it? When it first loads, the table is empty just for a moment. Then, when our AJAX call finishes, it gets filled in. That makes perfect sense, but it does make the page feel momentarily "broken"... and it may not always load this quickly.
No worries! This is why the Internet invented loading messages and animations! How can we add these in React? I have no idea! Let's find out!
Here's our goal: before our AJAX call finishes, I want to render one row that just says "Loading". Hmm... this will need to happen inside RepLogList
: that's where the tbody
lives. But, hmm again: this component somehow needs to know whether or not the AJAX call has finished... and that is something that only RepLogApp
knows.
To keep track of whether or not the AJAX call is finished, we need new state. On top, add some new state isLoaded: false
.
... lines 1 - 7 | |
constructor(props) { | |
... lines 9 - 10 | |
this.state = { | |
... lines 12 - 14 | |
isLoaded: false | |
}; | |
... lines 17 - 21 | |
} | |
... lines 23 - 85 |
Then, down below, when fetch()
finishes, set isLoaded
to true
!
... lines 1 - 23 | |
componentDidMount() { | |
getRepLogs() | |
.then((data) => { | |
this.setState({ | |
... line 28 | |
isLoaded: true | |
}) | |
}); | |
} | |
... lines 33 - 85 |
State, done! And thanks to how we're rendering RepLogs
, this state is automatically passed as a prop. And now we start the prop-passing dance! In RepLogs
, add the new prop type at the bottom: PropTypes.bool.isRequired
. Oh, and you've probably noticed that I like to make pretty much everything required. That's a personal preference. Because this is my app, if I forget to pass a prop, it's probably a typo and I want to know.
... lines 1 - 84 | |
RepLogs.propTypes = { | |
... lines 86 - 93 | |
isLoaded: PropTypes.bool.isRequired, | |
}; |
Next, scroll up, destructure the isLoaded
variable, find RepLogList
, and pass that prop: isLoaded={isLoaded}
.
... lines 1 - 17 | |
export default function RepLogs(props) { | |
const { | |
... lines 20 - 27 | |
isLoaded | |
} = props; | |
... lines 30 - 35 | |
return ( | |
<div className="col-md-7"> | |
... lines 38 - 56 | |
<RepLogList | |
... lines 58 - 61 | |
isLoaded={isLoaded} | |
/> | |
... lines 64 - 80 | |
</div> | |
); | |
} | |
... lines 84 - 96 |
Finally, do the same in that component: I'll steal the prop type and go up to destructure the variable.
... lines 1 - 3 | |
export default function RepLogList(props) { | |
const { highlightedRowId, onRowClick, onDeleteRepLog, repLogs, isLoaded } = props; | |
... lines 6 - 42 | |
} | |
... line 44 | |
RepLogList.propTypes = { | |
... lines 46 - 49 | |
isLoaded: PropTypes.bool.isRequired, | |
}; |
Ok, this is interesting: if the app is not loaded yet, we don't need to run any of this code down here. So, we can short-circuit the entire process: if !isLoaded
, then return a completely new set of JSX, with a tbody
, tr
and <td colSpan="4" className="text-center">
. Say, "Loading...".
... lines 1 - 6 | |
if (!isLoaded) { | |
return ( | |
<tbody> | |
<tr> | |
<td colSpan="4" className="text-center">Loading...</td> | |
</tr> | |
</tbody> | |
); | |
} | |
... lines 16 - 52 |
Oh, and notice that this is colSpan
with a capital "S". This is another, uncommon, case where the prop is slightly different than the HTML attribute. PhpStorm made it easy by auto-completing the correct version for React.
And... yea! That's it! Let's go refresh... but watch closely. There it was! And because React's model is so flexible, if you ever needed to, for some reason, reload the data, you could re-render that loading message simply by updating one piece of state. Nice.
We're on a roll! So let's make the delete link talk to our API.
"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
}
}