Search code examples
javascriptreactjsreact-admin

Do I need these handmade components (React.Fragment that will passthrough props and conditionnal wrapper)


I feel I just reinvented the wheel and it might not be round, because I'm new to the React ecosystem.

I have written these components :

const ForwardPropsWrapper = ({ children, ...rest }) => {
    return React.Children.map(children, child => React.cloneElement(child, rest))
}

Note that it just forwards its props to its children.

const ConditionalWrapper = ({ condition, children, ...rest }) => {
    return (!condition || condition(rest)) ?
    React.Children.map(children, child => React.cloneElement(child, rest))
    : null;
}

condition is a function and it's passed the wrapper's props

I use it with react-admin's to replace a ReferenceField inside a Datagrid with two fields combined :

<Datagrid rowClick="edit">
    {/*
    `Datagrid` will pass props to `ForwardPropsWrapper`
    */}
    <ForwardPropsWrapper label="User / Role">
        {/*
        Both `ConditionalWrapper`s will receive props passed by `Datagrid`
        through `ForwardPropsWrapper` and call the `condition` function
        with these props as argument
        */}
        <ConditionalWrapper condition={props=>props.record.RoleId}>
            <ReferenceField source="RoleId" reference="role">
                <TextField source="name" />
            </ReferenceField>
        </ConditionalWrapper>
        <ConditionalWrapper condition={props=>props.record.UserId}>
            <ReferenceField source="UserId" reference="user">
                <TextField source="email" />
            </ReferenceField>
        </ConditionalWrapper>
    </ForwardPropsWrapper>
</Datagrid>

Why ?

ForwardPropsWrapper because react-admin's Datagrid expects one child per column and passes props to it (the record). A React.Fragment is not good enough because it will swallow up the props.

ConditionalWrapper is explicit, I need to show either one of the two components depending on the props that Datagrid passes them. The condition needs to be evaluated with the props that are passed to the wrapped component by Datagrid so I can't use a simple ternary condition in the template.

So... I can't believe this is the way to go.

Is there a way to achieve this without writing custom components ?

What problems may I run into with the components above ?

Criticism is expected please !


Solution

  • Well I was right saying this was all useless. Just needed to create a custom Field component.

    <Datagrid rowClick="edit">
        <TextField source="id" />
        <DualReferenceField label="User / Role" />
        <TextField source="action" />
        <TextField source="subject" />
        <TextField source="criteria" />
        <TextField source="name" />
    </Datagrid>
    
    const DualReferenceField = props => {
        const hasRole = props.record.RoleId;
        const hasUser = props.record.UserId;
        return (
            <>
            { hasRole ? 
            <ReferenceField source="RoleId" reference="role" {...props}>
                <FunctionField render={(record) => {
                    return (
                        <span>{record.name} <small>(role)</small></span>
                    )
                }} />
            </ReferenceField>
            : null }
            { hasUser ?
            <ReferenceField source="UserId" reference="user" {...props}>
                <TextField source="email" />
            </ReferenceField>
            : null }
            </>
        )
    }