Reputation: 103
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
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