How to Design and Create Custom Hooks in React

An easy tutorial for designing custom hooks in React

Joshua Tal
JavaScript in Plain English

--

In this article, we’re going to discuss how and when to use custom react hooks to support common side effects and, in turn, write more reliable code.

If you know about abstraction, that’s great, and sometimes not. Still, occasionally we need more than a simple abstraction to make code readable and reusable. Out of custom hooks, we seek to attain the advantages of abstraction combined with the fluidity and power of modern hooks. Before moving on, I recommend reading about built-in hooks like useState and useEffect in the React docs if you’re not yet familiar.

3 steps to building a custom hook

  1. organize. My favorite practice is to create a hooks directory at the top level of a project so that all custom hooks are easy to find. In fact, Visual Studio Code has a default icon when you create a folder named “hooks”. By my rules, that’s a good sign!
  2. name. I like to start hook names with “use” and then avoid giving hooks vague names like “use” and then represent its responsibility down to the point. A nice example might be something like “useToggle”. That’s pretty obvious!
  3. design hook! Building custom react hooks are actually really easy! Just be wary of what logic is placed in there and keep in mind if they require clean-up. Missing that point could lead to memory leaks!

Let’s go over a few examples below!

Easy custom hook examples

Most often we base our custom hooks on existing ones. Let’s start by creating a super simple custom hook named useToggle which utilizes useState as its backbone.

Example #1: useToggle — best for on/off switch type of behavior

Let’s go over this example line-by-line (be patient, there are only 5):

  1. First, we create a function named useToggle that takes a boolean as argument that acts as the hook’s initialState. If none is provided, it’ll default to false.
  2. Next, we use a standard expression derived from React’s built-in useState hook. This is what will generally be used to store and manipulate our switch. This expression will give us access to state — a boolean variable (in this case) that’ll persist across renders — and setState — a method, that when passed a boolean, will schedule a state update and correspondingly trigger a re-render.
  3. We then define a local toggle method that will read the current state and set the opposite one. In other words, if the state is true, it will set it to false and vice-versa.
  4. Finally, an array is returned from our hook containing a reference to our toggle-able state ([0]) and to our toggle method ([1]). That’s all there is to it. Implement it in your component and go crazy! For an optimized version of this simple hook, refer here.
  5. Oh yeah…and there’s this “}”

We can use this code to make something simple but flashy like the following light switch (code below):

Example #2: useFocusToggle — for handling when a react-navigation screen is focused or blurred

To imagine a scenario a scenario where this might be useful, picture this: my team found a bug such that when we tried navigating to a certain screen, the app would crash. We soon realized that some complex component running in the background was causing this crash. Following some deeper investigation, I learned that when using react-navigation’s Bottom Tab Navigator, screens do not unmount after changing tabs. In other words, one solution was to figure out a way to unmount the problematic component when the screen loses focus. First, I tried utilizing react-navigation’s useFocusEffect but after that didn’t work for me — as I kept encountering an error and didn’t have time to dig deeper— I built the following hook:

This custom hook named useFocusToggle accepts 1 required and 2 optional arguments.

  1. navigation (required). This must be of some NavigationProp type belonging to some screen in a project using react-navigation
  2. focusEvents (optional). This should be a callback containing any tasks to be executed when the screen focuses.
  3. blurEvents (optional). This should be a callback containing any tasks to be executed when the screen leaves focus

Beginning on line 2, the hook establishes a switch-like state to represent whether the screen implementing the hook is focused or not.

Next, we leverage 2 separate useEffect hooks to be executed each time the navigation (prop #1) reference updates. Considering react-navigation’s behavior, this won’t usually occur more than a couple of times depending on your application’s architecture. In each hook, we define different but similar event listeners using react-navigation’s navigation.addEventListener method. One listener will turn the state to true and execute any callbacks passed in focusEvents (prop #2) when the screen focuses. And another listener will turn the state to false and execute any callbacks passed in blurEvents (prop #3) when the screen leaves focus.

Finally, we return the state representing whether the screen is focus or unfocused.

I understand that there may be some confusion as to the usefulness of this hook and in that case, let me establish that this hook is really only meant for top-level screen components utilizing react-navigation. If, in any case, useFocusToggle was called by a lower-level component, then it would need to have been passed a navigation prop by one of its ancestor screens.

Thinking back to our original dilemma, even though our tab screen will still remain mounted, this hook allows us to mount and unmount child components by using the focused state. This is a simple example of how it might look in action:

When not to use custom hooks (or hooks in general)

Suppose you’re building a checkout screen for a business and it needs to have all kinds of side effects. So, for example, when a user adds an item to the cart they’ll receive a 20% percent discount and free shipping if they spend over $50 (assuming the business is just getting started and likely to be super generous hoping to gain traction). Let’s look at a couple of ways to implement this:

Approach #1 with a custom hook:

Technically, we could create an unnecessary custom hook that accepts 3 arguments: a prop to “track”, a conditional expression that expects the prop as a parameter and returns a boolean value, and an initial state. Let’s look at how it’s implemented:

Every time the amountSpent prop changes, the useEffect from our useConditionalHook will fire and report whether the user is eligible or not for a discount.

Approach #2 no custom hook

Here’s why you DO NOT need this hook… useEffect and useState do not need to be used here. We should always remember that when props change, our component re-renders. Thus, most expressions (not hooked) are read and executed once again. To conclude, all we need is an expression, like the following, to replace our hook on line 4 in order to achieve the same results:

const eligibleForDiscount = amountSpent > 50

Voila! Every time amountSpent is changed by the parent, eligibleForDiscount will update precisely as we need it to. I think a nice trick to avoiding the former approach is to be wary when using props to initialize a local state.

I hope you learned something from this article! Please refer to the links here for further reading and please reach out to me if you found this article interesting, have any questions, or comments. Upvotes help and thanks for your support!

More content at plainenglish.io

--

--