Custom Hooks | React.js
Tags: react, tutorial, typescript, frontend
Published: 2024-03-21 00:00:36

## Custom Hooks what do they eat, how are they born, let's find out?


## Summary

In this article, I will discuss a bit about the usefulness of Custom Hooks in React.js, how and when to create them. Additionally, some examples of their applications will be provided.

## Introduction

Custom Hooks are helper functions that can only be used within React components, they work as a way to share logic among our components, allowing us to 'trim down' some of our code, making it cleaner and easier to maintain.

## How do they work exactly?

When we think about making code cleaner, most of the time, we are looking for a way to reuse logic that has already been written, such as state initialization for our form and its various functions.

### Presenting the problem:

Let's suppose we want an application where we have some items in a list, and we want to be able to change their quantities. We can write a sketch as follows, below is a component that renders a list with the names of our items and their quantities:

```tsx

function ItemsList() {
	const bananas = 0;
	const fishes = 0;
	return (
		<ul>
			<li>`Bananas: ${ bananas }`</li>
			<li>`Fishes: ${ fishes }`</li>
		</ul>
	);
}

export default ItemsList;
```

Okay, but this component is not yet dynamic. We have a pretty easy way to solve this by simply using a native hook from React.js, `useState`, which returns an array where the first element is the value and the second is the function to overwrite it, `[value, setValue]`.

```tsx

function ItemsList() {
	const [bananas, setBananas] = useState<number>(0);
	const [fishes, setFishes] = useState<number>(0);
	return (
		<ul>
			<li>`Bananas: ${ bananas }`</li>
			<li>`Fishes: ${ fishes }`</li>
		</ul>
	);
}

export default ItemsList;
```

Great, now we can start manipulating the values of our items through their states. And for that, we need some helper functions and buttons.

```tsx

function ItemsList() {
	const [bananas, setBananas] = useState<number>(0);
	const [fishes, setFishes] = useState<number>(0);
	
	const decreaseBananas = () => setBananas((prev) => prev - 1);
	const increaseBananas = () => setBananas((prev) => prev + 1);
	
	const decreaseFishes = () => setFishes((prev) => prev - 1);
	const increaseFishes = () => setFishes((prev) => prev + 1);

	return (
		<ul>
			<li>
				Bananas:
				<button onClick={ decreaseBananas }>-</button>
				<span>{ bananas }</span>
				<button onClick={ increaseBananas }>+</button>
			</li>
			<li>
			Fishes:
				<button onClick={ decreaseFishes }>-</button>
				<span>{ fishes }</span>
				<button onClick={ increaseFishes }>+</button>
			</li>
		</ul>
	);
}

export default ItemsList;
```

I know this code looks messy, but it's for didactic purposes. Maybe you can already see the direction our code is taking, and surely you could do something cleaner and more interesting without the need of custom hooks.

Still, what's the big problem with this approach? Well, for each item we add, our code gains 3 more lines, which would be identical if it weren't for the different names. Also, for example, if now we needed to prevent the number from being negative like -1 or -2, we would have to change it in several lines.

That's exactly where we're going to recycle this logic with the help of custom hooks.

## Solving the problem:

Before we build our custom hook, I'll point out a few details:

- By convention, every hook should start with the lowercase word `use`;
- Custom hooks are functions that contain other hooks inside;
- Hooks should return some useful values, such as an initialized state and/or functions that alter it;

Now that we have that in mind, let's start building it.

```tsx
function useItemCounter() {
	// initializing our state
	const [quantity, setQuantity] = useState<number>(0);
	
	// Our logic to increment the value by 1
	const increase = () => {
	  setQuantity((prev) => prev + 1);
	};
	
	// Our logic to decrement the value by 1
	const decrease = () => {
	  if(quantity - 1 < 0) return;
	  setQuantity((prev) => prev -1);
	};

	return [quantity, increase, decrease];
}
```

Now we're talking! This custom hook gives us control over all the actions and information we need. Let me explain step by step.

First, we initialize a state that will be responsible for storing our value and triggering the component re-render every time it's updated.

Then, we create functions that will work as specific actions to control our state.

Finally, we return the values we need; in this case, the two functions that control the state and the state itself.

Now we just need to use it in our example

```tsx

function ItemsList() {
	const [bananas, increaseBananas,
	decreaseBananas] = useItemCounter();
	
	const [fishes, increaseFishes,
	decreaseFishes] = useItemCounter();

	return (
		<ul>
			<li>
				Bananas:
				<button onClick={ decreaseBananas }>-</button>
				<span>{ bananas }</span>
				<button onClick={ increaseBananas }>+</button>
			</li>
			<li>
			Fishes:
				<button onClick={ decreaseFishes }>-</button>
				<span>{ fishes }</span>
				<button onClick={ increaseFishes }>+</button>
			</li>
		</ul>
	);
}

export default ItemsList;
```

## Applying in the real world

Now that we're familiar with hooks, what they eat, and how they're born. Let's see some of the most common ones!

### useLocalStorage

```tsx

function useLocalStorage<T>(key: string, initialValue: T) {
	const readValue = () => {
	if(window === undefined) return initialValue;
	const savedValue = JSON.parse(localStorage.getItem(key));
	return savedValue || initialValue;
	}
	
	const [value, setValue] = useState<T>(readValue());
	
	useEffect(() => {
		localStorage.setItem(key, JSON.stringify(value))
	}, [value]);
	
	return [value, setValue];
}
```

This hook works very similarly to a `useState`, since it simply returns the value and the function to overwrite it, with the small difference that if there's a value saved in the local storage key, it will be loaded on state initialization, and every time it's updated, the new value is saved in the local storage.

### useFetch

```tsx
function useFetch(url: string) {
	const [loading, setLoading] = useState<boolean>(true);
	const [error, setError] = useState<string>('');
	const [data, setData] = useState<any>();
	

	
	useEffect(() => {
		const fetchData = async () => {
			try {
				setLoading(true);
				setError('');
				const response = await fetch(url);
				const returnedData = await response.json();
				setData(returnedData);
			}
			catch(err) {
				if(err instanceof Error) {
					setError(err?.message ?? 'Error fetching data');
				}
			}
			finally {
				setLoading(false);
			}
		};
		fetchData();
	}, []);
	
	return [data, loading, error]
}
```

The useFetch is a way to perform that API fetch which often makes the code lengthy, and it already returns the loading status, error, and the data returned by the API.

### useForm

```tsx
function useForm<T>(initialForm: T) {
	const [form, setForm] = useState<T>(initialForm);
	
	const handleChange = ({ target }) => {
		setForm({ ...form, [target.name]: target.value});
	}
	
	const resetForm = () => setForm(initialForm);
	
	return [form, handleChange, resetForm];
}
```
Better than creating a state for each form element is to unify them all into a small hook. Just pass an object as a parameter and your form control is done.
```tsx
function LoginForm() {
	const initialForm = {email: '', password: ''};
	const [form, handleChange, resetForm] = useForm(initialForm);
	
	return (
		<form>
			<input
				type="text"
				name="email"
				value={ form.email }
				onChange={ (e) => handleChange(e) }
			/>
			<input
				type="password"
				name="password"
				value={ form.password }
				onChange={ (e) => handleChange(e) }
			/>
		</form>
	)
}
```
## Conclusion

Custom hooks are a design pattern in React.js that allow us to share logic between components, making the code cleaner and more reusable.