Author avatar

Gaurav Singhal

How to fire periodic actions using setTimeout and dispatcher in redux

Gaurav Singhal

  • Nov 15, 2019
  • 7 Min read
  • 21,473 Views
  • Nov 15, 2019
  • 7 Min read
  • 21,473 Views
Web Development
React

Introduction

In this guide, we’ll learn how to dispatch an action regularly using the setTimeout() function to create a "Quote Changer" app—a simple web app that displays a quote that changes every five seconds.

Writing the Reducer

In the reducer.js file, we will declare a quotes variable, which will contain an array of quotes. The idea is to loop through the quotes array and store the current quote in the global store object.

In the store, along with the current quote, the existing quote index is also included so that the next quote to be displayed can be determined.

1const quotes = [
2  "It’s not who I am underneath, but what I do that defines me.",
3  "Everything’s impossible until somebody does it.",
4  "Why do we fall? So that we can learn to pick ourselves back up.",
5  "Our greatest glory is not in ever falling, but in rising every time we fall.",
6  "If you kill a killer, the number of killers in the room remains the same.",
7  "Sometimes the truth isn’t good enough, sometimes people need more.",
8  "A hero can be anyone, even a man doing something as simple as reassuring putting a coat on a young boy’s shoulders."
9];
10
11const initialState = {
12  currentQuoteIndex: 0,
13  quote: null
14};
15
16const reducer = (state = initialState, action) => {
17  if (action.type === "CHANGE_QUOTE") {
18    let currentQuoteIndex = state.currentQuoteIndex + 1;
19    currentQuoteIndex =
20      currentQuoteIndex === quotes.length ? 0 : currentQuoteIndex;
21    return {
22      currentQuoteIndex,
23      quote: quotes[currentQuoteIndex]
24    };
25  }
26
27  return {
28    currentQuoteIndex: state.currentQuoteIndex,
29    quote: quotes[state.currentQuoteIndex]
30  };
31};
32
33export default reducer;
js

Since there is going to be only one action, I'm not using the switch block.

The QuoteChanger Component

Let's begin with connecting the <QuoteChanger /> component to the store using the connect method.

1// ...
2import { connect } from "react-redux";
3
4const QuoteChanger = props => {
5  return <div>{props.quote}</div>;
6};
7
8const mapStateToProps = state => ({
9  quote: state.quote
10});
11
12export default connect(mapStateToProps)(QuoteChanger);
jsx

Now we’ll to dispatch the CHANGE_QUOTE action from this component every 5 seconds (5,000 milliseconds). To enable this timer, we need to use setTimeout in our component. setTimeout is a Browser API function and runs asynchronously, hence it is considered a side effect.

In React, side effects are handled in the componentDidMount lifecycle hook. So, we probably need to change our functional <QuoteChanger /> component into a class component. We could do that, but instead, I’ll show you a modern way of handling side effects in React using the useEffect() hook.

useEffect() Hook

The useEffect() hook lets us perform side effects in a function component. It is equivalent to componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods wrapped in a single API.

The useEffect() hook accepts a callback function as a parameter which must return a function.

So to put the above into the context of our example, our useEffect() hook will look as follows:

1useEffect(() => {
2  const timer = setTimeout(
3    () => props.dispatch({ type: "CHANGE_QUOTE" }),
4    5000
5  );
6  return () => clearTimeout(timer);
7});
jsx

The setTimeout() function returns a timer object which must be cleared using the clearTimeout() function to avoid any adverse results in the application.

1import React, { useEffect } from "react";
2import { connect } from "react-redux";
3
4const QuoteChanger = props => {
5  useEffect(() => {
6    const timer = setTimeout(
7      () => props.dispatch({ type: "CHANGE_QUOTE" }),
8      5000
9    );
10    return () => clearTimeout(timer);
11  });
12  return <div>{props.quote}</div>;
13};
14
15const mapStateToProps = state => ({
16  quote: state.quote
17});
18
19export default connect(mapStateToProps)(QuoteChanger);
jsx

The Root Component

To finish up, we will wrap the root component with the <Provider /> component and pass our store object.

1import reducer from "./reducer";
2
3import QuoteChanger from "./QuoteChanger";
4
5const store = createStore(reducer);
6
7function App() {
8  return (
9    <Provider store={store}>
10      <QuoteChanger />
11    </Provider>
12  );
13}
jsx

Complete Source Code

QuoteChanger.js

1import React, { useEffect } from "react";
2import { connect } from "react-redux";
3
4const QuoteChanger = props => {
5  useEffect(() => {
6    const timer = setTimeout(
7      () => props.dispatch({ type: "CHANGE_QUOTE" }),
8      5000
9    );
10    return () => clearTimeout(timer);
11  });
12  return <div>{props.quote}</div>;
13};
14
15const mapStateToProps = state => ({
16  quote: state.quote
17});
18
19export default connect(mapStateToProps)(QuoteChanger);
jsx

reducer.js

1const quotes = [
2  "It’s not who I am underneath, but what I do that defines me.",
3  "Everything’s impossible until somebody does it.",
4  "Why do we fall? So that we can learn to pick ourselves back up.",
5  "Our greatest glory is not in ever falling, but in rising every time we fall.",
6  "If you kill a killer, the number of killers in the room remains the same.",
7  "Sometimes the truth isn’t good enough, sometimes people need more.",
8  "A hero can be anyone, even a man doing something as simple as reassuring putting a coat on a young boy’s shoulders."
9];
10
11const initialState = {
12  currentQuoteIndex: 0,
13  quote: null
14};
15
16const reducer = (state = initialState, action) => {
17  if (action.type === "CHANGE_QUOTE") {
18    let currentQuoteIndex = state.currentQuoteIndex + 1;
19    currentQuoteIndex =
20      currentQuoteIndex === quotes.length ? 0 : currentQuoteIndex;
21    return {
22      currentQuoteIndex,
23      quote: quotes[currentQuoteIndex]
24    };
25  }
26
27  return {
28    currentQuoteIndex: state.currentQuoteIndex,
29    quote: quotes[state.currentQuoteIndex]
30  };
31};
32
33export default reducer;
js

index.js

1import React from "react";
2import ReactDOM from "react-dom";
3import { createStore } from "redux";
4import { Provider, connect } from "react-redux";
5
6import "./styles.css";
7
8import reducer from "./reducer";
9
10import QuoteChanger from "./QuoteChanger";
11
12const store = createStore(reducer);
13
14function App() {
15  return (
16    <Provider store={store}>
17      <QuoteChanger />
18    </Provider>
19  );
20}
21
22const rootElement = document.getElementById("root");
23ReactDOM.render(<App />, rootElement);
jsx

styles.css

1body {
2  background: #000;
3}
4
5#root {
6  font-family: sans-serif;
7  display: flex;
8  align-items: center;
9  justify-content: center;
10  min-height: 90vh;
11  padding: 25px;
12}
13
14div {
15  font-size: 48px;
16  text-align: center;
17  color: #fff;
18}
css

Summary

It’s essential to keep your code clean and refactor the code when necessary, as we did in this example by using modern React hooks. Be careful when dealing with side effects as they can create bugs if not handled properly.