React Hooks For Beginners

Roman Sypchenko
12 min readJan 12, 2023

--

Photo by Clément Hélardot on Unsplash

Introduction

React hooks are a new feature in React 16.8 that allows developers to use state and other React features in functional components. Prior to hooks, this was only possible in class components. Hooks make it easier to share stateful logic across multiple components and to build reusable logic.

One of the main use cases for writing custom hooks is to extract stateful logic from a component and make it reusable across multiple components. This can help to make your code more organized, and easier to test and understand, and it can also make it easier to share logic across different parts of your application. Additionally, Custom hooks also allow for flexibility by abstracting away the implementation and only exposing what’s necessary for consumption, allowing for better reusability of code as well.

React hooks allow developers to use state and other React features in functional components. Before hooks, this functionality was only possible in class components. Hooks provide a way for functional components to manage state and side effects, making it easy to share logic between different components.

Hooks are useful because they make sharing logic across multiple components easy, making your code more organized and easier to understand. Additionally, hooks also make it easier to test and reuse code, which can help to improve the overall maintainability of your application. They also help to avoid the problem of Class component’s this keyword usage and its context, which sometimes leads to confusion. By using hooks, you can be sure that your component logic is consistent and can be reused throughout your application, regardless of whether it's a functional component or a class component.

A common use case for writing custom hooks is to extract stateful logic from a component and make it reusable across multiple components. This can help to make your code more organized, easier to test and understand, and can also make it easier to share logic across different parts of your application.

Custom hooks allow you to extract logic that might be used in multiple components, such as managing a form, making an API call, or handling a specific type of user interaction, and encapsulate it into a single, reusable hook. It also allows to abstract the implementation details and expose only what’s necessary for other components to consume. Additionally, Custom hooks also allows for flexibility, for example, you can create a hook for an API call and you can use it with different endpoints or payloads depending on where you use it in your application.

In short, custom hooks are a powerful way to manage state and side effects and make your code more modular, reusable, and easier to test and understand.

Understanding Hooks

There are a few basic rules that must be followed when using React hooks:

  1. Hooks must be called at the top level of your component or your own Hooks. They cannot be called inside loops or conditions. This helps React to determine which component the hook belongs to correctly.
  2. Hooks should only be called from React functional components or from other hooks. They cannot be called from class components or from regular JavaScript functions.
  3. Only call Hooks at the top level of your React functions. Don’t call Hooks inside loops, conditions, or nested functions. This is how React knows which state corresponds to which component.
  4. React guarantees the order in which Hooks are called is the same between renders. So you don’t need to rely on any complex mechanisms like keys to keep track of the state.
  5. Custom Hooks should follow the naming convention of prefixing use, which gives an indication that it is hook and it will be used in some component.

By following these basic rules, you will be able to use React hooks effectively and avoid any potential issues that could arise from misusing them.

React hooks allow developers to share stateful logic across multiple components by making it possible to use state and other React features in functional components. Prior to hooks, this was only possible in class components.

When you use a hook, you’re giving a functional component the ability to manage state and perform side effects, without having to convert it to a class component. This means that stateful logic that was previously only available to class components can now be shared across functional components as well.

One example of this is the useState hook, which allows you to manage the state of a component, even in a functional component. Another example is useEffect, which allows you to perform side effects, such as fetching data or subscribing to an event, in a functional component.

Additionally, custom hooks allow developers to reuse common logic across the different parts of their application, rather than rewriting the same code over and over in multiple components. It enables reusability, abstraction, and separation of concerns.

In short, React hooks provide a way for developers to share stateful logic across multiple components, making it possible to reuse logic in different parts of the application, making the code more maintainable, readable, and efficient.

Creating a Custom Hook

Here are the basic steps for creating a custom hook:

  1. Create a new file in your project and give it a descriptive name, prefixing “use” which gives the indication that it’s a hook. For example, if your hook manages a form, you might call it “useForm.js”.
  2. Inside the file, create a new function that will serve as your custom hook. The function should begin with the word “use” as it will indicate to React that it is a hook. For example, useForm or useAPI.
  3. Inside the function, you can use any of the built-in hooks such as useState, useEffect, useContext, etc. to manage state, perform side effects, or access context.
  4. You can also define any additional variables or functions that you need to implement the logic for your hook.
  5. Return an object or an array that contains any state variables or functions that your hook manages, so that they can be used in the component that calls your custom hook.
  6. Import the custom hook you created in the components where you want to use it.
  7. Call the custom hook from a functional component, the same way you would call a built-in React hook, at the top level of the component’s body.

Here is an example of a custom hook that manages the state of a form:

import { useState } from 'react';

export default function useForm(initialValues) {
const [form, setForm] = useState(initialValues);

function handleChange(event) {
const { name, value } = event.target;
setForm({ ...form, [name]: value });
}

function resetForm() {
setForm(initialValues);
}

return { form, handleChange, resetForm };
}

This hook exports an object with 3 properties: form, handleChange, and resetForm. The component that uses this hook can then use these properties to manage the form's state and behavior.

Note: Remember that custom hooks should be simple, focused, and testable. They should not contain any UI or component-specific logic.

In practice, you can also use other hooks inside of custom hooks, and use custom hooks inside other custom hooks, that allows for more advanced use cases and better code reuse.

Here’s an example of a custom hook called useFetch that can be used to handle the process of making an API call in a functional component:

import { useState, useEffect } from 'react';

export default function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
const json = await response.json();
setData(json);
setLoading(false);
} catch (err) {
setError(err);
setLoading(false);
}
}
fetchData();
}, [url]);

return { data, loading, error };
}

In this example, we define a custom hook useFetch that accepts a url as an argument, and it uses React built-in hooks useState and useEffect to manage the state of the request and perform the API call. We define 3 state variables data, loading, and error, and set them using useState. Then we use useEffect hook, that is called only once at the component mount, to make the actual API call. Here we use fetch() to make the API call, and when the response comes back we parse it as json and set data state variable.

Here’s an example of how the useFetch hook could be used in a functional component:

import useFetch from './useFetch';

function DataList() {
const { data, loading, error } = useFetch('https://api.example.com/data');

if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;

return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}

In this component, we import the useFetch hook and call it at the top level of the component body, passing in the URL for the API endpoint we want to fetch data from. The hook returns an object with three properties: data, loading, and error. We destructure this object and use these properties to render the component accordingly.

By using this custom hook, you can abstract away the logic and state management of making an API call, and easily reuse it across different components in your application. You can use this pattern to build multiple custom hooks and use them in your application to handle different state and logic, making your codebase more maintainable and readable.

Testing custom hooks is a bit different than testing regular components. Because custom hooks are JavaScript functions, they can be tested in isolation, without having to render any components.

Here are some general guidelines for testing a custom hook:

  1. Create a test file for your custom hook in the same location where the hook itself is located.
  2. Import the custom hook that you want to test, and any other dependencies it has, such as React or other hooks.
  3. Test the custom hook by calling it with different arguments and making assertions about the returned values.
  4. Test the side effects of the custom hook, like any network calls or changes made to the DOM, by using test utilities and mocking the dependencies.

Here’s an example of a test for the useFetch hook from the previous example:

import { renderHook } from '@testing-library/react-hooks';
import useFetch from './useFetch';

describe('useFetch', () => {
it('should return data, loading, and error', () => {
const { result } = renderHook(() => useFetch('https://api.example.com/data'));
expect(result.current).toHaveProperty('data');
expect(result.current).toHaveProperty('loading');
expect(result.current).toHaveProperty('error');
});
});

In this test, we’re using the renderHook utility from the @testing-library/react-hooks package, which allows us to render the hook as if it were being called in a component. We're passing an anonymous function that calls the useFetch hook as its argument. Then we are checking if returned value has properties data, loading, and error

If your custom hook is calling another API, you might want to mock the API calls to prevent making real network requests and to isolate the custom hook being tested. You can use libraries like jest or sinon for this and other purposes like spy or stub the functionality.

In general testing custom hooks is pretty similar to testing regular javascript functions, you pass inputs and you assert on the output, in this case, the returned value from the hook.

When you have tested your custom hooks, you can use them in any functional component in your React application, just like you would use any built-in React hooks. By abstracting state and logic, you can make your application more maintainable and readable, because the custom hooks will be responsible for handling specific logic and state management that can be reused across different components.

Best Practices

Here are some best practices to keep in mind when writing custom hooks:

  1. Keep hooks simple and focused. A custom hook should handle one specific piece of logic, such as managing a form or making an API call. Avoid writing complex hooks that handle multiple pieces of logic.
  2. Keep the number of state variables and side effects to a minimum. The more state a hook manages, the more difficult it is to test and understand.
  3. Use descriptive and meaningful names for your hooks. Make sure that the name of the hook reflects what it does, and that it’s easy to understand what the hook is used for.
  4. Make sure your hooks are testable. Write unit tests for your hooks and test them in isolation.
  5. Follow the React Hooks rules, hooks should only be called at the top level of your component or your own hooks, not inside loops or conditions.
  6. Don’t use hooks inside loops or conditions, hooks must be called on the top level of your component, this will help React to keep track of the state and the component associated with it.
  7. Use useEffect to handle the side-effects that the hook will create, like network calls or subscriptions
  8. Use dependency array in useEffect to avoid unnecessary re-render and performance issues
  9. Share logic with Custom Hooks, avoid duplicating logic across different components.
  10. Keep your hooks stateless, they should not contain any UI-related logic, only state management and logic.

By following these best practices, you will be able to create custom hooks that are easy to understand, test, and use, and that make your codebase more maintainable, readable and performant.

Here are some things to avoid when working with hooks:

  1. Do not call hooks inside loops or conditions. Hooks must be called at the top level of your component or your own hooks. This will help React to correctly determine which component the hook belongs to.
  2. Do not call hooks from class components. Hooks can only be called from functional components or from other hooks.
  3. Do not call hooks from regular JavaScript functions. React hooks must only be called from React functional components or other hooks.
  4. Do not mutate state variables directly. Always use the setter function returned by the useState hook to update state variables.
  5. Do not use the setState method of class components. When using hooks, use the useState hook to manage state instead.
  6. Do not call hooks inside conditions like if statement or inside ternary operators, hooks should be called at the top level of your component.
  7. Do not share state between hooks or between components, state should be local to a component.
  8. Do not use state from other components to update state in your component. Instead, pass the data as props and use the useState hook to manage local state.
  9. Do not use the state from other hooks to update the state in your hook, this will create a circular dependency, which can lead to unexpected behavior.

By avoiding these pitfalls, you’ll be able to use hooks effectively and prevent potential issues that could arise from misusing them. It is also important to keep in mind that hooks are not a silver bullet, it doesn’t solve all the problems, there still might be scenarios where class-based components could be more appropriate, depending on the complexity of the functionality.

Conclusion

React hooks are a new feature that allows developers to use state and other React features in functional components. Prior to hooks, this functionality was only possible in class components. Hooks provide a way for functional components to manage state and side effects, making it easy to share logic between different components. Custom hooks allow to extract logic that might be used in multiple components, abstract the implementation details, and expose only what’s necessary for other components to consume. When creating custom hooks it is important to follow the basic rules of hooks and avoid some pitfalls like calling hooks inside loops or conditions. It is also important to keep the hooks simple and focused, testable and have descriptive names. It is also important to keep in mind that hooks are not a silver bullet, it doesn’t solve all the problems, there still might be scenarios where class-based components could be more appropriate, depending on the complexity of the functionality.

Here are some additional resources for further reading and learning about React hooks:

In addition to these resources, it’s always a good idea to keep an eye on updates and new features related to React, as well as staying up-to-date with best practices, patterns and anti-patterns related to hooks and React development in general. The React community is active and always improving, so it’s important to follow the latest developments to stay on top of the technology.

Here are some additional tips and tricks for optimizing custom hooks:

  1. Memoize values returned by hooks to avoid unnecessary re-renders. You can use the useMemo hook to memoize the value of a hook, so that it only gets recomputed when one of its dependencies changes.
  2. Use the useCallback hook to memoize callback functions. This can be useful when passing callbacks as props to child components, to avoid unnecessary re-renders.
  3. Use the useEffect hook with a proper dependency array to avoid unnecessary re-renders and optimize performance.
  4. Avoid using unnecessary state variables in your hooks. Keep the number of state variables to a minimum and only use what is necessary.
  5. Don’t mix state updates from different hooks or components. Keep state updates as localized as possible, this will avoid race conditions and unexpected behavior.
  6. Use an eslint plugin for Hooks like eslint-plugin-react-hooks this will provide additional linting rules to keep your hooks code consistent and avoid common mistakes
  7. When possible, try to use hooks and libraries built by the React team and community, they are battle-tested and have a good performance.
  8. Use React dev tools to measure performance and check what’s happening in your application, it will give you a better understanding of your app’s performance, and identify potential bottlenecks.

By following these tips and tricks, you will be able to optimize the performance of your custom hooks and make your application run smoother and faster. However, keep in mind that performance optimization should not come at the cost of readability, maintainability and good practices, always prioritize them over performance optimization.

--

--