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

Temporary Messages & componentWillUnmount

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

That success message is cool... but it should probably disappear after a few seconds. No problem! We can use the native setTimeout() function to change the state back to empty after a few seconds.

Go back to RepLogApp. Let's refactor things first: create a new method called setSuccessMessage with a message argument. Inside, set the state.

... lines 1 - 83
setSuccessMessage(message) {
this.setState({
successMessage: message
});
... lines 88 - 93
}
... lines 95 - 112

We're making this change so that we can re-use our cool success message feature in the future. Above, instead of setting the successMessage in the object, use this.setSuccessMessage() and paste the text there.

... lines 1 - 39
handleAddRepLog(item, reps) {
... lines 41 - 49
createRepLog(newRep)
.then(repLog => {
... lines 52 - 60
this.setSuccessMessage('Rep Log Saved!');
})
;
}
... lines 65 - 112

Watch out for Multiple Re-Renders

But! There is a downside to what we just did! Every time you change the state, React re-renders our component. Thanks to this change, it's going to re-render once for this state and then again right after. That is probably not something you need to worry about. But, as your applications grow bigger and bigger and bigger, you should be aware when you're triggering your app to re-render. This is especially important because, when a component like RepLogApp re-renders, all of its children are also re-rendered, even if the props being passed to them don't change. And yes, there are ways to optimize this. But, for now, just be aware that re-rendering requires CPU. If you re-render a big app too often, it could slow down. But, there are ways to optimize.

Clearing the Message with setTimeout()

Back in setSuccessMessage(), to clear the message, use setTimeout(), pass it an arrow function, and use this.setState() to reset successMessage back to empty quotes. Let's do that after 3 seconds.

... lines 1 - 84
this.setState({
... lines 86 - 88
setTimeout(() => {
this.setState({
successMessage: ''
});
}, 3000)
}
... lines 95 - 112

Ok! Let's give this a try! Refresh and lift my big fat cat 5 times. Success! And... gone!

Clearing the Timeout

So easy! Except... yes... it was too easy. There's an edge case: if setSuccessMessage() is called once, then called again 2 seconds later, the second message will disappear after only 1 second! It's a minor detail, but we can code this better.

Basically, before we call setTimeout, we want to make sure to clear any previous timeout that may be waiting to fire. The setTimeout() function returns an integer, which we can use to clear it. To keep track of that value, in the constructor, initialize a new property: this.successMessageTimeoutHandle = 0.

... lines 1 - 7
constructor(props) {
... lines 9 - 18
this.successMessageTimeoutHandle = 0;
... lines 20 - 24
}
... lines 26 - 119

This has nothing to do with React: we're just taking advantage of our object to store some data. Oh, and the value 0 is just a "null" value in disguise: if we pass this to clearTimeout(), nothing will happen.

Back down in setSuccessMessage, before setTimeout, add clearTimeout(this.successMessageTimeoutHandle). To set that property, add this.successMessageTimeoutHandle = before setTimeout().

... lines 1 - 88
setSuccessMessage(message) {
... lines 90 - 93
clearTimeout(this.successMessageTimeoutHandle);
this.successMessageTimeoutHandle = setTimeout(() => {
... lines 96 - 100
}
... lines 102 - 119

And finally, to be completely on top of things, inside the callback, after we reset the state, set the timeout handle back to 0.

... lines 1 - 94
this.successMessageTimeoutHandle = setTimeout(() => {
... lines 96 - 98
this.successMessageTimeoutHandle = 0;
}, 3000)
... lines 101 - 119

Cleaning up Your Component: componentWillUnmount()

And... we're done! Our message will always disappear a full 3 seconds after the last success message has been set. Except... yea... there is still one teenie, tiny problem... and this time, it's special to React.

Right now, RepLogApp will always be rendered on the page. But, that's not true of React components in general. For example, we could choose to only render the RepLogCreator component after clicking a button. Or, if you're using React Router so that users can navigate to different "pages", then even RepLogApp would be rendered and unrendered as the user navigates.

Because of this, if your component is removed from the page, you need to ask yourself:

Is there anything I need to clean up?

The answer is usually... no! But, setTimeout() would cause a problem. Why? Basically, if setState() is called on a component that is not rendered to the page, React kinda freaks out. Thanks to setTimeout(), that could happen if the component was removed right after setting a success message.

It's not a big deal, but let's clean this up. Scroll up to componentDidMount() and add a new method: componentWillUnmount().

... lines 1 - 36
componentWillUnmount() {
... line 38
}
... lines 40 - 119

This is another one of those magic lifecycle functions: componentDidMount is called right after your component is rendered to the page. componentWillUnmount is called right before it's removed. It's your chance to clean stuff up.

Let's do that: clearTimeout(this.successMessageTimeoutHandle).

... lines 1 - 36
componentWillUnmount() {
clearTimeout(this.successMessageTimeoutHandle);
}
... lines 40 - 119

Honestly, this isn't that common. But, keep it in mind. Another example could be if you used an external library to add some cool feature directly to a DOM element. If you want to clean that up, this is the place to do it.

Leave a comment!

0
Login or Register to join the conversation
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