If you’re not new to React, you probably know about or have used hooks like useState and useEffect before. However, do you know that you can create your own hook? Yes, you heard that right! And this is what this article is about.
Also, if you’re a newbie to JavaScript library, I’ve got you covered! I’ll bring you up to speed on the existing React hooks, and help you understand how to create yours. Let’s dive right in.
WHAT ARE REACT HOOKS?
The React library is commonly used because it’s easy to handle. One of this library’s excellent functionalities is React hooks. In simple terms, React hooks are JavaScript functions that allow you to access state and other React features without writing a class.
These functions can also used to isolate the reusable parts of functional components. Additionally, they are identified by the word use, followed by the superpowers they possess like DebugValue, Effect, Callback, and LayoutEffect.
THE EXISTING REACT HOOKS
The latest React version (version 18) has 15 built-in hooks that you can use. The most commonly used hooks are useState, useEffect, and useContext. Here is a list and a summary of all the existing React hooks:
- useCallback: Returns a memoized (stored to avoid repeated computation) callback function so that the child component that depends on the function will not re-render unnecessarily. The function will only be recreated if one of its dependencies changes.
- useContext: After creating a context, useContext allows you to use a value in the context further down in your functional components. So, you don’t have to manually pass props down your component tree.
- useDebugValue: Helps you label the output of your custom hooks so you can easily understand their state and monitor their behaviour in React DevTools.
- useDeferredValue: Useful for prioritizing the responsiveness of your user interface by deferring long-running operations that might affect the performance of your application.
- useEffect: This hook handles side effects in your functional components. Side effects include fetching data, setting up event listeners, and DOM manipulation.
- useId: Helps you generate unique IDs across your React application.
- useImperativeHandle: Allows you to specify the properties of a component that should be exposed when using refs.
- useInsertionEffect: This makes it easy for you to execute a function after a component has been added to the DOM.
- useLayoutEffect: It works similarly to useEffect, but it’s synchronous. You can use it to make changes on your DOM immediately when it’s updated, and before the browser displays content on a user’s screen.
- useMemo: It is used to memoize the result of expensive computations to avoid unnecessary recalculations.
- useReducer: Instead of using useState you can use useReducer to handle more complex state logic within a functional component.
- useRef: Helps you create mutable references that you can access across multiple renders of a functional component.
- useState: Allows you to manage the state within a functional component.
- useSyncExternalStore: This hook allows you to read and subscribe to an external data store.
- useTransition: Helps you manage asynchronous updates to a React application’s UI.
WHY DO WE NEED CUSTOM HOOKS?
Don’t get me wrong, this article is not to say that the in-build React hooks are not sufficient. React has all these powerful hooks that will serve you well. Nonetheless, I can’t deny the reality that custom hooks can greatly improve the readability and overall quality of your code.
Let’s open up this fact a little bit by highlighting why you might need a custom hook:
- It makes logic reusable.
- It allows you to use other hooks provided by React.
- You can easily separate your logic from your UI.
- You can break down complex stateful logic into simple chunks of code that are easy to maintain.
- You can test specific parts of your stateful logic because custom hooks can be debugged in isolation.
TIPS FOR CREATING CUSTOM REACT HOOKS
Here are some tips to keep in mind to ensure your custom hooks are flexible, reusable, and easy to understand.
- Follow the naming convention by starting with use.
- Repeated/reusable logic within your components should be your custom hooks candidates.
- Feel free to use the built-in React hooks where necessary.
- Make sure a hook is focused on one responsibility instead of multiple functions.
- Your hooks should have value or functions that components can use.
- Ensure your hooks can accept parameters so you can easily customize their behaviour.
- Test your hooks in different scenarios to ensure they are performing as expected. You can use tools like Jest and/or the React Testing Library.
- Document your hook and succinctly explain its function.
CREATING YOUR FIRST CUSTOM HOOK
Step 1: Define Your Hook
Let’s create a custom React hook that would allow our application toggle between light and dark mode (our reusable logic). To begin, we create a JavaScript file to define our hook.
import { useState, useEffect } from 'react';
function useDarkMode() {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
const body = document.body;
if (isDarkMode) {
body.classList.add('dark-mode');
} else {
body.classList.remove('dark-mode');
}
}, [isDarkMode]);
const toggleDarkMode = () => {
setIsDarkMode(prevMode => !prevMode);
};
return [isDarkMode, toggleDarkMode]:
}
export default useDarkMode;
Step 2: Use Your Custom Hook
Now, we can use our custom hook in our React component.
import React from 'react';
import useDarkMode from './useDarkMode';
function App() {
const [isDarkMode, toggleDarkMode] = useDarkMode();
return (
<div className="App">
<button onClick={toggleDarkMode}>
Toggle Dark Mode
</button>
<div className={isDarkMode ? 'dark-mode' : 'light-mode'}>
{isDarkMode ? 'Dark Mode' : 'Light Mode'}
</div>
</div>
);
}
export default App;
Step 3: Test Your Hook
To carry out the test, you’ll have to install the Jest and the React Testing Library using:
npm install --save-dev @testing-library/react @testing-library/jest-dom jest
Then, we’ll create a test file for our custom hook. Let’s call it useDarkMode.test.js.
We’ll proceed to use the renderHook and act utilities in our React Testing Library to test our hook.
import { renderHook, act } from '@testing-library/react-hooks';
import useDarkMode from './useDarkMode';
describe('useDarkMode', () => {
test('should toggle dark mode correctly', () => {
const { result } = renderHook(() => useDarkMode());
// Initial state should be light mode (isDarkMode: false)
expect(result.current[0]).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(false);
// Toggle to dark mode
act(() => {
result.current[1](); // toggleDarkMode
});
expect(result.current[0]).toBe(true);
expect(document.body.classList.contains('dark-mode')).toBe(true);
// Toggle back to light mode
act(() => {
result.current[1](); // toggleDarkMode
});
expect(result.current[0]).toBe(false);
expect(document.body.classList.contains('dark-mode')).toBe(false);
});
});
Next, add Jest to your package.json file as follows, then run npm test to confirm that the useDarkMode hook is running as expected:
"scripts": {
"test": "jest"
}
Step 4: Document Your Custom Hook
Document how your custom hook works and include details like its parameters and return values.
CREATING A DATA FETCHING CUSTOM HOOK
Now that you have an idea of how to create a custom React hook, let’s build another common custom hook (a data fetching hook) to further reinforce our knowledge. Shall we?
Step 1: Create the hook.
We’ll call it useFetch.js. This custom hook is commonly used to allow several components to fetch data from an API.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
setData(data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Step 2: Use your new hook in a component.
import React from 'react';
import useFetch from './useFetch';
function App() {
const { data, loading, error } = useFetch('https://api.google.com/data');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Data:</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
export default App;
Step 3: Test and document your custom hook.
import { renderHook } from '@testing-library/react-hooks';
import useFetch from './useFetch';
// Mock the fetch function
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ data: 'mocked data' }),
})
);
describe('useFetch', () => {
afterEach(() => {
fetch.mockClear();
});
it('should return data after fetch', async () => {
const { result, waitForNextUpdate } = renderHook(() => useFetch('https://api.example.com/data'));
expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.data).toEqual({ data: 'mocked data' });
expect(result.current.error).toBe(null);
});
it('should return an error if fetch fails', async () => {
fetch.mockImplementationOnce(() =>
Promise.reject(new Error('Fetch failed'))
);
const { result, waitForNextUpdate } = renderHook(() => useFetch('https://api.example.com/data'));
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe('Fetch failed');
expect(result.current.data).toBe(null);
});
});
GET STARTED TODAY
By leveraging the power of custom hooks, you’ll be able to write simple, maintainable and readable codes. And trust me, once you get started, you are locked in by the ease these functions bring to your programming journey.