sir-haver
sir-haver

Reputation: 3592

How to use props as component?

I have a component where I receive props that include a Link component (of React router, but could be anything):

  children: [
    {
      id: '1',
      isActive: false,
      label: 'Forms',
      icon: <DestinationsIcon height={30} width={30} />,
      link: <Link to={'somewhere'} />,
    },

Then in the component receiving those props I can extract the props form the children:

  <List>
    {React.Children.map(children, (child) => {
      if (!React.isValidElement(child)) {
        return null;
      }
      return React.cloneElement(child, {
        children: <div>{child.props.label}</div>,
      });
    })}
  </List>

The child.props.label does return the correct label for example. However what I'd like to do is replace that wrapping <div> with the link props, something like this:

children: <child.props.link>{child.props.label}</child.props.link>,

This of course doesn't work. If I'm trying with React.createElement:

children: React.createElement(child.props.link),

The first argument should be a string, but I'm passing a component. The thing is that I must receive the Link component from outside with its own context, and I cannot re-create it, otherwise I could avoid all the complexity.

Does anyone know what would be the correct way to render that link props as a JSX wrapper?

Upvotes: 0

Views: 192

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074335

Your link: <Link to={'somewhere'} /> prop isn't a component, it's an element. This is what it would look like as a component: link: Link (and then you'd need to pass the props for Link separately, perhaps as linkProps):

children: [
    {
      id: '1',
      isActive: false,
      label: 'Forms',
      icon: <DestinationsIcon height={30} width={30} />,
      link: Link,                     // ***
      linkProps: {to: 'somewhere'},   // ***
    },

Then, to use it, you need to be referencing it via an identifier with an upper case first character, since that's how React/JSX differentiate between tags (like div or svg) and components.

So something along these lines:

const Link = child.props.link;
return React.cloneElement(child, {
    children: <Link {...child.props.linkProps}>{child.props.label}</Link>,
});

...although that may need tweaking as it's not clear to me why you're doing the extracting and cloning.

Here's a simpler example showing using a component as a prop:

const { useState } = React;

const Example = ({ link: Link, linkProps, label }) => {
    return <Link {...linkProps}>{label}</Link>;
};

const SomeNiftyLink = ({ href, className, children }) => (
    <a href={href} className={className}>
        {children}
    </a>
);

const App = () => {
    return (
        <Example
            link={SomeNiftyLink}
            linkProps={{
                href: "#somewhere",
            }}
            label="The label"
        />
    );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

Upvotes: 2

Related Questions