In this post, I would like to share two related things that I have developed in the course of a recent client project: ConditionalComponent and composeIf.

Conditional Components

Sometimes there is the need to render one component or another, based on some condition or context. This is usually done by either an early return or inline via a ternary expression:

// Early return:

if ( something ) {
    return (
        <This />
    );
}

return (
    <That />
);


// Inline ternary:

{ something ? (
    <This />
) : (
    <That />
) }

Reasons for this could be that there are two different implementations of a thing, potentially with different props. For example, an A/B test of two (slightly) different versions of some block or UI element. Or one could really have two completely different components, one for this case, one for the other, no relationship at all.

To streamline this process, I created the ConditionalComponent component, which takes the following three special props:

  • ComponentFalse: This is the component reference to be used in the first case, if the condition is false.
  • ComponentTrue: This is the component reference to be used in the other case, if the condition is true.
  • predicate: This is a function that returns a Boolean to indicate whether to use the true-component or the false-component.

The full code is as follows:

import PropTypes from 'prop-types';

const ConditionalComponent = ( {
    ComponentFalse,
    ComponentTrue,
    predicate,
    ...props
} ) => {
    const Component = predicate( props )
        ? ComponentTrue
        : ComponentFalse;

    return (
        <Component { ...props } />
    );
};

ConditionalComponent.propTypes = {
    ComponentFalse: PropTypes.func.isRequired,
    ComponentTrue: PropTypes.func.isRequired,
    predicate: PropTypes.func.isRequired,
};

export default ConditionalComponent;

As you can see, the predicate function gets passed the props.

Using this component, the simple example from before would look like so:

return(
    <ConditionalComponent
        ComponentFalse={ That }
        ComponentTrue={ This }
        predicate={ () => something }
    />
);

Of course, you can pass additional props to the component, and/or create either or both of the component references yourself; they don’t have to exist already.

For NewsPress, we actually don’t use this component directly though, but…

Conditional Composition

Gutenberg comes with only a few filters so far, and most of them are quite generic ones and only allow for more tageted things to happen if you implement the targeting logic yourself. One example of this would be that most of the block-specific filters only exist as static and generic version, for example, blocks.registerBlockType or blocks.getSaveElement. There is no such thing as a filter for the image block. You would need to do this like so:

addFilter(
    'blocks.registerBlockType',
    'my/image-settings',
    ( settings ) => {
        if ( settings.name !== 'core/image' ) {
            return settings;
        }

        return otherSettings;
    }
);

addFilter(
    'blocks.getSaveElement',
    'my/image-save',
    ( element, blockType ) => {
        if ( blockType.name !== 'core/image' ) {
            return element;
        }

        return otherElement;
    }
);

That’s not too bad, but if you start composing components, things can get messy.

For this reason, I created a smart variant of the Gutenberg-native compose function—well, actually, it’s just lodash’s flowRight, but anyway—that allows for both comfortable and performing results.

Usage would be like so:

const isCoreImageBlock = ( { name } ) => name === 'core/image';

addFilter(
    'editor.BlockEdit',
    'my/image-edit',
    composeIf( isCoreImageBlock )( [
        withCreditsField,
        withImageSwapper,
    ] )
);

The full code is, while quite indented due to the nature of a multi-level higher-order function, quite simple:

/**
 * Conditionally return a higher-order component creator.
 *
 * @param {Function} predicate - Function to test condition.
 *
 * @returns {Function} Higher-order component creator.
 */
export function composeIf( predicate ) {
    return ( hocs ) => {
        return ( Original ) => {
            const Composed = compose( hocs )( Original );

            return ( props ) => {
                return (
                    <ConditionalComponent
                        ComponentFalse={ Original }
                        ComponentTrue={ Composed }
                        predicate={ predicate }
                        { ...props }
                    />
                );
            };
        };
    };
}

As you can see, this uses the ConditionalComponent introduced before.

Leave a Reply

Your email address will not be published. Required fields are marked *