DEV Community

Cover image for Streamlining Component Logic with "Children as Function" in React
bhanu prasad
bhanu prasad

Posted on

Streamlining Component Logic with "Children as Function" in React

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:

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;
};
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
};

Enter fullscreen mode Exit fullscreen mode

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:

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;

Enter fullscreen mode Exit fullscreen mode

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.

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;

Enter fullscreen mode Exit fullscreen mode

Usage Example

1. Using CustomizableForm Directly

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

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

Enter fullscreen mode Exit fullscreen mode

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.

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;

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)