Codementor Events

How I create my own React Hooks (and why you should, too!)

Published May 12, 2022Last updated Nov 07, 2022
How I create my own React Hooks (and why you should, too!)

Intro

Hello Codementor! In this post, I will demonstrate how to create custom React Hooks to use in your React projects. Custom hooks increase code reusability, modularity and readability. I am going to be using React Context for state management and will be coding the example in Typescript, but if you use regular javascript, you can code along too. Just don't add in the typings. Let's go!

What are React Hooks?

React Hooks, from version 16.8 onwards, are functions that allow you to "hook" into the features of React, without having to write a class-based component. For example, the useState() hook allows you to create a local state variable without having to use a React Component with a state object:

const [name, setName] = useState("Anonymous");
return (
<button onClick={()=>setName("Sean")}>Hello {name}!</button>
)

When the button is clicked, the component will rerender because the state of name will change.
The most common two hooks are useState and useEffect. useEffect is equivalent of componentDidUpdate in class-based components, but can be modified to fire off only on specific changes in state, and not on every rerender.
React hooks can only be used within a React functional component. Trying to call useEffect, for instance, outside a functional component, will throw you nasty errors.
There is one exception: A custom hook. When creating a custom hook, you can use all the functionality of react. You can use the library's built-in hooks, and even your own custom hooks. You don't have to return JSX at all, since it's not a functional component.
NB: a React custom hook's variable name MUST begin with use. Let's go through an example below. I will be creating a Global State for a "Toast" alert which will pop up on certain user input, remain for a little bit and then disappear.

Basic Setup

First off, let's create a new app:
On your terminal,type:
npx create-react-app ts-test --template typescript
Assuming you have Nodejs installed on your machine, the terminal will do the rest. You will see a new folder ts-test. Let's open it up in our code editor (Visual Studio Code recommended!)
I like working with Styled Components, so let's install it
npm i styled-components @types/styled-components
I like to do a cleanup of the src folder, so delete everything but App.tsx, index.css and index.tsx.
First off, I like to change the index.css file to this:

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

Then, let's create a styles.ts file in src:

import styled from 'styled-components';

const Container = styled.div`
  width: 100vw;
  height: 100vh;
  text-align: center;
  background: lightblue;
  padding: 1rem;
`;

export { Container };

And here's how our App.tsx will look for now:

import React from 'react';
import { ToastProvider } from './context';
import { Container } from './styles';

function App() {
  return (
    <ToastProvider>
      <Container>
        <h1>MY APP!</h1>
      </Container>
    </ToastProvider>
  );
}

export default App;

As you see there, I have created a context for a Toast message that appears at the bottom of the screen. So create a context folder, and inside that, index.ts:

export * from './Toast';

This assumes a Toast folder, so let's create it, then in that folder create an index.tsx and a styles.ts. The code for index.tsx:

import React, { createContext, ReactNode, useEffect, useState } from 'react';
import { Container } from './styles';

interface ChildrenProps {
  children: ReactNode;
}

interface ToastSchema {
  type: 'WARNING' | 'SUCCESS';
  message: string;
}

const ToastContext = createContext<
  React.Dispatch<React.SetStateAction<ToastSchema | undefined>>
>(() => {});

const ToastProvider: React.FC<ChildrenProps> = ({ children }) => {
  const [toast, setToast] = useState<ToastSchema | undefined>();
  useEffect(() => {
    if (toast) {
      const timeoutId = setTimeout(() => setToast(undefined), 3000);
      return () => {
        clearTimeout(timeoutId);
      };
    }
  }, [toast]);
  return (
    <ToastContext.Provider value={setToast}>
      {children}
      {toast && <Container type={toast.type}>{toast.message}</Container>}
    </ToastContext.Provider>
  );
};

export { ToastProvider, ToastContext };

and for styles.ts:

import styled from 'styled-components';

const Container = styled.div<{ type: 'WARNING' | 'SUCCESS' }>`
  position: fixed;
  left: 50%;
  bottom: 10%;
  transform: translate(-50%, -50%);
  background: ${({ type }) => (type === 'SUCCESS' ? 'green' : 'red')};
  color: white;
  font-weight: bold;
  padding: 0.5rem;
  min-width: 200px;
  text-align: center;
  border-radius: 5px;
`;

export { Container };

Let's see what's going on in the Toast component there. I am creating a context which accepts a React state action as a value. I am then wrapping essentially my entire app in this context and thus exposing the entire app to the setToast value there. the useEffect will check if there's a toast, and then kick off a timer that will remove the toast after 3 seconds. Let's see if it works. Back in the src folder, create a file called Component.tsx:

import React, { useContext } from 'react';
import { ToastContext } from './context';

const Component = () => {
  const setToast = useContext(ToastContext);
  return (
    <div>
      <h1>MY COMPONENT!</h1>
      <button
        onClick={() => setToast({ type: 'SUCCESS', message: 'SUCCESS!' })}
      >
        Make a Successful Toast!
      </button>
      <button onClick={() => setToast({ type: 'WARNING', message: 'UH OH!!' })}>
        Make an Error Toast!
      </button>
    </div>
  );
};

export default Component;

and let's update App.tsx

import React from 'react';
import Component from './Component';
import { ToastProvider } from './context';
import { Container } from './styles';

function App() {
  return (
    <ToastProvider>
      <Container>
        <Component />
      </Container>
    </ToastProvider>
  );
}

export default App;

Your folder structure should be as such:
Screenshot 2022-05-12 at 15.08.17.png
If you run npm run start, you should get this:
Screenshot 2022-05-12 at 14.35.34.png
And that toast should be popping up depending on the button you pressed, and disappearing after 3 seconds.
Great!

The Value of a Custom Hook

Looking at the code in Component.tsx, there's some troubling stuff. It seems every time I want to display an alert, I need to import useContext, ToastContext, declare the setToast variable and set the type and message properties for each instance. This could get very cumbersome when, let's say for each form in my app, once it passes data to an API and receives a message, you want to show it as a toast. It's just a lot of seemingly unnecessary imports and lines of code.

Custom Hooks to the Rescue!

Let's write something nifty! In Toast/index.tsx, add this code:

const useToast = () => {
  const setToast = useContext(ToastContext);
  const alertSuccess = (message: string) =>
    setToast({ type: 'SUCCESS', message });
  const alertError = (message: string) =>
    setToast({ type: 'WARNING', message });
  return { alertSuccess, alertError };
};

In this function, starting with use, I am fetching the Toast context and creating two functions that area more lightweight and specific.
You can see that even though I am using a React Hook outside of a functional component, I am allowed to because this is a custom hook! I am then returning the two functions in an object, as a return value of the main function.
Remember to Export it along with the over variables at the bottom of the file. Now, let's refactor Component.tsx and see what we've got:

import React from 'react';
import { useToast } from './context';

const Component = () => {
  const { alertSuccess, alertError } = useToast();
  return (
    <div>
      <h1>MY COMPONENT!</h1>
      <button onClick={() => alertSuccess('SUCCESS!')}>
        Make a Successful Toast!
      </button>
      <button onClick={() => alertError('ERROR!')}>Make an Error Toast!</button>
    </div>
  );
};

export default Component;

Now if you reload your app, you will see no change in functionality, but your code looks way better! And isn't that what we want?

Conclusion

In this post, we learned how to create custom React Hooks and implement them effectively. The sky's the limit as to where custom hooks can take you. Reply in the comments if you've made a cool one too!

Happy coding!

~ Sean

Discover and read more posts from Sean Hurwitz
get started
post commentsBe the first to share your opinion
Show more replies