Home > Enterprise >  passing props to a props given component react
passing props to a props given component react

Time:10-16

I have the following function which takes an array of React components, and returns the same array but passes the key prop to these components automatically:

const keyIt = (components) => {
  const keyedComponents = components.map((Component, i)=> <Component key={i}/>)
  return keyedComponents
}

Usage:

const Sidenav = (props)=> {
  return (keyIt([
    <ListItem>Home</ListItem>,
    <ListItem>About</ListItem>,
    <ListItem>Contact</ListItem>,
    <ListItem>Downloads</ListItem>,
    <ListItem>Plans</ListItem>,
  ]))
}

It's a simple utility that I wanted to create to automatically pass the key prop to ignore the errors (only when I know that the order of the list is never going to change).

But it doesn't work, and it shows the following error:

Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: <ForwardRef(ListItem) />. Did you accidentally export a JSX literal instead of a component?

enter image description here

What's the problem? How can I solve this?

CodePudding user response:

As the error says, <ListItem>Home</ListItem> creates an element, not a component, but you're trying to use it as a component. (More about elements here and about components here.)

Instead, to do this, you'd have to pass the component and the props you wanted it to have, and then create the element in the map, like this:

const keyIt = (components) => {
    const keyedComponents = components.map(({Component, props}, i) => <Component key={i} {...props} />);
    return keyedComponents;
};

const Sidenav = (props) => {
    return keyIt([
        {Component: ListItem, props: {children: "Home"}},
        {Component: ListItem, props: {children: "About"}},
        {Component: ListItem, props: {children: "Contact"}},
        {Component: ListItem, props: {children: "Downloads"}},
        {Component: ListItem, props: {children: "Plans"}},
    ]);
};

But please note my comment above, key={i} is usually an anti-pattern.

You can make it less verbose by using [component, props, children] tuples:

const keyIt = (components) => {
    const keyedComponents = components.map(([Component, props, children], i) => (
        <Component key={i} {...props}>
            {children}
        </Component>
    ));
    return keyedComponents;
};

const Sidenav = (props) => {
    const [counter, setCounter] = useState(0);
    useEffect(() => {
        const handle = setInterval(() => {
            setCounter((c) => c   1);
        }, 1000);
        return () => {
            clearInterval(handle);
        };
    }, []);
    return keyIt([
        [ListItem, null, "Home"],
        [ListItem, null, "About"],
        [ListItem, null, "Contact"],
        [ListItem, null, "Downloads"],
        [ListItem, null, "Plans"],
    ]);
};

Live Example (I've added state to Sidenav in the example just to show that the elements are correctly re-rendered when their props changed, since you thought in a comment that wouldn't happen):

const { useState, useEffect } = React;

const ListItem = ({children}) => <div>{children}</div>;

const keyIt = (components) => {
    const keyedComponents = components.map(({ Component, props }, i) => (
        <Component key={i} {...props} />
    ));
    return keyedComponents;
};

const Sidenav = (props) => {
    const [counter, setCounter] = useState(0);
    useEffect(() => {
        const handle = setInterval(() => {
            setCounter((c) => c   1);
        }, 1000);
        return () => {
            clearInterval(handle);
        };
    }, []);
    return keyIt([
        { Component: ListItem, props: { children: `Home (${counter})` } },
        { Component: ListItem, props: { children: "About" } },
        { Component: ListItem, props: { children: "Contact" } },
        { Component: ListItem, props: { children: "Downloads" } },
        { Component: ListItem, props: { children: "Plans" } },
    ]);
};
const Example = () => {
    return <Sidenav />;
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<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>

Or the tuple version:

const { useState, useEffect } = React;

const ListItem = ({children}) => <div>{children}</div>;

const keyIt = (components) => {
    const keyedComponents = components.map(([Component, props, children], i) => (
        <Component key={i} {...props}>
            {children}
        </Component>
    ));
    return keyedComponents;
};

const Sidenav = (props) => {
    const [counter, setCounter] = useState(0);
    useEffect(() => {
        const handle = setInterval(() => {
            setCounter((c) => c   1);
        }, 1000);
        return () => {
            clearInterval(handle);
        };
    }, []);
    return keyIt([
        [ListItem, null, `Home (${counter})`],
        [ListItem, null, "About"],
        [ListItem, null, "Contact"],
        [ListItem, null, "Downloads"],
        [ListItem, null, "Plans"],
    ]);
};

const Example = () => {
    return <Sidenav />;
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<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>

  • Related