Skip to Content
How To Create A Custom Hook In React

How To Create A Custom Hook In React

If you’re familiar with React hooks but don’t know how to create a custom hook in React, this post is for you. I’ll provide you with a simple to understand custom hook from scratch so that you can get started making your own custom React hooks today!

Estimated reading time: 8 minutes

Before we begin

Before we get started, I’ll be using Material UI in this example to speed up the build process and get to the heart of this post which is creating a custom React hook. If you’re unfamiliar with Material UI that’s okay, all Material UI ( aka. MUI) is is just a library of pre-styled components that speeds up building time. One more thing to note is that the code example on github is in Typescript so if you need a Typescript example, definitely check it out here! Please feel free to download and test it out on your own device!

Another thing to note is that this main code example is for teaching purposes only. Click here to see an example of a custom useState hook that might actually be useful.

Custom React Hook that uses useState

For React dev’s who are familiar with useState, this example is for you. All we’re doing is taking useState out of a component and placing it in a custom hook.

Create App and Install Dependencies

Let’s start our custom useState hook by creating our React app and installing our dependencies.

In the VSCode terminal run the following:

npx create-react-app custom-state-hook

Next we’ll change directories into our app.

 cd custom-state-hook

Since we’ll be using Material UI, let’s add the Material UI dependencies.

npm install @mui/material @emotion/react @emotion/styled

Now that that’s done, let’s start coding!

The Simple Stateful Component

Next we’ll create a simple toggle button that uses useState. Start by creating a /components folder in the /src directory. Inside the /components folder, add a file titled “ToggleButton.jsx”.

At the top of this file add your imports:

import React, { useState } from 'react'
import { Button } from '@mui/material'

Below our imports let’s create a simple React Functional Component. As you can see below, we’re de-structuring out the children prop and we’ve added a piece of state to be used to style our button whether it’s been pressed or not.

const ToggleButton = (props) => {
    const { children } = props
    const [contain, setContain] = useState('outlined')
    return (
    <>
        <Button variant={contain}>
        {children}
        </Button>
    </>
    )
}
export default ToggleButton

We’ll need to add an onClick function that will update our piece of state:

const ToggleButton = (props) => {
    const { children } = props
    const [contain, setContain] = useState('outlined')
    const handleToggle = () => {
      setContain('contained')
    }
    return (
    <>
        <Button variant={contain} onClick={handleToggle}>
        {children}
        </Button>
    </>
    )
}
export default ToggleButton

Be sure to update your “App.jsx” file like so:

import ToggleButton from "./components/ToggleButton";

function App() {
  return <ToggleButton> Toggle button </ToggleButton>
}

export default App;

Want to see how to build Accessible React Toggle Buttons with Material UI? You’ll love this post.

Now if you run npm start, you should see a toggle button that changes from an outlined button to a contained button when clicked.

Button before being clicked:

button with blue border and blue text

Button after being clicked:

button with white text and blue background

Great! But we aren’t using a custom React hook yet. Right now we’re just using useState. But if we wanted to abstract out this simple code into a hook how would we do that?

(I need to restate that I don’t recommend writing this custom hook in production because this piece of state is tied directly to the material ui button and this hook would make no sense in a real world app).

Scaffold The Custom Hook

Start by creating a folder inside the /src directory that’s at the same level as your components folder. Call this folder /hooks. Inside the /hooks folder, add a file called “useButtonTheme.js”. Inside this file let’s copy over the use of useState and our onClick handler function.

import { useState } from "react"

export default function useButtonTheme() {
    const [contain, setContain] = useState('outlined')
    const handleToggle = () => {
        setContain('contained')
    }
}

Notice that we’re exporting this function as a default export.

Next we need to return something from this hook so that the value, or values, that are returned from this hook can be stored and used elsewhere in our app. I’m going to create a constant and title it value, then I’ll assign it with 2 properties. To complete this custom stateful hook I’ll return the value constant from this hook.

import { useState } from "react"

export default function useButtonTheme() {
    const [contain, setContain] = useState('outlined')
    const handleToggle = () => {
        setContain('contained')
    }
    const value = {
        contain,
        handleToggle
    }
    return value
}

Is a hook the best way to manage a theme in your App? Maybe you should check out if Context is a better solution for you. Check out our Context vs Props post to learn more about how Context works.

Adjusting Our Button To Use The Custom Hook

Finally, to use this custom hook all we have to do now is update our “ToggleButton.jsx” file in the following ways:

  • removing the useState import
  • importing the useButtonTheme hook
  • updating our component to use this custom hook
import React from 'react' // remove useState import
import { Button } from '@mui/material'
import useButtonTheme from '../hooks/useButtonTheme' // import custom hook
 
const ToggleButton = (props) => {
    const { children } = props
    return (
    <>
        <Button variant={contain} onClick={handleToggle}>
        {children}
        </Button>
    </>
    )
}
export default ToggleButton

Last, but definitely not least, is to assign a constant to the useButtonTheme hook.

    const value = useButtonTheme()

Then use that constant in replace of the variant and onClick values.

<Button variant={value.contain} onClick={value.handleToggle}>
        {children}
        </Button>

Now that’s all you have to do to create a custom hook!!

Here’s the full “ToggleButton.jsx” component:

import React from 'react'
import { Button } from '@mui/material'
import useButtonTheme from '../hooks/useButtonTheme'

const ToggleButton = (props) => {
    const { children } = props
    const value = useButtonTheme()
    return (
    <>
        <Button variant={value.contain} onClick={value.handleToggle}>
        {children}
        </Button>
    </>
    )
}
export default ToggleButton

If you’re familiar with useState, this example should make sense to you. All we’re doing is taking the useState values out of our component and putting them into a custom hook.

Abstracting the custom hook further to be more reusable

Now our hook works great if we want the button to go from the outlined style to the contained style only. But let’s imagine that we want to change the button initial state and the onClick state on a component by component level.

All we have to do is update our custom stateful React hook like so:

import { useState } from "react"

export default function useButtonTheme(initial, updated) {
    const [buttonStyle, setButtonStyle] = useState(initial)
    const handleToggle = () => {
        setButtonStyle(updated)
    }
    const value = {
        buttonStyle,
        handleToggle
    }
    return value
}

Then inside our component, we’ll make one small change:

const value = useButtonTheme('outlined', 'contained')

Let’s pretend we want to make a different button that’s exactly the same but in that button the styles we want to use instead will be text and outlined. In that case we’d update our hook like so:

const value = useButtonTheme('text', 'outlined')

Now we can use this custom hook throughout our application!!

Example of a real world custom hook that uses useState

the app in the browser. there is a rainbow border around the window, and around a box centered on the page. Inside the centered box is a button that says "toggle button"
The final app once the rainbow border is activated by different components

Let’s pretend that we have an app and whenever a user performs some action (like onMouseEnter) on some components in our app we’re going to add a class that causes the component to have a rainbow border.

This is state that we will be reusing over and over again on various components in various implementations, so in this instance it makes sense to use a custom hook.

import { useState } from "react"

export default function useRainbowTheme() {
    const [rainbow, setRainbow] = useState('')
    const handleToggle = () => {
        setRainbow('rainbow-border')
    }
    const value = {
        rainbow,
        handleToggle
    }
    return value
}

Example component that might use this custom React stateful hook.

import useRainbowTheme from '../hooks/useRainbowTheme'

const Card = (props) => {
   const {children} = props
   const rainbowClass = useRainbowTheme()

return (
<div className={`card ${rainbowClass.rainbow}`} onMouseEnter={rainbowClass.handleToggle}>
    {children}
</div>
)
}

export default Card

The CSS is below if you want to make this hook work on your own. (Like this CSS? I got it from this super fun CSS Border Animations post by Bramus here.)

//App.css file:
#root {
  padding: 3%;
}

.card {
	position: relative;
	z-index: 0;
	width: 400px;
	height: 300px;
	border-radius: 10px;
	overflow: hidden;
	padding: 2rem;
        margin: 0 auto;
}
	
.rainbow-border::before {
  content: '';
  position: absolute;
  z-index: -2;
  left: -50%;
  top: -50%;
  width: 200%;
  height: 200%;
  background-color: #d5111a;
  background-repeat: no-repeat;
  background-size: 50% 50%, 50% 50%;
  background-position: 0 0, 100% 0, 100% 100%, 0 100%;
  background-image: linear-gradient(#b511d5, #d5111a), linear-gradient(#fb3b00, #fbe800), linear-gradient(#33d553, #2700fb), linear-gradient(#334bd5, #9633d5);
}

.rainbow-border::after {
  content: '';
  position: absolute;
  z-index: -1;
  left: 6px;
  top: 6px;
  width: calc(100% - 12px);
  height: calc(100% - 12px);
  background: white;
  border-radius: 5px;
}

Just be sure to import the “Card.jsx” component and “App.css” file back into the “App.jsx” file.

Notice in the code below that I’m also using the useRainbowTheme hook in the App component.

import Card from './components/Card'
import ToggleButton from "./components/ToggleButton"
import './App.css'
import useRainbowTheme from './hooks/useRainbowTheme'

function App() {
  const rainbowTheme = useRainbowTheme()
  return (
    <>
      <div className={rainbowTheme.rainbow} onMouseEnter={rainbowTheme.handleToggle}>
        Rainbow border when you mouse enter here!
      </div>
      <Card>
        <ToggleButton> Toggle button </ToggleButton>
      </Card>
    </>
  )
}

export default App

So that’s a real world example that shows you how to abstract out useState into a custom React hook that you can use in your components.

Thanks so much for reading this far and I truly hope that making your own custom hook feels a lot less daunting to you now.

Photo by Grace To on Unsplash