DEV Community

Abolarin Esther
Abolarin Esther

Posted on • Updated on

A Simple Counter App using React.JS 

counter app with increment, decrement and reset buttons

Table of Content

  • Introduction
  • Project overview
  • Getting Started
  • Integrating the Counter Page
  • Error Boundary page
  • Custom 404 Page
  • About Page
  • Conclusion

Introduction

Firstly let us see the meaning of custom hook. Custom Hook is a more advanced feature of React. It can let you reuse stateful logic without changing your component hierarchy. It will also make your component code more readable and easier to maintain.

Task

Setup a custom counter hook with increment, decrement, reset, setValue functions with a valid UI. Implement a page for the custom hook, 404, and a page to test error boundary.

Project Overview

I built this web application with some important features and implementation which include;

  • An input field to set the counter to any starting value.
  • Buttons that can automatically increase or decrease the counter by a value more or less than 1 when clicked.
  •  A reset button that automatically set count back to 0
  • Implementing a 404 Page, Error Boundary, and About Page.
  • A clean and user friendly UI.
  • Routing of pages.

Getting Started

Before i began the project, i already had prior knowledge and was familiar with the basics of React Hooks and how to create a custom hook.

Firstly, i opened my terminal and i input the command npm create-vite@latest followed by the project name, i named mine My-Counter App.

Create react app

npm create-vite@latest my-react-app
cd my-react-app 
npm install 
npm run dev
Enter fullscreen mode Exit fullscreen mode

Install Dependencies

The next thing i did was to install the dependency i will be needing for my project, react router, which will enables me to implement dynamic routing in my web app.

npm i react-router-dom
Enter fullscreen mode Exit fullscreen mode

Setup

In my newly created app i opened the src folder and i deleted the asset and App.css files.
Then i went ahead to create another folder in the src folder which i named Components, in the components folder i created 6 files named: counter.jsx, useCounter.jsx, errorBoundary.jsx, notFound.jsx and about.jsx.

Then i set up my react-router in the App.jsx file

import { Routes, Route, NavLink } from "react-router-dom";
import Counter from "./components/Counter";
import About from "./components/About";
import NotFound from "./components/NotFound";
import TestError from "./components/TestError";
import './index.css'

function App() {
  return (
    <div className="App">
      <header className="nav-header">
        <NavLink className="counter-logo" to="/"><img src="./images/counter-logo.png" alt="logo" />
          Counter
        </NavLink>
        <nav>
          <NavLink className="nav-link" to="/test-error-boundary">Error Boundary</NavLink>
          <NavLink className="nav-link" to="/about">About</NavLink>
        </nav>
      </header>
      <main>
        <Routes>
          <Route path="/" element={<Counter />} />
          <Route path="/about" element={<About />} />
          <Route path="/test-error-boundary" element={<TestError />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </main>
      <footer>
        <p className="text-align-center">
          Copyright &copy; 2024 Ayaoba. <br />
          All rights reserved.
        </p>
      </footer>
    </div>
  )
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Counter Page

Implementing useCallback

At the top of the Counter.js. file i imported the useCallback hook from React.

The React useCallback Hook will returns a memoized callback function. which will allows us to isolate resource intensive functions so that they will not automatically run on every render.

import  { useCallback } from 'react'
import useCounter from '../components/UseCounter';

function Counter() {
    const { increment, decrement, reset, setValue, count } = useCounter();

    const handleSubmit = useCallback(
        (event) => {
            event.preventDefault();
            setValue(event.target.newValue.value);
            event.target.newValue.value = "";
        },
        [setValue]
    );

    return (
        <div className="counter">
        <h1>{count}</h1>
       <div className='counter-button'>
       <button className='counter-btn_plus button-shadow' onClick={() => increment()}><img src="./images/plus.png" alt="plus" /></button>
       <button className='counter-btn_reset button-shadow' onClick={() => reset()}><img src="./images/reset.png"  alt="reset" /></button>
       <button className='counter-btn_minus button-shadow' onClick={() => decrement()}><img src="./images/minus.png" alt="minus"/></button>
       </div>
            <form onSubmit={handleSubmit}>
          <input className='new-value' name="newValue" type="tel" placeholder="New value" />
          <button className="btn-set-value">Set value</button>
          </form>
        </div>
    );
  }
export default Counter;
Enter fullscreen mode Exit fullscreen mode

Implementing useReducer

In the useCounter.jsx file I implemented the UseReducer method. 
useReducer is a React hook that allows us to manage state in a simple and consistent way. This Hook accepts two arguments. It returns the current state and a dispatch method.

import { useReducer, useCallback } from "react";

const useCounter = () => {
  const ACTIONS = {
    INCREASE: "increase",
    DECREASE: "decrease",
    RESET: "reset",
    SET_VALUE: "setValue",
  };
  function getValue(value, count) {
    let result = count;
    if (!value) {
        result = count;
    } else if (Number.isFinite(+value)) {
        result = +value;
    }
      return result;
    }

  function reducer(count, action) {
    switch (action.type) {
      case ACTIONS.SET_VALUE:
        return getValue(action.value, count);
      case ACTIONS.INCREASE:
        return count + 1;
      case ACTIONS.DECREASE:
        return count -1;
      case ACTIONS.RESET:
        return 0;
      default:
        return count;
    }
  }
  const [count, dispatch] = useReducer(reducer, 0);
  const increment = useCallback(() => {
    dispatch({ type: ACTIONS.INCREASE });
  }, [ACTIONS.INCREASE]);

  const decrement = useCallback(() => {
    dispatch({ type: ACTIONS.DECREASE });
  }, [ACTIONS.DECREASE]);

  const reset = useCallback(() => {
    dispatch({ type: ACTIONS.RESET });
  }, [ACTIONS.RESET]);

  const setValue = useCallback(
    (value) => {
      dispatch({ type: ACTIONS.SET_VALUE, value });
    },
    [ACTIONS.SET_VALUE]
  );

  return { count, increment, decrement, reset, setValue };
};

export default useCounter;
Enter fullscreen mode Exit fullscreen mode

Error Boundary page

I opened the errorBoundary.jsx file and i created an Error boundary component to catch JavaScript errors anywhere in its child component tree, log the errors, and display a fallback UI instead of the component tree that crashed.

Custom 404 Page

In notfound.jsx i added a custom 404 page that will display when the server cannot find a page the user has requested.

About Page

Finally, in About.jsx i added an About page that consist of relevant information on how to navigate though the counter app.

Conclusion

This project was my second semester examination Project at AltSchool Africa.

You can check out the live site of my counter app. 
And also, the GitHub repo to the application.

Thanks for Reading.

Top comments (0)