Using a React Component's function from its Parent

Using a React Component's function from its Parent

With forwardRef and useImperativeHandle

React offers an awesome toolset that allows you to effectively break up and modularize your applications into smaller pieces that work together. This allows us developers to breakout functionality and keep it in one location.

While this way of architecting allows for amazing levels of flexibility and maintainability, we all will eventually hit this scenario: What if I want one custom React component to trigger a function that lives in one of its custom Child components?

Using React's forwardRef API along with the built-in hook useImperativeHandle, this is simple to do!

This tutorial assumes you have some basic knowledge of React and how to use it.


Setting up our Components

To demonstrate, we're going to build a simple component that presents you with some color options and a box. Clicking on the color buttons will change the color of the box.

Box changing colors when buttons are clicked

The header and buttons of our component will live in the Parent component, while the color-shifting box and the functionality to change the box's color will be held in the Child component. Here's what that looks like:

import { useState } from 'react'
const Parent = () => {
    return (
        <div className="flex flex-col gap-4 min-h-screen bg-gray-200 justify-center items-center">
            <h2 className="text-gray-500 text-2xl font-bold text-30">What color should the box be?</h2>
            <div className="flex justify-between w-80">
                <button className="bg-blue-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Blue</button>
                <button className="bg-green-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Green</button>
                <button className="bg-red-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Red</button>
                <button className="bg-yellow-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Yellow</button>
            </div>
            <Child/>
        </div>
    )
}

const Child = () => {
    const [ color, setColor ] = useState('bg-blue-300')
    const changeColor = color => {
        setColor(color)
    }
    return <div className={`w-40 h-40 transition-colors duration-900 ease-in-out rounded-2xl ${color}`}></div>
}

NOTE: I'm using TailwindCSS for some quick styling

There's nothing too crazy going on here, just rendering out our Parent and Child. The Child has a function to update the color of its box, and some state to hold that setting. For now the buttons don't do anything, the box is blue... boring! Let's bring this thing to life!

NOTE: I'm using this as an example because it's easy to understand and visualize the result. In a real-world setting, you'd be better off passing the color through to the Child component as a prop.


Reference the Child

First thing's first, we need to somehow reference the Child component to get access to its properties. React's useRef hook does exactly that. To create a reference to the Child component, we'll need to import that hook from react, create a reference, and apply that reference to the component.

// Added useRef to our imports
import { useState, useRef } from 'react'

const Parent = () => {
    // Set up our reference
    const boxRef = useRef(null)

    return (
        <div className="flex flex-col gap-4 min-h-screen bg-gray-200 justify-center items-center">
            <h2 className="text-gray-500 text-2xl font-bold text-30">What color should the box be?</h2>
            <div className="flex justify-between w-80">
                <button onClick={() => boxRef.current.changeColor('bg-blue-300')} className="bg-blue-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Blue</button>
                <button onClick={() => boxRef.current.changeColor('bg-green-300')} className="bg-green-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Green</button>
                <button onClick={() => boxRef.current.changeColor('bg-red-300')} className="bg-red-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Red</button>
                <button onClick={() => boxRef.current.changeColor('bg-yellow-300')} className="bg-yellow-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Yellow</button>
            </div>
            {/* Apply the reference to our component */}
            <Child ref={boxRef}/>
        </div>
    )
}

We now have a reference set up that should give us access to the properties of the Child. This reference has a property named .current that is set to value of the DOM node of the component it is attached to, giving it access to its properties.

I went ahead and added the click-handlers on each button to trigger the changeColor function in the Child component. Everything seems to be hooked up so we should be good to go, right? Let's try it out:

Forward Ref Error Message

Oof, it blew up! 💥 What's going on?

The reason this won't work, and the thing that makes this process tricky, is that the ref property on our <Child/> component is not a normal "prop". React handles ref differently than it handles most other props and doesn't pass it through to the Child in the props object.


forwardRef To The Rescue

To get this to work properly, we need to "forward" our ref to the Child component. Luckly, React has a nice API called forwardRef that allows exactly that.

To use this API, we need to import it from react and wrap our Child component in the forwardRef function. This function takes in props and ref parameters and returns the Child component.

// Added forwardRef to the import list
import { forwardRef, useState, useRef } from 'react'
const Child = forwardRef((props, ref) => {
    const [ color, setColor ] = useState('bg-blue-300')
    const changeColor = color => {
        setColor(color)
    }
    return <div className={`w-40 h-40 transition-colors duration-900 ease-in-out rounded-2xl ${color}`}></div>
})

This will pass along our ref to the Child component, but now we need to expose our changeColor function to the Parent component through that ref. To do that we will need to use useImperativeHandle, a hook that React provides. This hook takes in a ref param and a function that allows you to expose custom properties to the Parent through that ref. Here it is in action:

// Added useImperativeHandle to our imports
import { forwardRef, useState, useRef, useImperativeHandle } from 'react'
const Child = forwardRef((props, ref) => {
    const [ color, setColor ] = useState('bg-blue-300')
    useImperativeHandle(ref, () => ({
      changeColor: color => {
          setColor(color)
      }
    }))
    return <div className={`w-40 h-40 transition-colors duration-900 ease-in-out rounded-2xl ${color}`}></div>
})

We now have forwarded our ref into the Child component and customized the the instance that is exposed to the Parent component, giving it access to a function that will update the Child's state to change the color of our box.

Save that and give 'er a go!

Box changing colors when buttons are clicked

Fancy! Our "handle" into the Child component is accessible from our Parent component and allows us to update the child's state through the function we've exposed via that "handle".

Here's a look at both completed functions:

import { forwardRef, useState, useRef, useImperativeHandle } from 'react'

const Parent = () => {
    // Set up our reference
    const boxRef = useRef(null)

    return (
        <div className="flex flex-col gap-4 min-h-screen bg-gray-200 justify-center items-center">
            <h2 className="text-gray-500 text-2xl font-bold text-30">What color should the box be?</h2>
            <div className="flex justify-between w-80">
                <button onClick={() => boxRef.current.changeColor('bg-blue-300')} className="bg-blue-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Blue</button>
                <button onClick={() => boxRef.current.changeColor('bg-green-300')} className="bg-green-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Green</button>
                <button onClick={() => boxRef.current.changeColor('bg-red-300')} className="bg-red-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Red</button>
                <button onClick={() => boxRef.current.changeColor('bg-yellow-300')} className="bg-yellow-400 py-2 px-4 focus:outline-none rounded-xl text-white font-bold">Yellow</button>
            </div>
            {/* Apply the reference to our component */}
            <Child ref={boxRef}/>
        </div>
    )
}

const Child = forwardRef((props, ref) => {
    const [ color, setColor ] = useState('bg-blue-300')
    useImperativeHandle(ref, () => ({
      changeColor: color => {
          setColor(color)
      }
    }))
    return <div className={`w-40 h-40 transition-colors duration-900 ease-in-out rounded-2xl ${color}`}></div>
})

Conclusion

Using React's forwardRef API and useImperativeHandle hook, we gain the flexibility to allow for even greater component interactions, adding to the awesome flexibility of the React library. While the example in this article was a bit overkill and added a sorta unnecessary level of complexity to an otherwise simple component, these concepts can be extremely useful when building Component libraries with Alerts, Modals, etc...

Thanks so much for giving this a read, I hope it was helpful!

If you liked this, be sure to follow me on Twitter to get updates on new articles I write!