DEV Community

Kafayat Adigun
Kafayat Adigun

Posted on

How to build a counter app using useReducer react.js

In this article, we will explore how to create a counter app using useReducer .

Prerequisites

This article is for developers stepping into React.js library not for beginner in web development. I assume the reader already has basic knowledge in the following:

  • Html

  • CSS

  • Basic of JavaScript(ES6)

  • Basic of React.js library

What is React?

The React.js framework is an open-source JavaScript framework and library developed by Facebook. It’s used for building interactive user interfaces and web applications quickly and efficiently with significantly less code. It is one of the most popular front-end JavaScript libraries. Many companies use React to develop their user interfaces and it's gained wide popularity among developers

Basic concept of Reactjs

Before we dive into the project, you need to understand some basic things we will be using in this project. To have a deep understanding of reactjs click here

  • Component: Components are independent and reusable bits of code. They serve the same purpose as JavaScript functions. Using components you can split the user interface into separate parts. You can then reuse those parts and work with them independently. Components come in two types, Class components and Function components, in this tutorial we will concentrate on Function components.

  • JSX: JSX stands for JavaScript XML. It is a syntax extension for JavaScript that lets you write JavaScript similar to HTML.

  • State: state is a built-in React object that is used to contain data or information about the component. A component’s state can change over time; whenever it changes, the component re-renders. The change in state can happen as a response to user action or system-generated events and these changes determine the behavior of the component and how it will render.Learn more

  • Hooks: hook is a react function that lets you use state and react features from a function based component. Hooks let you use the functions instead of switching between HOCs, Classes, and functions. As Hooks are regular Javascript functions, thus you can use the built-in Hooks and create your own custom one.

Installation

Before we get anything done we need to have NodeJS and npm installed. To install Nodejs click here.
Nodejs - is a runtime environment use to mainly execute javascript code.
NPM - npm stands for Node Package Manager. It is the default package manager for JavaScript's runtime Node.js. You can use it to install packages for your javascript apps. Fortunately it comes with Node, you don't have to install it separately.

How to create React App using vite

Once you have Nodejs installed, open your terminal and enter the following command inside the folder you want your project to be

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

The above command will create a react app for us that we can start working with immediately.
Cd the name of the project in your terminal. This allows us to be in the project directory/folder. After that is done you will open your vs-code from your terminal using the following code.

code .
Enter fullscreen mode Exit fullscreen mode

The above command will open the project folder in your text editor.

Open your text-editor terminal and enter the following command

npm install
Enter fullscreen mode Exit fullscreen mode

the above command is use to install the react dependencies. After that, type the below command to start your project in your browser

npm run dev
Enter fullscreen mode Exit fullscreen mode

copy the local address and paste in your web browser. You should have this react vite template in your browser

the default vite
Before we start to get our hands dirty and dive into the code, we need to install some libraries.

npm install react-router-dom@6 

Enter fullscreen mode Exit fullscreen mode
npm install react-error-boundaries
Enter fullscreen mode Exit fullscreen mode

React-router

React-router-dom is basically the go-to library for managing routing in a single page application. You see, in traditional html webpages, we will be sending to physical static html pages that is in your domains folder, for example, if user clicked on Error page it will send user to the file Error.html which then contains all the content that is meant for the page, but in react that is completely different. By manipulating Virtual DOM, we display the content we want dynamically on the same page, hence we are using this routing library to display content according to user click.

Error Boundaries

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

To check whether you have installed the library, you may check out desired library is listed as dependencies in your package.json file.

Let's write some code.

First you clean up some code from the template.
Our main goal is to create main-page/Home, Error-page, 404-page and custom page.
Router will allow all children to use Link, you can think of it as anchor tag in html. Route component will then route the user based on the url entered in the address.

For example: ‘/’ means that if a user is in the base url direct to Home component.

When path={“/error”} then your url should be http://localhost:3000/error

Of course the react-router-dom is not just a simple routing library, it is actually a powerful tool, for now we will use it for basic web page routing only.

Say if you want to have dynamic routing, you configure with the following:

<Route path={'/:userID'} component={UserProfile}/>
Enter fullscreen mode Exit fullscreen mode

Main.jsx
In this component import BrowserRouter from react-router

import {BrowserRouter} from 'react-router-dom'
Enter fullscreen mode Exit fullscreen mode

wrap ur App component in Main component with BrowserRouter

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
   <BrowserRouter>
      <App />
   </BrowserRouter>
  </React.StrictMode>
)

Enter fullscreen mode Exit fullscreen mode

BrowserRouter: BrowserRouter is a router implementation that uses the HTML5 history API to keep your UI in sync with the URL. It is the parent component used to store all other components.

ReactstrictMode: React strict mode is a development mode only feature, it is use for highlighting potential problems in an application. It helps to identify unsafe lifecycles, legacy API usage, and a number of other features.

We are to create a Components folder inside the src folder and
create the following file with an extension of jsx
Home
Custom
Error
404
Navbar
ErrorFallback

Before going forward i will assume that you have some basic knowledge on writing simple css for styling, there is plenty of resource out there if you want to learn.

Lets go to App.jsx
In our app.jsx we will import the above created files.

import {ErrorBoundary} from 'react-error-boundary'
import ErrorFallback  from './Component/ErrorFallback'
import Navbar from './Component/Navbar'

import Home from './Component/Home'
import CustomHook from './Component/CustomHook'
import NoMatch from './Component/NoMatch'
import Error from './Component/Error'
Enter fullscreen mode Exit fullscreen mode

Next we import:

import {Routes, Route} from 'react-router-dom'
Enter fullscreen mode Exit fullscreen mode

Routes : It looks through all its child routes to find the best match and renders that branch of the UI.

Route: Route elements may be nested to indicate nested UI, which also correspond to nested URL paths.

Our routing will take place inside our app component. Let's create a 'className' called app or you create a fragment <>.
We will render our Navbar component inside the app component.Below the Navbar component will Routes all the pages.

 <div className="App">
        <Navbar />
          <Routes>
            <Route>
              <Route path='/' element={<Home/>} />
              <Route path='custom' element={<CustomHook />} />
              <Route path='error' element={<Error/>}/>
              <Route path='*' element={<NoMatch />}/>
            </Route>
          </Routes>
      </ErrorBoundary>
    </div>
Enter fullscreen mode Exit fullscreen mode

Navbar.js

The navbar component contains the navbar of the page. A navigation bar (also called a Navbar) is a user interface element within a webpage that contains links to other sections of the website

import {NavLink, Link} from 'react-router-dom'
Enter fullscreen mode Exit fullscreen mode
 <nav>
            <Link 
            to= '/'>
            <h2 className="logo">KATHY</h2>
            </Link>
            <div className="nav-link">
            <ul>

                <li>
                    <NavLink to='custom'
                    className={(isActive) =>(isActive? 'active' : 'inactive')}>Custom</NavLink>
                </li>
                <li>
                    <NavLink to='error'
                    className={(isActive) =>(isActive? 'active' : 'inactive')}>Error</NavLink>
                </li>
                <li>
                    <NavLink to='errorPage'
                    className={(isActive) =>(isActive? 'active' : 'inactive')}>404page</NavLink>
                </li>
            </ul>
            </div>
        </nav>
Enter fullscreen mode Exit fullscreen mode

NavLink: The NavLink is used when you want to highlight a link as active. So, on every routing to a page, the link is highlighted according to the activeClassName.

Link: A Link is an element that lets the user navigate to another page by clicking or tapping on it.

Still inside our app.js wrap the content inside the app className with the errorBoundary component.

<ErrorBoundary FallbackComponent={ErrorFallback}  onReset={()=>{
      navigate("/")
    }
        }>
        <Navbar />
          <Routes>
            <Route>
              <Route path='/' element={<Home/>} />
              <Route path='custom' element={<CustomHook />} />
              <Route path='error' element={<Error/>}/>
              <Route path='*' element={<NoMatch />}/>
            </Route>
          </Routes>
      </ErrorBoundary>
Enter fullscreen mode Exit fullscreen mode

ErrorBoundary is use to catch JavaScript error anywhere in the child component. We will be able to catch error in any of the pages in our counter app.
In the above component, we simply wrap our component with the ErrorBoundary component and pass in our ErrorFallback component to the FallbackComponent prop so that if there’s an error (that can be caught and handled by react-error-boundary), our fallback component will be rendered.

ErrorFallback.jsx
The ErrorBoundary also has an onError prop, which acts as a listener that is triggered when our error boundary catches and handles an error in its child components. It is from this place that we might choose to log such errors to whatever error logging service we might be using.Type the following command in your ErrorFallback.js

const ErrorFallback = ({ error, resetErrorBoundary }) => {

  return (
    <>
      <div role="alert" className="error">
        <h2>Something went wrong</h2>
        <p>{error.message}</p>
        <button onClick={resetErrorBoundary} className="fallback-btn">Go Home</button>
      </div>
    </>
  )
}
export default ErrorFallback;
Enter fullscreen mode Exit fullscreen mode

ResetErrorBoundary
react-error-boundary also provides a way for our code to recover from errors caught by our error boundaries. This is done using the reset keys and the resetErrorBoundary function passed to the fallback component
The error.meaasgae is use to return the error message.

Home.jsx
In your home.js import the following:

import { useReducer, useState } from "react"
Enter fullscreen mode Exit fullscreen mode

Our Home.js contain the following functionality

  1. An Input value form an a set value button to set the value inputed in the value form

  2. An increment button to increase the number of count.

  3. A decrement button to decrease the number of count.

  4. A reset button to reset the count to a default number.

type the following in the app.js

const Home = () =>{

    const [value, setValue] = useState(0);

    return(
        <div className="container">
            <div className="counter-display">
                <h1>Count : {state}</h1>
                <div className="btn">
                    <div className="item first">
                    <input 
                    type="text" 
                    value={value}
                    onChange={(e) =>{
                    setValue(e.target.value)
                    }}
                    className="input-box"/>
                <button 
                 onClick={handleSet}
                 className='btn-btn'
                 >SetValue
                 </button>
                    </div>
                    <div className="item">
                    <button 
                    onClick={handleDecreament}
                    className="item-btn"
                    disabled={state === 0}
                    >Decreament</button>


                <button 
                 onClick={handleIncrement}
                 className="item-btn"
                 >Increment
                 </button>
                    </div>
                <button  
                className="item second btn-btn"
                 onClick={handleReset}
                 >Reset
                 </button>
                </div>
            </div>

        </div>
    )

}
Enter fullscreen mode Exit fullscreen mode

In the above code we have only one input field and four buttons. The input field has a value and an onChange handler added so we can update the state based on the user's input.

Event Handler:Event handlers determine what action is to be taken whenever an event is fired. This could be a button click or a change in a text input. Event handlers are what make it possible for users to interact with your React app. Handling events with React elements is similar to handling events on DOM elements, with a few minor exceptions.

The React onClick event handler enables you to call a function and trigger an action when a user clicks an element, such as a button, in your app.

Event names are written in camelCase, so the onclick event is written as onClick in a React app. Function can be pass as props to the onClick function.

Next we are going to to create and name a file inside our src/components folder reducer with an extension of js.

Reducer.js
Let's talk about useReducer.
The useReducer Hook is used to store and update states, just like the useState Hook. It accepts a reducer function as its first parameter and the initial state as the second.

useReducer returns an array that holds the current state value and a dispatch function to which you can pass an action and later invoke it. While this is similar to the pattern Redux uses, there are a few differences.

For example, the useReducer function is tightly coupled to a specific reducer. We dispatch action objects to that reducer only, the dispatch function sends the action object to the store. At the time of dispatch, the components don’t need to know which reducer will process the action.

A reducer is a function that is able to process our message, our Action. A reducer takes the existing state and applies the message on it. The end result is a new state. A reducer typically operates on a slice of state.

useReducer is set up for you to create a reducer function and provide it with an action. The action provided should have a type and some value to update the state.

initialArg: The value from which the initial state is calculated. It can be a value of any type. How the initial state is calculated from it depends on the next init argument.

The dispatch function returned by useReducer lets you update the state to a different value and trigger a re-render. You need to pass the action as the only argument to the dispatch function:

action: The action performed by the user. It can be a value of any type. By convention, an action is usually an object with a type property identifying it and, optionally, other properties with additional information.
Writing the reducer function
A reducer function is declared like this:

function reducer(state, action) {
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Then you need to fill in the code that will calculate and return the next state. By convention, it is common to write it as a switch statement. For each case in the switch, you need to calculate and return some next state.
the reducer.js will be updated by adding the following code

export const reducer = (state, action) => {
    switch (action.type) {
      case 'Increment':
        return state + 1 

      case 'Decreament':
        return  state - 1 

      case 'Reset':
        return  0 

      case 'Set':
        return  state = action.payload

      default:
        return state;
    }
  }
Enter fullscreen mode Exit fullscreen mode

Next we will go back to our Home.jsx and import reducer.js

import {reducer} from './reducer'
Enter fullscreen mode Exit fullscreen mode

In the code below, we initialize state with the useReducer Hook:

const [state, dispatch] = useReducer(reducer, 0);
Enter fullscreen mode Exit fullscreen mode

Then we create function for each onClick function

const handleSet = () =>{
        dispatch({type: 'Set' , payload: +value})
        setValue(0)
    }
    const handleIncrement = (e) =>{
        e.preventDefault()
        dispatch({type: 'Increment'})
        //console.log('button clicked')


    }
    const handleDecreament = (e) =>{
        e.preventDefault();
        dispatch({type: 'Decreament'})

    }
    const handleReset = (e) =>{
        e.preventDefault()
        dispatch({type: 'Reset'})

    }
Enter fullscreen mode Exit fullscreen mode

The dispatch function accepts an object that represents the type of action we want to execute when it is called. Basically, it sends the type of action to the reducer function to perform its job, which, of course, is updating the state.

The action to be executed is specified in our reducer function, which in turn, is passed to the useReducer. The reducer function will then return the updated state.

The actions that will be dispatched by our components should always be represented as one object with the type and payloadkey, where type stands as the identifier of the dispatched action and payload is the piece of information that this action will add to the state.

To listen to events in React, add the onClickattribute which is the event handler to the target element. This specifies the function to be executed when that element is clicked, as shown above:

The whole Home.jsx should look like this

import { useReducer, useState } from "react"
import {reducer} from './reducer'
//import useCounter from "./useCounter"




const Home = () =>{

    const [state, dispatch] = useReducer(reducer, 0);

    const [value, setValue] = useState(0);

    const handleSet = () =>{
        dispatch({type: 'Set' , payload: +value})
        setValue(0)
    }
    const handleIncrement = (e) =>{
        e.preventDefault()
        dispatch({type: 'Increment'})
        //console.log('button clicked')


    }
    const handleDecreament = (e) =>{
        e.preventDefault();
        dispatch({type: 'Decreament'})

    }
    const handleReset = (e) =>{
        e.preventDefault()
        dispatch({type: 'Reset'})

    }

    return(
        <div className="container">
            <div className="counter-display">
                <h1>Count : {state}</h1>
                <div className="btn">
                    <div className="item first">
                    <input 
                    type="text" 
                    value={value}
                    onChange={(e) =>{
                    setValue(e.target.value)
                    }}
                    className="input-box"/>
                <button 
                 onClick={handleSet}
                 className='btn-btn'
                 >SetValue
                 </button>
                    </div>
                    <div className="item">
                    <button 
                    onClick={handleDecreament}
                    className="item-btn"
                    disabled={state === 0}
                    >Decreament</button>


                <button 
                 onClick={handleIncrement}
                 className="item-btn"
                 >Increment
                 </button>
                    </div>
                <button  
                className="item second btn-btn"
                 onClick={handleReset}
                 >Reset
                 </button>
                </div>
            </div>

        </div>
    )

}
export default Home;
Enter fullscreen mode Exit fullscreen mode

We move for ward to NoMatch.jsx

NoMatch.jsx
The NoMatch.jsx page is a page not found.
The useNavigate hook returns a function that lets you navigate programmatically.We use react-router to router between pages and to be able to redirect from the current page back to the Homepage we use useNavigate.

import {useNavigate} from 'react-router-dom'

const NoMatch = () => {
    const navigate = useNavigate()
    return (
       <div className="error-page">
        <h3>404 Page</h3>
        <p>Page not found</p>
        <button 
        className="error-btn"
        onClick={() =>{
            navigate('/') }}>Go Back</button>
       </div>
    );
}

export default NoMatch;
Enter fullscreen mode Exit fullscreen mode

The onClick function is use to go back to the homepage with the help of useNavigate.

Conclusion
In this article, we created a React application, implementing various features using third-party libraries such as React-Router, React-Error-Boundry.

You can write React code in different ways, but it's important to structure it as cleanly as possible. This will ensure you can maintain it easily and can help to improve the overall performance of your application.

You can follow several React practices recommended by the React community like avoiding repetitive code, writing tests for each React component, using object destructuring for props, and following naming conventions.

Top comments (0)