简体   繁体   中英

How to pass components to children?

I have this simple component that renders a link and a comment form. Clicking the link focuses the comment form. For that I use a ref that I create in the top level component and pass it down to the two sub-components. This part works fine.

import { useRef } from "react";

export default function Commentable({children}) {
    const textareaRef = useRef();

    return (
      <>
           <CommentableButton formRef={textareaRef} />
           <CommentForm formRef={textareaRef} />
      </>
    )
}

export function CommentableButton({formRef}) {

    function focusForm(e) {
        e.preventDefault();
        formRef.current.focus();
    }

    return (
        <a href="#" onClick={focusForm}>Comment</a>
    )
}

export function CommentForm({formRef}) {
    return (
        <textarea ref={formRef}></textarea>
    )
}

I want to be able to pass CommentButton and CommentForm down to children like this:

export default function Commentable({children}) {
    const textareaRef = useRef();

    return (
      {children 
           button={<CommentableButton formRef={textareaRef} />}
           form={<CommentForm formRef={textareaRef} />}
      }
    )
}

so I can use it like this:

<Commentable>

    <div class="card">
        <div class="card-body">
            <h5>Media Title</h5>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur nec enim id dolor elementum imperdiet. Phasellus luctus ante elit, ac egestas diam posuere at.</p>
            <hr />
            {button}
        </div>
        <div class="card-footer">
            {form}
        </div>
    </div>

<Commentable>

There are many commentable entities in the app that I'm building and I'd like to keep all the commentable behavior located in one component that I can use when needed. I've tried different ways of achieving this, but can't seem to figure it out. I'm pretty new to React and just getting started with it. Please let me know if there's a better way to do this. I'm trying to do this just with function components and hooks. Thanks for any help!

This is where forwardRef come to play.

Ref forwarding is a technique for automatically passing a ref through a component to one of its children.

First, wrap the comment button and comment form inside forwardRef function:

export const CommentButton = React.forwardRef((_props, ref) => {
  const focusForm = (e) => {
    e.preventDefault();
    ref.current.focus();
  };

  return (
    // eslint-disable-next-line jsx-a11y/anchor-is-valid
    <a href="#" onClick={focusForm}>
      Comment
    </a>
  );
});

export const CommentForm = React.forwardRef((_props, ref) => {
  return <textarea ref={ref}></textarea>;
});

Then create commentable component where we initialize the textareaRef and pass down the textareaRef directly to the children. Here we render both the button and form components inside arrow function and make the children callable so that we can pass those two component into commentable component.

export const Commentable = ({ children }) => {
  const textareaRef = React.useRef();
  const button = () => {
    return <CommentButton ref={textareaRef} />;
  };
  const form = () => {
    return <CommentForm ref={textareaRef} />;
  };

  return children(button(), form());
};

Or by passing the component directly as arguments:

export const Commentable = ({ children }) => {
  const textareaRef = React.useRef();
  return children(
     <CommentButton ref={textareaRef} />,
     <CommentForm ref={textareaRef} />
  );
};

This is the final result code and the implementation:

Commentable.js


import React from "react";

export const Commentable = ({ children }) => {
  const textareaRef = React.useRef();
  return children(
    <CommentButton ref={textareaRef} />,
    <CommentForm ref={textareaRef} />
  );
};

export const CommentButton = React.forwardRef((_props, ref) => {
  const focusForm = (e) => {
    e.preventDefault();
    ref.current.focus();
  };

  return (
    // eslint-disable-next-line jsx-a11y/anchor-is-valid
    <a href="#" onClick={focusForm}>
      Comment
    </a>
  );
});

export const CommentForm = React.forwardRef((_props, ref) => {
  return <textarea ref={ref}></textarea>;
});


And you can use the Commentable component like this:

import { Commentable } from "./Commentable";

export default function App() {
  return (
    <Commentable>
      {(button, form) => {
        return (
          <div className="card">
            <div className="card-body">
              <h5>Media Title</h5>
              <p>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit.
                Curabitur nec enim id dolor elementum imperdiet. Phasellus
                luctus ante elit, ac egestas diam posuere at.
              </p>
              <hr />
              {button}
            </div>
            <div className="card-footer">{form}</div>
          </div>
        );
      }}
    </Commentable>
  );
}

The pattern you're looking for is probably the render props pattern. https://reactjs.org/docs/render-props.html

You'll often see libs written this way, where a wrapper component expects a single function as a child which has particular props/enhanced props passed to it.

const Commentable = ({ children }) => {
  const ref = useRef();

  return children({
    button: <Button ref={ref} />,
    form: <Form ref={ref} />
  });
};

<Commentable>
    {({ button, form }) => (
      <>
        <h1>Hello CodeSandbox</h1>
        {button}
        <h2>Start editing to see some magic happen!</h2>
        {form}
      </>
    )}
</Commentable>

You could use a hook too:

const useCommentable = () => {
  const ref = useRef();
  return {
    button: <Button ref={ref} />,
    form: <Form ref={ref} />
  };
};

const MyComponent = () => {
     const {button, form} = useCommentable();
     return <>{button}{form}</>
}

I forget if in the hook example here you need to do extra work to prevent re-renders. Anyway, like React often says.. optimize later.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM