Hi everybody!
Today, we're going to learn how to create a very basic counter in React using hooks.
Let's assume that, at this point, we all already know the concept of component in React and the fact that every component has something called state that we need to manage in order for our app to work the way we want.
In this post, we'll be covering useState and useReducer hooks. We're going to study two different examples (each of them using a different hook) that will lead to the same result.
With all that said, let's get it started!
Main idea
The main idea is to create a basic counter that is going to be able to perform the following actions:
- Adding / subtracting one single unit to / from the total count.
- Adding / subtracting an arbitrary quantity to / from the total count.
Structure and styles
We'll be using the same HTML structure and CSS styles for both examples. The structure will basically consist of a total count, some buttons to call a few actions to be performed and an input field to introduce the quantity we want to add or subtract. Rest is just styling to make our counter's interface more user-friendly. But don't you rush. You could play with the code later.
Now, let's take a look at our counter's functionality.
Example 1: Using useState
Importing useState
In order to be able to use React hooks, we need to import them. So let's do it.
import React, {useState} from 'react';
Setting up the state
First thing that comes into mind when it comes to a counter, is, obviously, the total count. So we need to define it as part of the state and initialize its value to zero.
const [count, setCount] = useState(0);
useState returns a stateful value and a function to update it.
Same happens if we're planning to add / subtract an arbitrary quantity to / from the total count. Our app's state should know about that quantity. So, let's make it part of the state as well. Its default value will also be initialized to zero.
const [quantity, setQuantity] = useState(0);
Adding functionality
Now that we have defined the state for our app, we can start adding some basic functionality to the counter.
1. Adding / subtracting one single unit
First thing to remark is that we'll be triggering functionality through buttons, which means that these should reference functions to handle the actions that will be performed. We'll use React's onClick event handler for such purpose.
<button onClick={handleSubtractOne}>-1</button>
<button onClick={handleAddOne}>+1</button>
const handleSubtractOne = () => {
setCount(count - 1);
}
const handleAddOne = () => {
setCount(count + 1);
}
And that would be it for this basic functionality. Easy, right? Now, let's take a further step.
2. Adding / subtracting an arbitrary quantity
For implementing this functionality, we'll need an input field to enter the desired quantity and a couple of buttons as well.
<input type="text" value={quantity} onChange={handleOnChange} />
<button onClick={handleSubtractQuantity}>-</button>
<button onClick={handleAddQuantity}>+</button>
The onClick event handlers work in the exact same way as the others, with the only distinction that these ones call different handlers (because the functionality they're triggering is different).
The onChange event handler declared on the input element is used to store the entered value into the state.
Also note that the value we are passing to the value attribute on the input is the one stored in the state, which will be changing accordingly.
const handleOnChange = (e) => {
setQuantity(e.target.value);
}
const handleSubtractQuantity = () => {
if(quantity)
setCount(count - parseInt(quantity, 10));
}
const handleAddQuantity = () =>{
if(quantity)
setCount(count + parseInt(quantity, 10));
}
Important: Since input fields in HTML can't retrieve its value as a number (not even when the input type is set to number, it always takes a string as a value), it's necessary to parse it to integer before using it. Otherwise, it will be concatenated to the current count.
Also note that we're adding a condition to make sure no empty value is added or subtracted, which would result in a NaN.
3. Reseting the counter
Since we want our counter to be the most functional possible, we're going to add a button to reset the counter to zero. Its event handler will reference a function that will just set count and quantity state values to zero.
<button onClick={handleResetCounter}>Reset counter</button>
const handleResetCounter = () => {
setCount(0);
setQuantity(0);
}
And that's it! Our counter is now ready to use.
Check the full implementation here:
https://codesandbox.io/s/beautiful-mahavira-r1idm
Example 2: Using useReducer
Now, we're going to create the exact same counter but, this time, its functionality will be implemented using React's useReducer hook.
Importing useReducer
As we did in the other example, we need to import the hook we're going to work with.
import React, {useReducer} from 'react';
Setting up the state
For this new case, we're going to set up the state in a slightly different way: we'll specify an initial state and also a reducer function that will take care of all the functionality, as it's required by useReducer. Both the initial state and the function will be taken as parameters by this hook.
const initialState = {count: 0, quantity: 0};
const [state, dispatch] = useReducer(reducer, initialState);
useReducer returns the current state and a dispatch method.
The reducer function
The approach of this hook is to have a reducer function that accepts the state of the app (or component) and an action as parameters, and, based on that action, the state is managed one way or another.
So let's take a look at the reducer function that we'll be using:
const reducer = (state, action) => {
switch (action.type) {
case "addOne":
return {...state, count: state.count + 1};
case "subtractOne":
return {...state, count: state.count - 1};
case "addQuantity":
return {...state, count: state.count + parseInt(state.quantity, 10)};
case "subtractQuantity":
return {...state, count: state.count - parseInt(state.quantity, 10)};
case "setQuantity":
return {...state, quantity: action.payload};
case "resetCounter":
return initialState;
default:
throw new Error();
}
};
This function contains all the possible use cases that the counter is able to perform. If the action type passed in doesn't appear in the list of specified functions, an error will be thrown.
Important: Don't forget to spread the state every time you set any of its fields to keep the rest of the values as they are. Otherwise, your stored data will be overwritten with the returned value.
The handlers
We'll be using the same handlers, but now, they won't update the state values directly using a setter function. Instead, they will dispatch different actions that will return values to useReducer, which will handle them properly to update the state.
useReducer throws actions in the way Redux does, so, if you're familiarized with Redux, you'll find its behavior pretty much the same.
const handleSubtractOne = () => {
dispatch({type: "subtractOne"});
};
const handleAddOne = () => {
dispatch({type: "addOne"});
};
const handleSubtractQuantity = () => {
if (state.quantity)
dispatch({type: "subtractQuantity"});
};
const handleAddQuantity = () => {
if (state.quantity)
dispatch({type: "addQuantity"});
};
const handleResetCounter = () => {
dispatch({type: "resetCounter"});
};
const handleOnChange = (e) => {
dispatch({type: "setQuantity", payload: e.target.value});
};
And that's all.
Check the full implementation here:
https://codesandbox.io/s/quizzical-framework-3r2pp
I hope you find this tutorial useful and see you in the next.
🎉 Follow me on Instagram and Twitter for more related content: @underscorecode
Top comments (0)