Table of contents
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.