DEV Community

Oluwaseun Oyedele
Oluwaseun Oyedele

Posted on • Edited on

How to build a React useCounter and use Reducer counter app

To start with, what is React?

React is a powerful and flexible JavaScript library that makes it easy to build complex and high-performance user interfaces.

React allows developers to build reusable UI components and manage the state of their applications.

How to Create a React Application

Note: In your local computer, you must install Node.

First, let's create a new React application using create-react-app in your command line, navigate to the documents directory where you want to create your app and run the following command:

npx create-react-app my-counter-app
Enter fullscreen mode Exit fullscreen mode

This will create a new directory called 'my-counter-app' that contains the basic file structure for our React application.

Image description

Next, Using your VS code, launch 'the my counter app' file.

Image description

The App.js in the src directory is where we will build our counter application.
Image description.

Clicking on it will see the pre-written code created by React.

Image description

To see the the pre built app by the pre-written code by React.

Go to your terminal,

Image description

go to the documents directory where our my-counter-app file that contains our React Application in our terminal. Follow these command lines in the terminal.

 cd documents

Enter fullscreen mode Exit fullscreen mode

Image description

It will take you to the documents directory. From the documents directory, go to where our my-counter-app file for the React Application is. Follow these command lines in your terminal,

cd my-counter-app
Enter fullscreen mode Exit fullscreen mode

it will take you to the my-counter-app file in your directory.

Image description

To run the development on the server, follow these command lines on your terminal.

npm run start
   or
npm start
Enter fullscreen mode Exit fullscreen mode

The file will need to be successfully compiled, which will take a few minutes. After that, you can see my-counter-app in the browser. You should be directed to your browser where this should appear.

Image description

when you go to your terminal, you should see this.

Image description

Return to your VS code after that and remove the codes from the App.js file in the src directory.

Image description

And your my-counter-app in the localhost:3000 will be empty.

The counter app will also include the following features..

  • Error Boundary, to ensure that an error in a child component does not cause the entire application to crash.

  • React Router,a library for React that enables you to manage routing within your application.

  • The technique of optimizing a website or web application to increase its exposure and position in search engine results pages is known as SEO.

  • A page that directs users to a broken error page

Installing these packages is required in order to implement the added features to the counter application.

  • React Router

  • React Error Boundary

  • React Helmet Async

Following these command lines will allow us to install them together.

npm install react-router-dom  react-helmet-async react-error-boundary.
Enter fullscreen mode Exit fullscreen mode

Next, lets's create a new directory in the src directory called hooks and add a new file in the hooks called useCounter.js. This file will contain the useCounter hook that we will use to manage the state of our counter. Here is how the useCounter file and hook directory going to look:

Image description

This hook use usestate hook to manage the counter, and expose increment, decrement, multiply, input value and the count value. we have to import it. The useCounter hook's code is provided here.

import { useRef, useState } from 'react';

function useCounter() {
  const [count, setCount] = useState(0);

  const inputRef = useRef();

  const decrement = () => setCount((prevValue) => prevValue - 1);
  const increment = () => setCount((prevValue) => prevValue + 1);
  const multiply = () => setCount((prevValue) => prevValue * 9);
  const reset = () => setCount(0);
  const handleClick = () => setCount(inputRef.current.value);

  return {
    count,
    decrement,
    inputRef,
    handleClick,
    increment,
    multiply,
    setCount,
    reset,
  };
}

export default useCounter;

Enter fullscreen mode Exit fullscreen mode

Now that we have our hook, we will create new directory in the src directory called components. Let's create a new file in the components directory called CustomHookPage. js to initialize our useCounter hook. How do we do that? we have to import it to be able to access it.

In this file, we'll create a component that displays the current count and buttons to increment, decrement, input any value, multiply, and reset the count.

import React from 'react';
import useCounter from '../hooks/useCounter';


const CustomHookPage = () => {
  const counter = useCounter(0);
  const {
    inputRef,
    count,
    handleClick,
    multiply,
    decrement,
    increment,
    reset,
  } = counter;

  return (
    <div>


      <div className="main-counter-container">
        <div className="counter-container">
          <div>
            <button onClick={increment}>
              Increment
            </button>
            <button onClick={decrement}>
              Decrement
            </button>
            <button onClick={multiply}>
              multiply by 9
            </button>
          </div>
           </div>{count}</div>

           <div>
            <p>
              <input ref={inputRef} type="number" />
              <button onClick={handleClick}>set value</button>
            </p>
          </div>

          <button
            onClick={reset}
          >
            Reset
          </button>
        </div>
      </div>
    </div>
  );
};

export default CustomHookPage;

Enter fullscreen mode Exit fullscreen mode

Next, lets's create a another file in the hooks directory called 'useReducer.js'. This file will contain the useReducer hook that we will use to manage the state of our counter. Here is how the hooks directory going to look when the useReducer file is added:

Image description

This hook create a useReducer function that takes in the current state and an action, and returns the updated state based on the action type. In this case, our state will just be a single number representing the count, and our actions will be increment, decrement, multiply, and an input value. Here is the code.

const countReducer = (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 'multiply':
      return { count: state.count * 3 };
    case 'setValue':
      return { count: action.value };
    default:
      return state;
  }
};

export default countReducer;

Enter fullscreen mode Exit fullscreen mode

Now that we have another hook, let's initialize it in a new file in the components directory called UseReducerPage.js. How do we do that? we have to import it to be able to access it.

In this file, we'll create a component that displays the current value of the counter, and dispatch function to send actions when user clicks the increment, decrement, input value, multiply, and reset. Here is code.



import React from 'react';
import useReducer from '../hooks/useReducer';

const UseReducerPage = () => {
  const [state, dispatch] = useReducer(countReducer, { count: 0 });

  const setValue = (value) => dispatch({ type: 'setValue', value });

  return (

      <div>
        <div>
          <div>
            <button
              onClick={() => dispatch({ type: 'increment' })}
            >
              Increment
            </button>
            <button
              onClick={() => dispatch({ type: 'decrement' })}
            >
              Decrement
            </button>
            <button
              onClick={() => dispatch({ type: 'multiply' })}
            >
              multiply by 3
            </button>
          </div>
          <div>{state.count}</div>

          <p>
            <input
              type="number"
              placeholder="input number"
              value={state.count}
              onChange={(e) => setValue(e.target.value)}
              onMouseOut={(e) => (e.target.value = '')}
            />
          </p>

          <button
            onClick={() => dispatch({ type: 'reset' })}
          >
            Reset
          </button>
        </div>
      </div>
    </div>
  );
};

export default UseReducerPage;

Enter fullscreen mode Exit fullscreen mode

What next?

In our components directory, add an ErrorPage file. This component will show users an error link.

Image description

We will create a function that return errors

import React from 'react';

const ErrorPage = () => {
  return (
    <div className="error-container">
        <h1>404 Page</h1>
        <p>This is an Error page.</p>
      </div>
    </div>
  );
};
export default ErrorPage;

Enter fullscreen mode Exit fullscreen mode

In our components directory, add an Error Boundary file. In this component, we'll build a counter function that detects errors when  count exceeds a certain threshold. Additionally, a fallback function will be created in the components directory to return to home .

Error Boundary file.

Image description

In the Error Boundary component, we have to import the Error Handler from the Error Boundary package we installed to handle the error, we will also import useRef and useState before we create the function.

 import React from 'react';
import { useRef, useState } from 'react';
import { useErrorHandler } from 'react-error-boundary';


export default function ErrorBoundary() {

  const [state, setState] = useState(0);
  const [history, setHistory] = useState('');


  const HandleError = useErrorHandler();

  if (state > 5) {
    throw new Error('Exceeded count!');
  }
  const Increment = () => {
    try {
      if (state === 5) {
        throw new Error('Exceeded count!');
      } else {
        setState(state + +1);
      }
    } catch (e) {
      HandleError(e);
    }
  };
  const Decrement = () => {
    setState(state - 1);
  };
  const Reset = (prev) => {
    if (prev === 0) {
      setHistory(0);
    } else {
      setHistory(state);
    }
    setState((prev = 0));
  };

  const inputRef = useRef(null);


  function handleClick() {
    setState(inputRef.current.value - 0);
  }

  return (
    <section>
      <div>
        <h1>Error prone!</h1>
        <h3>
          Count limit for this counter is 5. Numbers above the 
    set limit throw
          is error
        </h3>
        <p>
          <input ref={inputRef} type="number" id="message" name="message" />
          <button onClick={handleClick}>set value</button>
        </p>
        <div>Count: {state}</div>
        <button onClick={Increment}>
          Increment
        </button>
        <button onClick={Decrement}>
          Decrement
        </button>
        <button onClick={Reset}>
          reset
        </button>
        <p>Your last count before you reset was: {history}</p>
      </div>
    </section>
  );
}

Enter fullscreen mode Exit fullscreen mode

Fallback file.

Image description

In the Fallback component, we will create a function that returns to home page after error thrown.

import React from 'react';

export const Fallback = () => {
  const Return = () => {
    window.location.href = '/';
  };
  return (
    <div>
      <h1>Something went wrong!</h1>
      <p>Count limit exceeded!</p>
      <button onClick={Return}>Return to Home page</button>
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

In our components directory, add a Home file. Our useCounter and useReducer hooks, Error page, and Error Boundary will all be located on this page, which will serve as our home page.

Image description

We have to create a function for it.

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

const Home = () => {
  return (
    <div>
      <div>
        <h1>Hi, Welcome to my counter Project.</h1>
      </div>
    </div>
  );
};

export default Home;

Enter fullscreen mode Exit fullscreen mode

Now, we have created:

  • We have created our useCounter hook Counter
  • we have created our useReducer hook Counter
  • An Error Page that has a broken site.
  • An Error Boundary page that handles count error
  • A Home page that links every other.

What next?

We must import all of our components directory files into the empty App.js in order to see what we have created.

Also, in order to enable managing routing within what we have created in our application, we need to import React Router packages that we installed in the App.js file.

Image description

Then, we must create an App.js function that returns all imported components. Within that function, we must create a different function to handle errors, and log the associated information on the console.

We must enclose the imported components in the pre-installed React Router before returning the function. It contains nested routes. We'll discuss the reason for nested routes later.

We must wrap our Error Boundary to handle errors, fallbacks, and the error handler before wrapping our nested routes in the router.

Why are nested routes necessary?

Having a Route component inside the opening and closing tags of another Route component is known as nested routing.

When we do this, our App.js appears as follows:

 import React from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import CustomHookPage from './components/CustomHookPage';
import UseReducerPage from './components/UseReducerPage';
import Home from './components/Home';
import ErrorBoundary from './components/Errorboundary';
import { Fallback } from './components/Fallback';
import ErrorPage from './components/ErrorPage';


function App() {
  const errorHandler = (error, errorInfo) => {
    console.log('logging', error, errorInfo);
  };
  return (
    <main>
      <Router>
        <ErrorBoundary FallbackComponent={Fallback} onError={errorHandler}>
          <Routes>
            <Route path="/" element={<Home />} />;
            <Route path="/customhookpage" element={<CustomHookPage />} />;
            <Route path="/usereducerpage" element={<UseReducerPage />} />;
            <Route path="/errorboundary" element={<ErrorBoundary />} />;
            <Route path="/errorpage" element={<ErrorPage />} />;
          </Routes>
        </ErrorBoundary>
      </Router>
    </main>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

To enable routing within our application, we added the paths to each component in the nested routes. As a result, we also need to add it as a link so that users may access the pages we created.

To accomplish this, we must navigate to each component of each page we generated and import the proper link from React Route.

Home page

We have to import it link and return it link in the Home component

import React from 'react';

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

const Home = () => {
  return (
    <div >
      <div>
        <h1>Hi, Welcome to my counter Project.</h1>
          //Link returned in the function

         <Link to="/customhookpage">
          <button>custom hook page</button>
        </Link>
        <br />
        <br />
        <Link to="/UseReducerPage">
          <button>usereducer page</button>
        </Link>
        <br />
        <br />
        <Link to="/error">
          <button>Error boundary</button>
        </Link>
        <br />
        <br />
        <Link to="/errorpage">
          <button>error page</button>
        </Link>
      </div>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

Error page

We have to import it link and return it link in the Error component

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

const ErrorPage = () => {
  return (
    <div className="error-container">
      <Link className="link-color" to="/">
        <button className="link-button">home</button>
      </Link>
      <div className="error">
        <h1>404 Page</h1>
        <p>This is an Error page.</p>
      </div>
    </div>
  );
};
export default ErrorPage;

Enter fullscreen mode Exit fullscreen mode

Custom Hook page

We have to import it link and return it link in the Custom Hook Page component

import React from 'react';
import useCounter from '../hooks/useCounter';

//imported link
import { Link } from 'react-router-dom';


const CustomHookPage = () => {
  const counter = useCounter(0);
  const {
    inputRef,
    count,
    handleClick,
    multiply,
    decrement,
    increment,
    reset,
  } = counter;

  return (
    <div>
      <div>
          //returned link
        <Link className="link-color" to="/">
          <button>home</button>
        </Link>
        <Link className="link-color" to="/UseReducerPage">
          <button>usereducer page</button>
        </Link>
        <br />
        <br />
      </div>

      <div className="main-counter-container">
        <div className="counter-container">
          <div>
            <button onClick={increment}>
              Increment
            </button>
            <button onClick={decrement}>
              Decrement
            </button>
            <button onClick={multiply}>
              multiply by 9
            </button>
          </div>
          <div>{count}</div>

          <div>
            <p>
              <input ref={inputRef} type="number" />
              <button onClick={handleClick}>set value</button>
            </p>
          </div>

          <button
            onClick={reset}
          >
            Reset
          </button>
        </div>
      </div>
    </div>
  );
};

export default CustomHookPage;

Enter fullscreen mode Exit fullscreen mode

UseReducer Page

We have to import it link and return it link in the UseReducer component

import React from 'react';
import countReducer from '../hooks/countReducer';
import { useReducer } from 'react';

//imported link
import { Link } from 'react-router-dom';


const UseReducerPage = () => {
  const [state, dispatch] = useReducer(countReducer, { count: 0 });

  const setValue = (value) => dispatch({ type: 'setValue', value });

  return (
    <div>

      <div>
        //returned link
        <Link to="/">
          <button>home</button>
        </Link>
        <Link to="/customhookpage">
          <button>custom hook page</button>
        </Link>
      </div>
      <div>
        <div>
          <div>
            <button onClick={() => dispatch({ type: 'increment' })}
            >
              Increment
            </button>
            <button
              onClick={() => dispatch({ type: 'decrement' })}
            >
              Decrement
            </button>
            <button
              onClick={() => dispatch({ type: 'multiply' })}
            >
              mulitiply by 3
            </button>
          </div>
          <div>{state.count}</div>

          <p>
            <input
              type="number"
              placeholder="input number"
              value={state.count}
              onChange={(e) => setValue(e.target.value)}
              onMouseOut={(e) => (e.target.value = '')}
            />
          </p>

          <button
            onClick={() => dispatch({ type: 'reset' })}
          >
            Reset
          </button>
        </div>
      </div>
    </div>
  );
};

export default UseReducerPage;

Enter fullscreen mode Exit fullscreen mode

What next?

After completing the building of our application, the following step is to import our React Helmet Async in our application to increase its exposure and position in search engine results.

Then import our App.js file to the root of our index.js directory.

Image description

Summary

we created a useCounter and useReducer hook counter Application, that has pages link to Error Boundary, Error Page with a good SEO.

You can add your own UI.

source code: https://github.com/Seundev/react-pno6jr.git
Live link: https://react-pno6jr.stackblitz.io

Top comments (0)