How to use built-in or custom hooks in your React application

Estafet consultants occasionally produce short, practical tech notes designed to help the broader software development community and colleagues. 

If you would like to have a more detailed discussion about any of these areas and/or how Estafet can help your organisation and teams with best practices in improving your SDLC, you are very welcome to contact us at enquiries@estafet.com

Introduction

Hooks solve a wide variety of issues in React that were encountered over the years before their introduction. React follows the principle of component-based architecture. A component in React is a once defined, reusable piece of code. The components are of two types – class components and functional components. Hooks are new React APIs added to React 16.8. They enable React functional components to use React features that were previously available only in React class components. 

React Hooks therefore address several key challenges that developers face with class components. These included complex state management, difficulty in reusing logic, convoluted lifecycle methods, and issues with the `this` keyword, all of which often led to bloated and hard-to-maintain code. Hooks like `useState` and `useEffect` simplify state and side-effect management, while custom hooks enable easy reuse of logic across components. 

Most people know that functional components in React are just pure JavaScript functions. So, if functions have composability, React components can also have composability. This means we can create one component by using previously created ones. Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don’t work inside classes — they let you use React without classes. They allow for a cleaner, function-based approach that eliminates the need for class syntax, making React more intuitive, flexible, and accessible, especially for new developers.

Stateful or Stateless components

Components in React can be stateful or stateless. A stateful component declares and manages local state in itself. A stateless component is a pure JavaScript function that doesn’t have a local state and side-effects to manage. If we take out the state and side-effects logic from a functional component, we have a real stateless component. Also, we have to take it out in a way that stateful and side-effects logic can be reusable elsewhere in the app. So it makes sense to isolate this logic from a component.

The question is, what is this stateful logic? Could it be any variable that need to be declared and manage a state variable locally? The answer is yes, exactly! For example, the logic to fetch data and manage the data in a local variable is stateful. We may also want to reuse the fetching logic in multiple components.

React hooks have a few rules, such as:

  •  Hooks can only be called inside React function components
  •  Hooks can only be called at the top level of a component
  •  Hooks cannot be conditional

Some of the most popular built-in hooks are:

  • useState – this hook enables us to add state to functional components. Let’s see how to use it with a simple example:

useState returns an array with exactly two values:

  1. The current state. During the first render, it will match the initialState you have passed.
  2. The set function that lets you update the state to a different value and trigger a re-render.

(!) Calling the set function does not change the current state in the already executing code:

  • useEffect: this is a React Hook that lets you synchronise a component with an external system. Call useEffect at the top level of your component to declare an Effect:

useEffect is one of the most important built-in React hooks. useEffect(setup, dependencies?)

setup: The function with your Effect’s logic. Your setup function may also optionally return a cleanup function. When your component is added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. After your component is removed from the DOM, React will run your cleanup function.

optional dependencies: The list of all reactive values referenced inside of the setup code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison. If you omit this argument, your Effect will re-run after every re-render of the component. See the difference between passing an array of dependencies, an empty array, and no dependencies at all.

  • useCallback: this is a React Hook that lets you cache a function definition between re-renders. useCallback(fn, dependencies)

fn: The function value that you want to cache. It can take any arguments and return any values. React will return (not call!) your function back to you during the initial render. On next renders, React will give you the same function again if the dependencies have not changed since the last render. Otherwise, it will give you the function that you have passed during the current render, and store it in case it can be reused later. React will not call your function. The function is returned to you so you can decide when and whether to call it.

dependencies: The list of all reactive values referenced inside of the fn code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison algorithm.

  • Usage
  1. Skipping re-rendering of components
  2. Updating state from a memoized callback
  3. Preventing an Effect from firing too often
  4. Optimising a custom Hook
  • Troubleshooting
  1. Every time my component renders, useCallback returns a different function
  2. I need to call useCallback for each list item in a loop, but it’s not allowed

Skipping rerendering of the components is important, because of performance!

In this case “handleSubmit” will be different every time, because in JavaScript, a function () {} or () => {} always creates a different function, similar to how the {} object literal always creates a new object. Normally, this wouldn’t be a problem, but it means that ShippingForm props will never be the same, so rerendering of “ShippingForm” will happen every time.

  • useMemo: this is a React Hook that lets you cache the result of a calculation between re-renders. useMemo(calculateValue, dependencies)

calculateValue: The function calculating the value that you want to cache. It should be pure, should take no arguments, and should return a value of any type. React will call your function during the initial render. On next renders, React will return the same value again if the dependencies have not changed since the last render. Otherwise, it will call calculateValue, return its result, and store it so it can be reused later.

dependencies: The list of all reactive values referenced inside of the calculateValue code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like [dep1, dep2, dep3]. React will compare each dependency with its previous value using the Object.is comparison.

  • Usage
  1. Skipping expensive recalculations
  2. Skipping re-rendering of components
  3. Memoizing a dependency of another Hook
  4. Memoizing a function
  • Troubleshooting
  1. My calculation runs twice on every re-render
  2. My useMemo call is supposed to return an object, but returns undefined
  3. Every time my component renders, the calculation in useMemo re-runs
  4. I need to call useMemo for each list item in a loop, but it’s not allowed
  • useContext: this is a React Hook that lets you read and subscribe to context from your component. useContext(SomeContext)

SomeContext: The context that you’ve previously created with createContext. The context itself does not hold the information, it only represents the kind of information you can provide or read from components.

  • Usage
  1. Passing data deeply into the tree
  2. Updating data passed via context
  3. Specifying a fallback default value
  4. Overriding context for a part of the tree
  5. Optimising re-renders when passing objects and functions
  • Troubleshooting
  1. My component doesn’t see the value from my provider
  2. I am always getting undefined from my context although the default value is different

Writing custom hooks

Custom Hooks in React are a powerful feature that allows us to extract component logic into reusable functions. These Hooks are JavaScript functions that can use other Hooks provided by React. They enable us to organise logic into separate, reusable modules.

Custom Hooks start with “use”. for example: “useLoad”

Let’s start by creating a simple custom Hook. We’ll build a useCounter hook that encapsulates the logic for incrementing a counter.

To use our useCounter Hook, we can include it in a functional component:

Tips for Building Custom Hooks

  • Start by identifying repetitive logic across your components.
  • Extract this logic into a function named with use prefix. Custom hooks should start with use, like useFormInput or useFetch.
  • Use React’s built-in Hooks within your custom Hook as needed.
  • Return anything that will be useful for the component using this Hook.
  • Each custom hook should be responsible for a single piece of functionality.

Sharing Logic with Custom Hooks

Custom Hooks are excellent for sharing logic across multiple components. In this section, we will create two common custom hooks that React developers often create to share logic across multiple components.

Conclusion

React Hooks have changed the way we write React applications, providing a more elegant and functional approach to handling state and side effects. With the useState, useEffect, useContext, useCallback, and custom hooks at our disposal, functional components can now match and even surpass the capabilities of class components. Understanding the nuances of React Hooks opens up a world of possibilities for creating cleaner, more efficient, and scalable React applications.

As you dive into React Hooks, remember that continuous learning and exploration are key to staying up-to-date with the evolving React ecosystem.

By Valeri Kotsev, Consultant, Estafet

Stay Informed with Our Newsletter!

Get the latest news, exclusive articles, and updates delivered to your inbox.