DEV Community

Cover image for Pass refs to child components using the function as a child pattern
Phuoc Nguyen
Phuoc Nguyen

Posted on • Edited on • Originally published at phuoc.ng

Pass refs to child components using the function as a child pattern

In the previous post, we learned about using a callback ref to build simple container queries. That's cool and all, but what if we want to use those queries in other parts of our app? That's where creating reusable components comes in.

Reusable components are a fundamental part of React development. They allow us to write code that's modular and easy to maintain, and can be used across different parts of our app. By creating reusable components, we can write less code and avoid bugs.

To do this, we'll use a pattern called functions as a child. But before we dive into that, let's first understand what this pattern is all about.

Introduction to the function as a child pattern

When working with children props in React, you'll typically encounter components or simple strings/numbers. But did you know that the children prop can also be a function?

Enter the Function as a child (FAAC) pattern. This popular technique in React allows for more dynamic communication between parent and child components. Instead of passing down props directly, the parent component passes down a function as a child. The child component can then call this function with any necessary data, providing more flexibility and interactivity between components.

This pattern is particularly useful when the child component needs to interact with its parent in some way. By passing a function as a child, the parent retains control over what data is being passed down, while still allowing the child to manipulate and use that data in a meaningful way.

The Function as a child pattern is a popular technique used in many libraries and frameworks, including React Router, a widely used routing library for React applications. This pattern allows the router to pass its routing information down to its child components, giving them access to the router's state and enabling them to navigate between different routes.

<Router>
    <nav>
        <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/about">About</Link></li>
            <li><Link to="/topics">Topics</Link></li>
        </ul>
    </nav>

    <Switch>
        {/* Render some UI here based on the current URL */}
        <Route exact path="/">
        {({ match }) => (
            // Render some UI here if the current URL matches "/"
        )}
        </Route>

        {/* Render some UI here based on the ID parameter */}
        <Route path="/topics/:topicId">
        {({ match }) => (
            // Render some UI here if the current URL matches "/topics/:topicId"
        )}
        </Route>

        {/* Render some UI here for any other URLs */}
        <Route path="/">
        {({ match }) => (
            // Render some UI here if no other routes match
        )}
        </Route>
    </Switch>
</Router>
Enter fullscreen mode Exit fullscreen mode

This example shows how React Router passes routing information to its child components using a "Function as a Child". The Route component takes a function as its child, which gets an object with information on whether the current route matches the specified path. This makes it possible to render UI conditionally based on the current URL.

Passing a callback ref to child components

Now that you understand the basic concepts of the Function as a Child pattern, let's see how we can use it to pass a callback reference to a child component.

To do this, let's take a step back to the example in the previous section. We'll create a reusable component named SizeTracker that passes the callback reference and internal state to children.

export const SizeTracker = ({ children }) => {
    const [width, setWidth] = React.useState(0);

    const trackSize = (ele) => {
        // ...
    };

    return children({
        ref: trackSize,
        width,
    });
};
Enter fullscreen mode Exit fullscreen mode

The SizeTracker component we've just created uses the Function as a Child pattern to pass down its internal state and the callback reference to its children.

This approach allows us to use SizeTracker in a more flexible way, as it doesn't make any assumptions about how we want to render our components. Instead, it simply provides us with the necessary information and leaves the rendering logic up to us.

Here's an example of how we can use SizeTracker with a child component:

<SizeTracker>
{
    ({ ref, width }) => (
        <div ref={ref}>
            {/* Render some UI here based on the current width */}
        </div>
    )
}
</SizeTracker>
Enter fullscreen mode Exit fullscreen mode

In this example, we're passing a function as a child to SizeTracker. The function receives an object with two properties: ref and width. We can then use these properties in our child component to track the size of the element and render UI based on its width.

Below you'll find the sample code that performs the same functions as described in the previous post:

<SizeTracker>
{
    ({ ref, width }) => (
        <div ref={ref}>
            <div
                style={{
                    columnCount: width < 200 ? 1 : (width < 400 ? 2 : 3)
                }}
            >
                ...
            </div>
        </div>
    )
}
</SizeTracker>
Enter fullscreen mode Exit fullscreen mode

Check out the demo below to see how the number of columns adjusts. Try dragging the element on the right side to move it left or right, and watch the size of the container change, updating the layout accordingly. Go ahead and give it a try!

Using the Function as a child pattern allows us to create more flexible and reusable components that can be used in different contexts without assuming how they should be rendered. This leads to more maintainable and composable code, which is especially important for large-scale projects.


It's highly recommended that you visit the original post to play with the interactive demos.

If you found this series helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

If you want more helpful content like this, feel free to follow me:

Top comments (0)