React - Predicate functional component

Viewed 690

I created a react functional component to wrap sections of code based on a predicate (instead of using ternary within JSX).

So, the component looks like this:

const PredicateWrapper = ({ children, iff, otherwise }) => (
    iff ? children : (otherwise || null)
);

And can be used like this:

<PredicateWrapper iff={students}>
   {students.map(student => <Student />)}
</PredicateWrapper>

The issue is that this throws an error "cannot read property map of null" if students is null. If I replace the students.map with some text, the text does not render, proving the wrapper is working, however, the point of the wrapper is to deal with cases where students is null, and I would expect it not to "enter" inside the wrapper.

How can I achieve something like this that allows for the inner code to not evaluate and throw runtime errors if iff is falsy?

2 Answers

Really interesting question!

I could be woefully wrong here but I think it's because students.map(student => <Student /> is still part of your parent component's render tree. So when React tries to build the parent component's tree, it's trying to execute that line which throws an error.

PredicateWrapper is mounted and resolved when the child components are recursed upon, which is when the iff would kick in - but that's on the second iteration.

For eg, if I do this, I don't get the error

const Child = ({ students }) => {
  return students.map(student => <Student />);
};


<PredicateWrapper iff={students}>
  <Child students={students} />
</PredicateWrapper>

Let's have a look at how the JSX will be compiled to JavaScript with the online Babel compiler:

const PredicateWrapper = ({ children, iff, otherwise }) =>
  iff ? children : otherwise || null;

const students = null;

const App = () =>
  React.createElement(
    PredicateWrapper,
    {
      iff: students,
    },
    students.map((student) => React.createElement(Student, null))
  );

Now you can see students.map will be executed and cause the error "Cannot read property map of null"

One possible implementation to achieve your goal is to use render props:

The term “render prop” refers to a technique for sharing code between React components using a prop whose value is a function.

const PredicateWrapper = ({ render, iff, otherwise = () => null }) =>
  iff ? render() : otherwise();

const StudentA = () => <span></span>;
const StudentB = () => <span></span>;

const students = [StudentA, StudentB];
//const students = null;

function App() {
  return (
    <PredicateWrapper
      iff={students}
      render={() => students.map(Student => <Student />)}
    />
  );
}

Edit react-predicate-functional-component

Related