A setTimeout method executes a function after a particular time. This can be useful for creating time-based effects, such as showing a loading spinner for a few seconds before loading new data or hiding a success message after a few seconds. However, using the traditional setTimeout in React can be challenging. To maintain the efficiency of a react application, we need to keep track of the timeouts and clear them up using the useEffect hook.
Let us understand how to deal with setTimeouts in react components:
Using setTimeouts in React Components:
We can use the setTimeout method in React components similar to how we deal with it in javascript. In React components, the setTimeout method follows the same principles as in Javascript. However, we should take certain caveats into account.
To set the timeouts, we need to use the useEffect hook in our react components. Setting a timeout directly within a component might be an inappropriate approach because React will regenerate the setTimeout method each time the component is re-rendered, resulting in a new timeout. Our application will quickly become bloated from these timeouts and become unreliable. Additional re-renders are required as a result of several state changes. As a result, we’ll make a timeout for React components using the useEffect hook. When the component re-renders, the useEffect hook executes side effects. It accepts an array of dependencies as well as a callback to handle the side effects. The dependencies are state variables that, when changed, cause these side effects. We will include the timeout inside the useEffect hook to manage them.
Let us understand the various ways to implement the setTimeout method in React.
Creating React Application:
Step 1: Make a project directory, head over to the terminal, and create a react app named spinner-gfg using the following command:
npx create-react-app spinnner-gfg
Step 2: After the spinner-gfg app is created, switch to the new folder spinner-gfg by typing the command below:
cd spinner-gfg
Project Structure: We will modify the folder and keep the files we need for this example. Now, make sure your file structure looks like this:
Example 1: We will use the setTimeout method to show a loading spinner for 5 seconds before rendering some data.
Approach: Creating the Loading spinner application:
- We will use the useEffect hook with an empty dependency array to create a timeout once the component mounts.
- The data state variable stores the content, and the setData function updates the value of the content.
- When the timer expires, the data is displayed, and the isLoading state is set to false.
index.html: Write the following code in your index.html file, located in the public folder of your project directory:
HTML
<!DOCTYPE html> < html lang = "en" > < head > < meta charset = "utf-8" /> < meta name = "viewport" content = "width=device-width, initial-scale=1" /> < meta name = "theme-color" content = "#000000" /> < meta name = "description" content = "Web site created using create-react-app" /> < title >Timeouts in React</ title > </ head > < body > < div id = "root" ></ div > </ body > </ html > |
App.js:
Javascript
import { useState, useEffect } from 'react' ; import './App.css' const App = () => { const [isLoading, setIsLoading] = useState( true ); const [data, setData] = useState( null ); useEffect(() => { // Creating a timeout within the useEffect hook setTimeout(() => { setData( "Welcome to gfg!" ); setIsLoading( false ); }, 5000); }, []); if (isLoading) { return <div className= 'spinner' > Loading.....</div>; } return ( <div className= 'container' > {data} </div> ); } export default App; |
App.css: Add the following code to App.css to style the spinner application.
CSS
.spinner, .container { margin : 1 rem; display : flex; flex- direction : column; align-items: center ; justify- content : center ; font-size : 2 rem; } .container { border : 2px solid darkGreen; } |
index.js: Add the following code in the index.js file.
Javascript
import React from 'react' ; import ReactDOM from 'react-dom/client' ; import './index.css' ; import App from './App' ; const root = ReactDOM.createRoot(document.getElementById( 'root' )); root.render( <React.StrictMode> <App /> </React.StrictMode> ); |
Step to run the application: Run the application by using the following command:
npm start
Output: By default, the React project will run on port 3000. You can access it at localhost:3000 on your browser.
Explanation: The useEffect hook invokes an empty array dependency which implies that it will only run once when the component mounts and ensures that the timer is set only once. The isLoading state variable controls the rendering of the component. If isLoading is true, the component will render a loading spinner. If isLoading is false, the component will display the data. We can display a loading spinner while the data is being loaded by using the setTimeout method within the useEffect hook.
Let us understand how we can create timeouts on re-renders.
Clearing setTimeouts:
In the useEffect hook, we can create timeouts on re-renders whenever the component’s state changes. However, this generates new timeouts each time the state changes and can cause performance issues. It is necessary to clear timeouts when they are no longer needed to prevent potential memory leaks. The setTimeout method creates a timer and queues a callback function to be executed after a specified time has passed. If the timer expires and the callback function is executed, the timeout is completed. However, if a new timeout is set using the same variable, the previous timeout is canceled and the new timeout is initiated. If a timeout is not canceled when it is no longer required, the callback function may still be executed, even if the component that set the timeout has been unmounted. This can result in wasted resources and potential bugs, particularly if the callback function has side effects that are no longer relevant.
To avoid this, we can clear the previous timeout before creating a new one using the clearTimeout method. This is important to prevent memory leaks in our application, as the callback for setTimeout may still execute if we don’t clear it when the component unmounts.
The clearTimeout Method:
- clearTimeout cancels the timeouts generated by the setTimeout method.
- The setTimeout method returns a timeoutId, which is passed to the clearTimeout.
- The timeoutId identifies a particular timer generated by the setTimeout function.
Syntax:
clearTimeout(timeoutID)
Here’s an example of how we can clear setTimeouts in React and create timeouts on re-renders:
Example 2: Displaying a warning message which disappears after a specified time.
Approach: Creating the Warning message component: We will display a warning for 5 seconds and then hide it by using the setTimeout method.
- We will use the useEffect hook to set a timer when the showWarning state is true and to cancel the timer when the component unmounts or the showWarning state changes.
- The useEffect hook invokes whenever the component renders, implying that a timer sets every time the component mounts and the showWarning state is true.
- To cancel the timer when the component unmounts or the showWarning state changes, we need a way to store the timer ID and access it from the useEffect hook’s cleanup function. This is where the useRef hook comes in.
- The useRef hook allows us to create a mutable ref that persists across re-renders and can be used to store the timer ID. When the component mounts, the timer ID is stored in the current property of the timerId ref.
- When the component unmounts or the showWarning state changes, the useEffect hook’s cleanup function is called, which cancels the timer using the timer ID stored in the timerId ref.
App.js:
Javascript
import { useState, useEffect, useRef } from 'react' ; import './App.css' const App = () => { const [showWarning, setShowWarning] = useState( false ); const timerId = useRef( null ); useEffect(() => { if (showWarning) { //Creating a timeout timerId.current = setTimeout(() => { setShowWarning( false ); }, 5000); } return () => { //Clearing a timeout clearTimeout(timerId.current); }; }, [showWarning]); function handleClick() { setShowWarning( true ); } return ( <div className= 'warn' > {showWarning && <div className= 'warningMsg' > This is a Warning ⚠️!</div>} <button className= 'btn' onClick={handleClick}> Show Warning</button> </div> ); } export default App; |
App.css: Add the following code to App.css to style the message application.
Javascript
.warn { margin: 1rem; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 1rem; } .warningMsg { border: 2px solid orangered; background-color: rgb(210, 153, 124); } .btn { border: 2px solid darkGreen; border-radius: 10px; margin: 1rem; cursor: pointer; } |
Step to run the application: Run the application by using the following command:
npm start
Output:
Explanation:
When the component mounts, the useEffect hook is called with an empty showWarning array, which means that the effect will only run once. The if (showWarning) condition is false, so the timer is not set. When the user clicks the Show Warning button, the handleClick function is called, which sets the showWarning state to true. This causes the component to re-render and the useEffect hook to be called again. This time, the if (showWarning) condition is true, so a timer is set using setTimeout to hide the warning message after 5 seconds.
If the user clicks the Show Warning button again before the 5 seconds have passed, the showWarning state will be set to true again, causing the component to re-render and the useEffect hook to be called again. This will cancel the previous timer and set a new timer to hide the warning message after 5 seconds.
If the user navigates away from the page before the 5 seconds have passed, the component will unmount and the useEffect hook’s cleanup function will be called. The cleanup function calls clearTimeout with the timer ID stored in the timerId ref, which cancels the timer and prevents the callback function from being executed.
This allows us to display a warning message for 5 seconds, then hide it automatically, without causing unnecessary side effects.