Austin Knight
Austin Knight

Reputation: 103

Assign a React component to a variable with extra props

I'm working on a component mapping function that loops through a list of objects which have a type key. The function returns an object of types of React components, it looks like this:

import _ from 'lodash';
import cellBodyTypes from './cellBodyTypes';
import {
    GenericCellBody,
    SubData
} from './components/CellBody';

const columnMapper = {};

_.forEach(cellBodyTypes, (type) => {
    switch (type) {
        case cellBodyTypes.SUB_DATA:
            columnMapper[type] = SubData;
            break;
        case cellBodyTypes.DEFAULT:
            columnMapper[type] = GenericCellBody;
            break;
        default:
            columnMapper[type] = GenericCellBody;
    }
});

export default columnMapper;

And it's used like this:

renderCellBody = (columnType, cellData, index) => {
    const type = columnType || cellBodyTypes.DEFAULT;
    const CellBodyComponent = columnMapper[type];

    return <CellBodyComponent />;
}

And the render looks something like:

render (
    <div>
        {this.props.cellData.map((cell, index) => (
            <div key={cell.id}>
                {this.renderCellBody(cell.type, cell, index)}
            </div>
        ))}
    </div>
);

What I want to do is to be able to assign column types for new cases which utilize the same React components as other cases, but decorate those new column types with additional props. Something like:

case cellBodyTypes.NUMBER_SUB_DATA:
    columnMapper[type] = React.cloneElement(SubData, {someAdditionalProp: 'something'});
    break;
case cellBodyTypes.SINGLE_NUMBER:
    columnMapper[type] = React.cloneElement(GenericCellBody, {someAdditionalProp: 'something'});
    break;

I tried returning a clone of the React component using React.cloneElement but that does not work, as it gives me this error: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.

Is there a way to do this? Am I close to the right path and just missing something? Thanks.

Upvotes: 2

Views: 21912

Answers (1)

Prakash Sharma
Prakash Sharma

Reputation: 16472

That is because React.cloneElement return a react element, not component. So after

columnMapper[type] = React.cloneElement(SubData,...,

columnMapper[type] will containt an element.

But problem is that in renderCellBody function, you are trying to convert an element again into element by writing

return <CellBodyComponent />;

And that throws an error.

I would suggest that you keep columnMapper an array of elements. So the switch/case code should look something like this

_.forEach(cellBodyTypes, (type) => {
    switch (type) {
        case cellBodyTypes.SUB_DATA:
            // Store element instead of component
            columnMapper[type] = <SubData />;
            break;
        case cellBodyTypes.DEFAULT:
            // Store element instead of component
            columnMapper[type] = <GenericCellBody />;
            break;
        case cellBodyTypes.NUMBER_SUB_DATA:
            columnMapper[type] = React.cloneElement(SubData, {someAdditionalProp: 'something'});
            break;
        case cellBodyTypes.SINGLE_NUMBER:
            columnMapper[type] = React.cloneElement(GenericCellBody, {someAdditionalProp: 'something'});
            break;
        default:
            columnMapper[type] = <GenericCellBody />;
    }
});

So now columnMapper is an array of elements. Therefore in renderCellBody function, you don't need to convert them into element again. You can simply return the value

renderCellBody = (columnType, cellData, index) => {
    const type = columnType || cellBodyTypes.DEFAULT;
    const CellBodyComponent = columnMapper[type];

    // CellBodyComponent is already an element. So directly return it.
    return CellBodyComponent;
}

Upvotes: 5

Related Questions