Author avatar

Gaurav Singhal

How to fire periodic actions using setTimeout and dispatcher in redux

Gaurav Singhal

  • Nov 15, 2019
  • 7 Min read
  • 59 Views
  • Nov 15, 2019
  • 7 Min read
  • 59 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.

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

const initialState = {
  currentQuoteIndex: 0,
  quote: null
};

const reducer = (state = initialState, action) => {
  if (action.type === "CHANGE_QUOTE") {
    let currentQuoteIndex = state.currentQuoteIndex + 1;
    currentQuoteIndex =
      currentQuoteIndex === quotes.length ? 0 : currentQuoteIndex;
    return {
      currentQuoteIndex,
      quote: quotes[currentQuoteIndex]
    };
  }

  return {
    currentQuoteIndex: state.currentQuoteIndex,
    quote: quotes[state.currentQuoteIndex]
  };
};

export 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
2
3
4
5
6
7
8
9
10
11
12
// ...
import { connect } from "react-redux";

const QuoteChanger = props => {
  return <div>{props.quote}</div>;
};

const mapStateToProps = state => ({
  quote: state.quote
});

export 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:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { useEffect } from "react";
import { connect } from "react-redux";

const QuoteChanger = props => {
  useEffect(() => {
    const timer = setTimeout(
      () => props.dispatch({ type: "CHANGE_QUOTE" }),
      5000
    );
    return () => clearTimeout(timer);
  });
  return <div>{props.quote}</div>;
};

const mapStateToProps = state => ({
  quote: state.quote
});

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

1
2
3
4
5
6
7
8
9
10
11
12
13
import reducer from "./reducer";

import QuoteChanger from "./QuoteChanger";

const store = createStore(reducer);

function App() {
  return (
    <Provider store={store}>
      <QuoteChanger />
    </Provider>
  );
}
jsx

Complete Source Code

QuoteChanger.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { useEffect } from "react";
import { connect } from "react-redux";

const QuoteChanger = props => {
  useEffect(() => {
    const timer = setTimeout(
      () => props.dispatch({ type: "CHANGE_QUOTE" }),
      5000
    );
    return () => clearTimeout(timer);
  });
  return <div>{props.quote}</div>;
};

const mapStateToProps = state => ({
  quote: state.quote
});

export default connect(mapStateToProps)(QuoteChanger);
jsx

reducer.js

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

const initialState = {
  currentQuoteIndex: 0,
  quote: null
};

const reducer = (state = initialState, action) => {
  if (action.type === "CHANGE_QUOTE") {
    let currentQuoteIndex = state.currentQuoteIndex + 1;
    currentQuoteIndex =
      currentQuoteIndex === quotes.length ? 0 : currentQuoteIndex;
    return {
      currentQuoteIndex,
      quote: quotes[currentQuoteIndex]
    };
  }

  return {
    currentQuoteIndex: state.currentQuoteIndex,
    quote: quotes[state.currentQuoteIndex]
  };
};

export default reducer;
js

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider, connect } from "react-redux";

import "./styles.css";

import reducer from "./reducer";

import QuoteChanger from "./QuoteChanger";

const store = createStore(reducer);

function App() {
  return (
    <Provider store={store}>
      <QuoteChanger />
    </Provider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
jsx

styles.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
body {
  background: #000;
}

#root {
  font-family: sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 90vh;
  padding: 25px;
}

div {
  font-size: 48px;
  text-align: center;
  color: #fff;
}
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.

0