How To Build a Counter Application: using useReducer and Custom Hooks in ReactJS.

How To Build a Counter Application: using useReducer and Custom Hooks in ReactJS.

Introduction

A Counter application is a utility tool app used to keep track of multiple values. In this project, I developed a counter application with the following features: Increment button, Decrement button, Reset button and finally the setValue function.

I also implemented a combination of states with a useReducer hook that implements a counter with the four evident features in the custom hook - increment, decrement, reset, and setValue.

In addition, I created a 404 (NotFound) page, a page to test Error Boundary, with proper SEO optimization, while also making use of React Routers to Link different pages.

In this article, I'll introduce you to an amazing technique (based on my experience), which would help you build a counter application using useReducer and Custom Hooks in ReactJS.

Prerequisites

Before you begin this tutorial, you'll need the following:

  • Web Browser

  • Nodejs installed

  • Reactjs installed

  • A text editor of your choice installed

  • React router and React error boundary installed

  • Basic understanding of JavaScript

  • Basic knowledge of ReactJs, custom and useReducer hooks in ReactJS.

  • Basic knowledge of React routers and error boundaries in react.

If you are new to React or you need a refresher, I would recommend you to check out the official ReactJsdocumentation.

Quick Recap of Custom and useReducer Hooks

A react custom hook allows you to create reusable stateful logic, that return stored values. They are reusable, reliable, time saving, makes code clean and finally, it improves the rendering speed.

A React custom Hook starts with "use" and suffixed with any word of your choice.

A react useReducer hook is used to store and update states, and also helps you to manage complex states in React applications. It can therefore serve as an alternative for the useState Hook.

This hook typically accepts two arguments: a reducer function and an initial state. useReducer returns an array that holds the current state value and a dispatch function to which you can pass an action and later call on it. Now, Let's Get Started!

Getting Started

Step 1: Setting up the Project

npx create-react-app react-counter-app

This will create a new react app for you and you can start building your project. All you have to do is open the app in your code editor.

After which you will run npm start to start the development server, which will open a new tab in your browser, pointing to localhost:3000. Then you can see all the changes you make to your app there.

Step 2: Creating the layout of the app

The next step is to create a simple UI that will display the counter app, and of course, style it.

    <div>
      <h1 className="header">Counter App with useReducer</h1>
      <div className="count">Count = {state.count}</div>
      <div>
        <button>Increment</button>
        <button>Reset</button>
        <button>Decrement</button>
        <button>SetValue</button>
      </div>

        <div>
        <h3>Click on the button below to see a Custom Counter App</h3>
        <button><Link>Click Me!</Link></button>
        </div>

        <div>
        <h3>Click here to go to Error Boundary Test Page</h3>
        <button><Link >Click Me!</Link></button>
        </div>
      </div>

So, I'm going to create two counter apps, one with useReducer and the other one with custom hook (with the same UI or as you wish).

Step 3: Creating the Counter App with useReducer hook

I created the first counter and named it CounterOne.js in the components folder.

Inside this file, we will add the logic to create the counter feature.

Firstly, we import the hook we are working with i.e. useReducer,

import React, { useReducer } from "react";

the {Link} from react-router-dom, to be able to link to other pages,

import { Link } from 'react-router-dom';

and of course, the CSS file.

import "../App.css"

The approach of this hook is to have a reducer function that accepts the state of the app, and an action as parameters, and, based on that action, the state is being managed.

So, we proceed to create the reducer function:

const initialState = { count: 0 };
// The reducer function
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return { count: (state.count = 0) };
      case "setValue":
        return { count: action.payload };  
    default:
      return { count: state.count };
      // throw new Error();
  }
};

We initialize the state with 0, then we create a reducer function that accepts the current state of our count as an argument and an action. The state is updated by the reducer based on the action type, which in this app are (increment, decrement, reset and setValue). If the action type passed in is not contained in the reducer function, an error will be thrown.

Now, we proceed to call the dispatch function:

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div className="counter">
        <h1 className="header">Counter App with useReducer</h1>
      <div className="count">Count = {state.count}</div>
      <br />
      <br />
      <div>
        <button className="buttons" onClick={() => dispatch({ type: "increment" })}>
          Increment
        </button>
        <button className="buttons" onClick={() => dispatch({ type: "reset" })}>Reset</button>
        <button className="buttons" onClick={() => dispatch({ type: "decrement" })}>
          Decrement
        </button>
        <button className="buttons" onClick={(num) => dispatch({ type: "setValue", payload: 5 })}>SetValue</button>

        </div>
        <div className="mini-div">
        <h3>Click on the button below to see a Custom Counter App</h3>
        <button className='goback_button'><Link to="/countertwo" className="link">Click Me!</Link></button>
        </div>

        <div className="mini-div2">
        <h3>Click here to go to Error Boundary Test Page</h3>
        <button className='goback_button'><Link to="/testerrorboundary" className="link">Click Me!</Link></button>
        </div>
      </div>
  );
};

export default Counter;

Note that onClick events have been created for each button, which executes the given function once the user clicks. For example, when clicking the Reset button, reset() action type will be executed and the count will return to its default value.

Step 4: Creating the Counter App with Custom hook

Now, we are going to create the exact same counter but, this time, its functionality will be implemented using a React custom hook. I named the file CounterTwo.js, also located in the components folder.

Like we did earlier, we start by importing the hook we are working with. In this case, we will be importing another file (useCounter) inside which we have imported the hook we are working with:

import useCounter from "./useCounter";

the {Link} from react-router-dom, to be able to link to other pages,

import { Link } from 'react-router-dom';

and of course, the CSS file.

import "../App.css"

Let us quickly access the useCounter file we imported, where we created the custom hook itself:

import { useState } from "react";

export default function UseCounter(initialCount) {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    setCount((prevCount) => prevCount + 1);
  };
  const decrement = () => {
    setCount((prevCount) => prevCount - 1);
  };
  const reset = () => {
    setCount(initialCount);
  };
  const setValue = (num) => {
    setCount(5);
  };
  return [count, increment, decrement, reset, setValue];
};

Then, we proceed to create the CounterTwo component:

function CounterTwo() {
  const [count, increment, decrement, reset, setValue] = useCounter(0);

  return (
    <div className="counter">
      <h1 className="header">Counter App with Custom Counter Hook</h1>
      <div className="count">Count = {count}</div>
      <br />

    <div>
      <button className="buttons" onClick={increment}>Increment</button>
      <button className="buttons" onClick={reset}>Reset</button>
      <button className="buttons" onClick={decrement}>Decrement</button>
      <button className="buttons" onClick={setValue}>Set Value</button>
   </div>

      <div className="mini-div">
        <h3>Click on the button below to go back</h3>
        <button className="goback_button">
          <Link to="/" className="link">Click Me!</Link>
        </button>
      </div>
    </div>
  );
};

export default CounterTwo;

This brings us to the end of the Counter application, all we are left with is the custom 404 and error boundary page.

Step 5: Creating the custom 404 page with React Router

A 404 page is a page that comes up when a user tries to access a non-existent page or URL on your website. By default, react-router will display a simple "404 - Page Not Found" message when a user tries to access an invalid URL in your application, however, you can customize this message by creating a custom 404 page using react-router.

We will be adding a special route and passing it the "*" prop in order to render everytime a path does not match.

The code sample for creating a custom 404 page is shown below:

import React from "react";
import { Link } from "react-router-dom";
import "./NotFound.css";

export default function NotFound() {
  return (
    <div className="not-found">
      <h1 className="error">404</h1>
      <h3 className="oops">OOPS! YOU SEEM TO BE LOST.</h3>
      <p className="sorry">Sorry, the page you're looking for doesn't exist.</p>
      <p className="return">Click on the link below to return home</p>
      <div>
        <button className="goback_button">
          <Link to="/" className="link">
            Counter App
          </Link>
        </button>
      </div>
    </div>
  );
}

Here's a snapshot of the 404 page:

Step 6: Implementing Error Boundary

Errors are bound to happen in our applications, as such, some methods have been developed to help catch and handle errors that occur in the UI parts of our component.

To create an error boundary, we simply have to create a class component (because an error boundary component can only be written as a class-based component), making use of a third-party library, react-error-boundary.

Here's an example of what a simple error boundary should look like (taken from the Reactdocs):

class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false };
    }

    static getDerivedStateFromError(error) {
      // Update state so the next render will show the fallback UI.
      return { hasError: true };
    }

    componentDidCatch(error, errorInfo) {
      this.setState({ hasError: true})
      // You can also log the error to an error reporting service
    //   logErrorToMyService(error, errorInfo);
    }

    render() {
      if (this.state.hasError) {
        // You can render any custom fallback UI
        return <h1>Something went wrong.</h1>;
      }

      return this.props.children; 
    }
  }

  export default ErrorBoundary;

With react-error-boundary, we can simply wrap components where we expect errors with the provided ErrorBoundary component and pass in some extra props to customize our error boundary's behavior.

We can simply wrap our component with the ErrorBoundary component and pass in our fallback component so that if there is an error, our fallback component will be rendered.

Yayyy!! We have come to the end of our React application. Your app should be running locally on localhost:3000. Below is a snapshot of what the counter app looks like:

Conclusion

Thank you for reaching this far, you rock!!

In this article, we've talked about how you can build a React application, implementing various features using other libraries like React-Router and React-Error-Boundary.

It was quite basic, but I enjoyed working on it.

You can check out the app on the live link and also the link to the github repo.

Your reviews will be appreciated.

GitHub repo: https://github.com/oyin-systems/altschool-examproject

Live link: https://altschool-examproject.vercel.app/

Thanks for reading!