How To Create a Counter Application with React

How To Create a Counter Application with React

One good way to consolidate your knowledge of React concepts is by building applications that will require you to apply those concepts.

In this article, you will learn how to build a counter page with an Increment, Decrement, Resetvalue, and Setvalue function implementing a combination of states. For this, you will need to apply some React concepts such as useReducer, useNavigate, and others.

To get started, I'll define what React and custom hooks are.

React is a declarative, open-source, front-end JavaScript library used in building interactive user interfaces and single-page applications.

Custom React JS hooks are reusable functions used to add component logic to multiple components without having to rewrite the code. And that is the main idea behind custom hooks - code reusability. Custom logic usually starts with the word use and in this case, the useReducer hook will be used.

Building a custom counter hook

To get started, you need to have a few setups in place.

Prerequisite:

  • Basic knowledge of React

  • Node.js installed on your local machine

In your src folder, create 3 files in your components folder representing the 3 pages you will be working with; the counter page, the error boundary page, and the 404 page. Create two additional files. The first file should be used to write your counter logic, and the second will contain links to the three component files. So your src folder should look something like this:

You will also need some packages to install. These packages are React-Router, React-helmet-async, and React-Error-Boundary.

Run the following code in your code editor terminal to download these packages.

npm install react-router-dom 

npm install react-helmet-async

npm install react-error-boundary

In your App.js file, import the following:

import { Route, Routes, useNavigate } from "react-router-dom";
import Counter from "./Components/Counter1";
import Error404Page from "./Components/Error404Page";
import Header from "./Components/Header";

import { ErrorBoundary } from "react-error-boundary";
import TestError from "./Components/TestError";

Export the App.js file to make it available for import in the index.js file.

You can copy the following code into your index.js file.

import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import "./App.css";
import { HelmetProvider } from "react-helmet-async";
import "./index.css";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <HelmetProvider>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </HelmetProvider>
);

Now let's go back into the App.js:

const ErrorBoundaryFallback = ({ error, resetErrorBoundary }) => {
  return (
    <div id="wholepage">
      <p>{error.message}</p>
      <button className="errorpage" onClick={() => resetErrorBoundary()}>Restart application</button>
    </div>
  );
};

In the example above, we create a function, ErrorBoundaryFallback which takes in two parameters and returns the error message and the button element with the onClick event.

We then make use of the useNavigate hook from react-router-dom to allow us to navigate to the home page whenever an error is encountered in the application. All the route component paths are placed inside the Routes tag. This enables navigation between each route.

This is demonstrated in the code below:

const App = () => {
  const navigate = useNavigate();
  return (
    <>
      <Header />
      <ErrorBoundary
        FallbackComponent={ErrorBoundaryFallback}
        onReset={() => navigate("/")}
      >
        <Routes>
          <Route path="/" element={<Counter />} />
          <Route path="/testerror" element={<TestError />} />
          <Route path="/test404page" element={<Error404Page />} />

          <Route path="*" element={<Error404Page />} />
        </Routes>
      </ErrorBoundary>
    </>
  )
};

On the counter page, we import the following libraries:

import { useRef } from "react";
import { Helmet } from "react-helmet-async";
import useCounter from "./counter";

TheuseRef hook here is used to get the value of the input field so we can pass the value along to the stateReducer whenever we call the set function. The useCounter returns a function with an object that contains the counter functions(increment, decrement, reset, set) and the state.

const Counter = () => {
  // To get the input value.
  const inputRef = useRef();

  const { count, increment, decrement, reset, set } = useCounter();

It also returns a function that can be used in an onClick event in any button whenever any of the buttons is clicked. The code is shown below:

 <h1>{count}</h1>
        <button className="increment" onClick={() => increment()}>
          Increase
        </button>
        <button className="decrement" onClick={() => decrement()}>
          Decrease
        </button>
        <button className="reset" onClick={() => reset()}>
          Reset
        </button>

        <input type="number" ref={inputRef} />
        <button
          className="set"
          onClick={() => {
            const value = Number(inputRef.current.value);
            set(value);
          }}
        >
          Set
        </button>

Export each of the components pages to be available in theApp.js this way:

export default Counter;

Now let's go into the counter.js file that defines the functionality for the counter page. Here, we import useReducer from React.

import { useReducer } from "react";

The stateReducer is the function that is called whenever an action is dispatched and it is used to update the states.

const useCounter = () => {
  const [state, dispatch] = useReducer(stateReducer, initialData);

  const increment = () => {
    dispatch({ type: "increment" });
  };

  const decrement = () => {
    dispatch({ type: "decrement" });
  };

  const reset = () => {
    dispatch({ type: "reset" });
  };

  const set = (value) => {
    dispatch({ type: "set", payload: value });
  };

  return { count: state.count, increment, decrement, reset, set };
};

The stateReducer function is triggered whenever an action is dispatched and then there will be an update in the state of the counter depending on the identifier passed to the stateReducer function we declared.

const initialData = { count: 0 };

const stateReducer = (state, action) => {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return { count: 0 };
    case "set":
      return { count: action.payload };
    default:
      return state;
  }
};

For the Error404page which displays our 404 error, we start by importing some third-party libraries.

import { useNavigate } from "react-router-dom";
import { Helmet, HelmetProvider } from "react-helmet-async";

This page basically sets the error message and takes the user back to the home(counter) page. A function is created which returns the SEO properties of the page. It is set and wrapped in the helmet tag to improve web accessibility.

Finally, for the TestError.js page, we also start by importing some React third-party libraries.

import { useNavigate } from "react-router-dom";
import { Helmet, HelmetProvider } from "react-helmet-async";

Then we declare a function that uses useState to set the value of the content from the string Hello World to an empty array. This causes the page to throw an error.

The code is shown below:

const Error404Page = () => {
  const navigate = useNavigate();
  return (
    <HelmetProvider>
    <Helmet>
        <title>Counter</title>
        <meta name="description" content="Error 404 page throws an error!" />
      </Helmet>

    <br></br>
    <br></br>
      <div id= "message">This page does not exist!</div>
      <br></br>
      <button className="page404" onClick={() => navigate("/")}>Go to home</button>
    </HelmetProvider>
  );
};

The same protocol for the application SEO is also observed here.

The HelmetProvider tag is used to wrap the page component to create context and make it available for exporting in the root app.

Conclusion

In this article, we built a counter app implementing increment, decrement, reset, and setValue functions using useReducer to update our states. We also implemented a page to test the 404 and another page to test the error boundary.

The link to the entire code is available on GitHub

Let me know if you found this article helpful.

Did you find this article valuable?

Support DevUnlock with Nana by becoming a sponsor. Any amount is appreciated!