Streamlining Component Logic with "Children as Function" in React
Tags: designpatterns, javascript, react, frontend
Published: 2024-03-25 17:47:20

The "Children as Function" pattern in React, distinct from the Render Props pattern, utilizes children as a function to dynamically render content. This approach allows parent components to dictate the rendering logic of their children in a highly flexible and reusable manner.

## **Core Advantages**

- **Simplifies Component Composition:** Avoids prop drilling and reduces component hierarchy complexity.
- **Enhances Flexibility:** Enables dynamic rendering based on parent component's state or props.
- **Boosts Reusability:** Facilitates the creation of utility components that can adapt their rendering based on different contexts.

### Use Cases

## **1. Conditional Rendering Based on Device Type**

The **`AutoSizer`** component provided is a practical example of the "Children as Function" pattern, designed to dynamically adjust the rendering of its children based on the parent component's size. This is particularly useful in responsive layouts and complex UIs where component size needs to adapt to available space.

First, let's create a utility hook **`useResizeRect`** to listen for resize events and provide the current dimensions of the **`AutoSizer`** component's element:

```jsx
import React, { useState, useEffect } from 'react';

// Hook to listen for resize events and return the element's rect
const useResizeRect = (ref) => {
  const [rect, setRect] = useState(null);

  useEffect(() => {
    if (!ref.current) return;

    const setDimensions = () => {
      setRect(ref.current.getBoundingClientRect());
    };

    setDimensions();

    window.addEventListener('resize', setDimensions);
    return () => window.removeEventListener('resize', setDimensions);
  }, [ref]);

  return rect;
};
```

Next, let's implement the **`AutoSizer`** component as per the snippet you've provided. For the sake of completeness, we'll add the missing **`Box`** component, assuming it's a styled component that accepts **`flexGrow`** and **`css`** props. You might replace this with a div or any other container element suitable for your project:

```jsx
import React, { useRef } from 'react';

// Assuming Box is a styled component or similar. Replace with your own implementation
const Box = ({ children, flexGrow, css, ref }) => (
  <div ref={ref} style={{ flexGrow, ...css }}>
    {children}
  </div>
);

const AutoSizer = ({ children }) => {
  const ref = useRef(null);
  const rect = useResizeRect(ref);

  return (
    <Box ref={ref} flexGrow={1} css={{ position: 'relative' }}>
      <Box css={{ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }}>
        {rect && children({ width: rect.width, height: rect.height })}
      </Box>
    </Box>
  );
};

```

Finally, to use the **`AutoSizer`**, we can render a component inside it that adjusts its content or styling based on the provided **`width`** and **`height`**. Here's an example of a component that displays its dimensions:

```jsx
const ResponsiveComponent = () => (
  <AutoSizer>
    {({ width, height }) => (
      <div style={{ width: '100%', height: '100%', backgroundColor: '#f0f0f0' }}>
        The size is {width}px by {height}px.
      </div>
    )}
  </AutoSizer>
);

export default ResponsiveComponent;

```

In this example, **`ResponsiveComponent`** uses the **`AutoSizer`** to adapt its content based on the available space, demonstrating how the "Children as Function" pattern can effectively manage dynamic sizing concerns in a React application. This pattern enables components to remain decoupled and reusable while still being able to respond to changes in their environment.

## **2. The `CustomizableForm` Component**

This component renders a basic form and accepts an optional **`children`** prop. If **`children`** is a function, it calls this function with the form and additional props. Otherwise, it renders the form directly.

```jsx
mport React from 'react';

// Mock functions for demonstration purposes
const useFormik = () => ({
  submitForm: () => alert('Form submitted'),
  resetForm: () => alert('Form reset'),
  // Add additional formik mock handlers and properties as needed
});

const CustomizableForm = ({ children }) => {
  const formik = useFormik(); // Assume this hook returns our form handlers and state

  // Define the default form UI
  const defaultForm = (
    <form onSubmit={formik.submitForm}>
      {/* Form fields go here */}
      <input type="text" placeholder="Enter something..." />
      <button type="submit">Submit</button>
    </form>
  );

  // If children is a function, call it with the form and additional props
  // Otherwise, render the default form
  return typeof children === 'function' ?
    children({
      form: defaultForm,
      submitForm: formik.submitForm,
      resetForm: formik.resetForm,
      // Include additional form state or handlers as needed
    }) :
    defaultForm;
};

export default CustomizableForm;

```

### **Usage Example**

### 1. Using **`CustomizableForm`** Directly

When you want to use the **`CustomizableForm`** without customization, simply render it without passing children.

```jsx
const App = () => (
  <CustomizableForm />
);

```

### 2. Customizing **`CustomizableForm`** with Additional Content

To customize the form, provide a function as children that returns a React element. This function receives an object containing the default form component and form handlers, allowing you to incorporate the form into a more complex structure or augment it with additional elements.

```jsx
const CustomizedForm = () => (
  <CustomizableForm>
    {({ form, resetForm }) => (
      <>
        {form} {/* Render the default form */}
        <button onClick={resetForm} type="button">Reset</button>
        {/* Additional custom content can go here */}
      </>
    )}
  </CustomizableForm>
);

export default CustomizedForm;

```

This approach offers the best of both worlds: the simplicity and reuse of a standard form component with the flexibility to customize and extend it as needed, without duplicating the core form logic and markup across the application.