2 min read

React Function Component as Children

I am creating a React component for my InertiaJS app that accepts children with a callback for passing the parent’s state to the current children.

To clarify, here’s a snippet from the Menu component of HeadlessUI.

<MenuButton as={Fragment}>
  {({ active }) => <button className={clsx(active && 'bg-blue-200')}>My account</button>}
</MenuButton>

As shown, the children are wrapped inside a callback with an active prop. This prop allows us to style the button’s className as the state changes.

Here’s a simplified version of a component where you can pass children with a callback or just normal children:

import React, { ReactNode, useState } from "react";
 
interface ChildrenProps {
  count: number;
  increment: () => void;
  decrement: () => void;
}
 
interface Props {
  children: ReactNode | ((props: ChildrenProps) => ReactNode);
}
 
export const Counter = ({ children }: Props) => {
  const [count, setCount] = useState<number>(0);
 
  const increment = (): void => {
    setCount((oldValue) => oldValue + 1);
  };
 
  const decrement = (): void => {
    setCount((oldValue) => oldValue - 1);
  };
 
  // Check the type of children; if it's a function, build it with ChildrenProps
  // Otherwise, just pass it as a normal component
  let resolvedChildren =
    typeof children === "function"
      ? children({ count, increment, decrement })
      : children;
 
  return resolvedChildren;
};

You can now use it like this:

<Counter>
  {/* these props all typed properly */}
  {(count, increment, decrement) => (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  )}
</Counter>

Or you can pass a normal component as children without a callback:

<Counter>
  <div>
    <p>Static content</p>
  </div>
</Counter>

Ref: Medium