Hideable React component using HOC

Hideable React component using HOC

Imagine that we have a very simple React component Hello which greets a user with a message:

import React from 'react';
import PropTypes from 'prop-types';

export default function Hello({ name }) {
  return (
    

Hello, {name}!

); } Hello.propTypes = { name: PropTypes.string.isRequired };

And here is how we use it:

Great simple component, we really like the code. But it turns out that the name value can be null until we fetch it from a Back-End. We don’t want our users to see a stub: Hello, !. So we decided to hide the text if the name is null:

import React from 'react';
import PropTypes from 'prop-types';

export default function Hello({ name }) {
  if (name == null) {
    return null;
  }
  return (
    

Hello, {name}!

); } Hello.propTypes = { name: PropTypes.string.isRequired };

Looks good! Stub has gone and the message shows up only when the profile is loaded. But there is another issue – some noisy error message in the console:

Warning: Failed prop type: Required prop 'name' was not specified in 'Hello'

Ouch, because name is null it doesn’t pass propTypes validation. Maybe we need to provide some default value like empty string and check for the name not to be empty?

This escalates quickly and we are now shedding crutches instead of focusing on the business logic of the component. What if we have dozens of required properties – name, age and other data which will come later. It doesn’t sound cool if we have to set up some default values for all of them.

Of course, this case can be handled directly by JavaScript outside of the component:

{user.name && }

This solution is okay but what if we use Redux and provide name value directly to container:

import { connect } from 'react-redux';
import Hello from '../../components/Hello';

const mapStateToProps = (state) => {
  const user = state.user;
  return {
    name: user.name
  };
};

export default connect(mapStateToProps)(Hello);

Now we need to pass the user object to the Hello’s parent for using JavaScript-hiding. Or we should rollback to the uglier solution and handle empty state within Hello.

Solution

But can we do better? Can we have a simple, original, neat version of Hello with a normal propTypes validation and make them hideable on some condition? As a bonus, we want to extract such behavior and reuse with other components.

It’s actually doable! Because React components are composable, we can easily create Higher Order Component(HOC) which accepts original always-shown component and turns it into hideable when some condition is met. Here is the code of such HOC:

import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
  hideComponent: PropTypes.bool,
  children: PropTypes.node
};
const defaultProps = {
  hideComponent: false
};

function getDisplayName(component) {
  return component.displayName || component.name || 'Component';
}

export default function Hideable(component) {
  function HideableComponent({ hideComponent, children, ...props }) {
    if (hideComponent) {
      return null;
    }
    return React.createElement(component, props, children);
  }
  HideableComponent.displayName = `Hideable(${getDisplayName(component)})`;
  HideableComponent.propTypes = propTypes;
  HideableComponent.defaultProps = defaultProps;
  return HideableComponent;
}

So Hideable is a simple function which wraps any passed-in component into a proxy HideableComponent. This proxy component has only one real property – hideComponent and if the value of hideComponent is truthy – child component will not be rendered:

Hideable component

Let’s make our Hello hideable and wrap it into Redux:

import { connect } from 'react-redux';
import Hello from '../../components/Hello';
import Hideable from '../../utils/hideable';

const HideableHello = Hideable(Hello);

const mapStateToProps = (state) => {
  const user = state.user;
  if (!user.name) {
    return { hideComponent: true };
  }
  return {
    name: user.name
  };
};
export default connect(mapStateToProps)(HideableHello);

Now mapStateToProps manages visibility of the Hello component and we haven’t changed anything within the original Hello component.

Conclusion

So we started from a very simple component and ended up with the same component topped-up with hideable logic. Using the same HOC approach, you can build more complex shareable behaviors and use them to build complex components from the simple ones.

That’s it! I hope this neat trick will help you write simpler React components and reduce an amount of a boilerplate code.