romellem
romellem

Reputation: 6491

Check if value is a valid React Child

I have a simple React component that wraps its children:

function Wrapper(props) {
    return <div>{props.children}</div>;
}

If I pass an invalid child, this component will throw an error:

const plain_object = {};

// This throws an error
ReactDOM.render(<Wrapper>{plain_object}</Wrapper>, container);

What I'd like to have instead is to check if the children I'm passing in will render without error, for any valid "renderable" value. For example:

function SafeWrapper(props) {
    // Note, this function doesn't exist, but I want it to
    if (!isValidChildren(props.children)) {
        return null;
    }

    return <div>{props.children}</div>;
}

const plain_object = {};

// This will no longer throw
ReactDOM.render(<SafeWrapper>{plain_object}</SafeWrapper>, container);

I know that React has React.isValidElement, but this doesn't check for primatives, or any other items that would otherwise render fine:

// The following calls all return `false` despite them
// not throwing errors when you try and pass them in the `children` prop
// Note that not all of these will render (e.g. the falsy values are skipped)
// but they still don't throw.
React.isValidElement('');
React.isValidElement('Hello');
React.isValidElement(0);
React.isValidElement(1);
React.isValidElement(null);
React.isValidElement(undefined);
React.isValidElement([1]);
React.isValidElement([<div />]);

Is there a function isValidChildren like I mentioned in my example? If not, what would writing one look like?

/**
 * Returns `true` if `children` can be passed without throwing. `false` if otherwise.
 */
function isValidChildren(children) {
    // ?
}

Upvotes: 0

Views: 2253

Answers (2)

Eric Haynes
Eric Haynes

Reputation: 5786

It doesn't directly answer your question about implementing a function to check, but you could use an ErrorBoundary with an inner empty wrapper to handle cases of unrenderable content:

class ErrorBoundary extends React.Component {
  state = { error: null, errorInfo: null }

  static getDerivedStateFromError(error, errorInfo) {
    return { error, errorInfo }
  }

  render() {
    if (this.state.error) {
      return <span>something went wrong</span>
    }
    return this.props.children
  }
}

// needed because the error boundary catches errors
// in *nested* components, whereas if it tried
// to render the children directly, it can't catch
// errors in its own `render`
const Inner = ({ children }) => children

const SafeWrapper = ({ children }) => (
  <ErrorBoundary>
    <Inner>{children}</Inner>
  </ErrorBoundary>
)

That said, there is a bit of a code smell if you're trying to render things without knowing what they are. I can't envision a use case where you would want to just attempt to throw random values at the DOM.

Upvotes: 0

Andrew Gillis
Andrew Gillis

Reputation: 3885

You could do something like this:

const isRenderable = (node) => {
  let renderable;
  switch (typeof node) {
    case "string":
    case "number":
      renderable = true;
      break;
    default:
      if (Array.isArray(node) && node.length) {
        renderable = node.reduce((acc, e) => acc && isRenderable(e), true);
        break;
      }
      renderable = React.isValidElement(node);
      break;
  }
  return renderable;
};

Upvotes: 1

Related Questions