React Props and Children

Master prop drilling, children, and patterns for composable component APIs.

TL;DR

  1. 01Pass data to children using props in a parent component.
  2. 02Use children to accept JSX from parent components.
  3. 03Avoid prop drilling by using Context or composition patterns.

Tips

  1. 01Use composition and Context instead of drilling props through many levels — it makes code cleaner and easier to maintain.

Warnings

  1. 01Don't pass too many props to a single component — it's a sign to break it into smaller, more focused components.

Basic Props

  • Pass data to child components using props as an object.
    function Greeting({ name, age }) {
      return <p>Hello {name}, age {age}</p>;
    }
    
    <Greeting name="Alice" age={30} />
  • Destructure props for cleaner code in function components.
    function Button({ label, onClick, disabled = false }) {
      return (
        <button onClick={onClick} disabled={disabled}>
          {label}
        </button>
      );
    }
  • Use default values for optional props.
    function Card({ title = "Untitled", content }) {
      return <div><h2>{title}</h2><p>{content}</p></div>;
    }
  • Pass functions as props to handle events from children.
    <Button onClick={() => handleClick()} label="Click me" />

Using Children

  • Accept JSX as children to create flexible wrapper components.
    function Card({ children, title }) {
      return (
        <div className="card">
          <h2>{title}</h2>
          <div className="content">{children}</div>
        </div>
      );
    }
    
    <Card title="Welcome">
      <p>This is card content</p>
    </Card>
  • Children is a special prop that contains nested JSX.
  • Multiple children are passed automatically as an array.
    <Container>
      <Header />
      <Main />
      <Footer />
    </Container>
  • Use React.Children.map to iterate over children.
    function Row({ children }) {
      return (
        <tr>
          {React.Children.map(children, (child, index) => (
            <td key={index}>{child}</td>
          ))}
        </tr>
      );
    }

Prop Drilling and Solutions

  • Prop drilling occurs when passing props through many levels.
    // Level 1
    <App user={user}>
      // Level 2
      <Layout user={user}>
        // Level 3
        <Sidebar user={user}>
          // Level 4
          <Profile user={user} />
        </Sidebar>
      </Layout>
    </App>
  • Use Context to avoid prop drilling for shared data.
    const UserContext = createContext();
    
    <UserContext.Provider value={user}>
      <App />
    </UserContext.Provider>
    
    function Profile() {
      const user = useContext(UserContext);
      return <div>{user.name}</div>;
    }
  • Use composition to pass components instead of data.
    <Layout sidebar={<Sidebar />}>
      <Main />
    </Layout>

Component Composition Patterns

  • Build flexible layouts by accepting components as props.
    function Page({ header: Header, content: Content, footer: Footer }) {
      return (
        <>
          <Header />
          <Content />
          <Footer />
        </>
      );
    }
    
    <Page
      header={<Header />}
      content={<MainContent />}
      footer={<Footer />}
    />
  • Use the render prop pattern for dynamic content.
    function DataFetcher({ render }) {
      const [data, setData] = useState(null);
      useEffect(() => { fetchData().then(setData); }, []);
      return render(data);
    }
    
    <DataFetcher render={(data) => <List items={data} />} />
  • Compose small, focused components for reusability.
    <Modal>
      <Modal.Header>Title</Modal.Header>
      <Modal.Body>Content</Modal.Body>
      <Modal.Footer>Actions</Modal.Footer>
    </Modal>

Advanced Patterns

  • Clone and modify children with cloneElement.
    function FormFields({ children, error }) {
      return React.Children.map(children, (child) =>
        React.cloneElement(child, { error })
      );
    }
  • Use children as a function for render props.
    function Toggle({ children }) {
      const [open, setOpen] = useState(false);
      return children({ open, toggle: () => setOpen(!open) });
    }
    
    <Toggle>
      {({ open, toggle }) => (
        <button onClick={toggle}>
          {open ? "Close" : "Open"}
        </button>
      )}
    </Toggle>
  • Spread remaining props to avoid manual forwarding.
    function Button({ label, ...rest }) {
      return <button {...rest}>{label}</button>;
    }

FAQ