
Published July 24, 2017
Rethinking Function as Child Components
Update: In an earlier version of this article, I referred to the pattern of passing a rendering function as Function as Prop Component. I’ve updated this article to use the now common name, Render Props, as well as to refresh some of the sample code.
React has also embraced the concept of passing a function as
children
in the recent context APIConsumer
component. With deference to the React core team, I still maintain that the practice of naming things matters.
In the world of React JS, render callbacks are emerging as a powerful alternative to higher-order components (HOCs). But just what is a render callback? And why should you avoid using one of the most popular implementations of a render callback known as Function as Child Components? I’ll introduce what I believe are two better solutions: Render Props and Component Injection.
What is a render callback?
First some background. In JavaScript, functions are first-class citizens. They are merely objects and thus you can pass them around at will, even as parameters to other functions. This is one of the most powerful features of the language.
Take a look at the example below. Notice that we create a function foo
which takes a callback function as a parameter.
When we call foo
, it turns around and “calls back” to the passed-in function.
const foo = (hello) => {
return hello('foo');
};
foo((name) => {
return `hello from ${name}`;
});
// hello from foo
As you can see, foo
used the callback function to complete a portion of a string.
In the React world, a render callback works the same way, but returning a portion of the rendered markup.
What is a Function as Child Component?
A Function as Child Component (or FaCC) is a pattern that lets you you pass a render function to a component as the children
prop.
It exploits the fact that you can change what you can pass as children
to a component.
By default, children
is of type ReactNodeList
(think of this as an array of JSX elements). It’s what we all are used to when placing JSX within other tags.
When you use a FaCC, instead of passing JSX markup, you assign children
as a function.
<Foo>
{(name) => <div>`hello from ${name}`</div>}
</Foo>
In this example, the Foo
component would look something like this.
const Foo = ({ children }) => {
return children('foo');
};
As you can see, this is nearly identical to the initial example that I gave of a callback function.
There is an excellent article that explains Function as Child Components beautifully. While I think there are better alternatives to FaCCs, the author does a fine job of explaining them.
A real-world example of a FaCC
Now let’s look at a more advanced example of a FaCC. This one is based on reactpatterns example of a render callback.
class WindowWidth extends React.Component {
constructor(props) {
super(props);
this.state = { width: window.innerWidth };
this.onResize = this.onResize.bind(this);
}
componentDidMount() {
window.addEventListener('resize', this.onResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResize);
}
onResize({ target }) {
this.setState({ width: target.innerWidth });
}
render() {
const { width } = this.state;
return this.props.children(width);
}
}
Then you could use it as follows.
<WindowWidth>
{width => <div>window is {width}</div>}
</WindowWidth>
As you can see above, children
is “overloaded” and passed to WindowWidth
as a function
instead of being a ReactNodeList
as nature intended.
The WindowWidth
component’s render
method calls this.props.children
(passing it width
), which returns rendered JSX.
The real power of render callbacks can be seen in this example.
WindowWidth
will do all of the heavy lifting, while exactly what is rendered can change, depending on the render function that is passed.
Not all rectangles are squares
It’s important not confuse Function as Child Components with render callbacks. A FaCC is simply one implementation of a render callback. There are other ways to implement render callbacks. In other words, a FaCC is a type of render callback, but not all render callbacks are FaCCs.
FaCCs are an anti-pattern. There, I said it.
What?! How can that be? FaCCs are loved and embraced by many in the React community. How can it be an anti-pattern? Allow me make my case by taking a step back.
“There are only two hard things in Computer Science: cache invalidation and naming things.”
– Popularly attributed to Phil Karlton
Let’s say that you had a function that took two numbers, added them together and returned the result.
What would you call that function? You might call it add
, or maybe sum
.
const add = (a, b) => {
return a + b;
};
add(2, 2);
Clean coding practices state that you use descriptive names for properties, variables, functions, etc.
I hope that we can all agree with this concept.
You would never name your add
function above badger
(although that would be cool) or tapioca
or even children
, would you?
const children = (a, b) => {
return a + b;
};
children(2, 2);
Of course you wouldn’t. That would be silly, confusing, and not very self-documenting.
Then why in the world would you ever use a prop named children
to pass a render callback function?
I can only imagine seeing this used for the first time in a pull request.
Not in my organization, fella. Rejected!
Using a pattern that goes against what we all consider to be a best practice is called an anti-pattern. By definition, then, FaCCs are an anti-pattern.
Render Props
I’ll remind you that passing a render callback function to a component is not the issue.
The issue is that the FaCC implementation chose to use the prop children
.
So how could we pass a render callback function to a component in a clean manner?
You would need to name your prop something meaningful.
Here’s how we could change our Foo
example above to pass a function as a prop.
<Foo render={(name) => <div>`hello from ${name}`</div>} />
And here’s another example, except hello
is created outside of the JSX (a better practice, IMO).
const hello = (name) => {
return <div>`hello from ${name}`</div>;
};
<Foo render={hello} />
And this time, Foo
makes a lot more sense.
const Foo = ({ render }) => {
return render('foo');
};
Notice how this is much more descriptive? The code is self-documenting. You can say to yourself, “Foo calls a function to do the rendering,” instead of “Foo calls something; I don’t remember why.”
Component Injection - A better solution
We’ve already seen that naming your props is important. But instead of calling a function to render as you do with Render Props, I’d like to propose that, in many cases, you should pass a component. This allows you to use the more expressive JSX syntax. It’s called Component Injection because you pass (or inject) a component into another component.
Here’s the altered Foo
solution.
const Hello = ({ name }) => {
return <div>`hello from ${name}`</div>;
};
<Foo render={Hello} />
Things look pretty much the same as in the Render Props example above,
except the prop we pass is capitalized because components must be capitalized in React.
We also pass in props
, an object, instead of a single string
parameter, but those are the only differences.
This should all look very familiar to you.
And our Foo
component looks a lot more like a traditional React component.
const Foo = ({ render: View }) => {
return <View name="foo" />;
};
One thing to notice is that we rename render
to View
using ES6 destructuring assignment syntax
(i.e., the render: View
part), again because components must be capitalized in React.
But the cool part is that instead of calling a function to render, it uses standard JSX composition. Refreshing, huh?
The only thing that might be different from what you might be used to is that instead
of import
ing Hello
, we inject it (i.e., pass it as a prop).
Injecting your dependencies is a powerful side effect of this technique that allows for easier run-time altering of components and increased ease of mocking tests.
Need more examples?
Here’s the more advanced solution shown above, altered for Component Injection.
Everything with WindowWidth
remains the same except for the render
method:
render() {
const { width } = this.state;
const { render: View } = this.props;
return <View width={width} />;
}
…as well as how you use it.
<WindowWidth render={DisplayWindowWidthText} />
const DisplayWindowWidthText = ({ width }) => {
return <div>window is {width}</div>;
};
As you can see, the DisplayWindowWidthText
component is “injected” into WindowWidth
as a prop named render
.
We could even pass a different component and get a completely different rendered output – again, thanks to the power of render callback.
<WindowWidth render={DisplayDevice} />
const DisplayDevice = ({ width }) => {
let device = null;
if (width <= 480) {
device = 'mobile';
} else if (width <= 768) {
device = 'tablet';
} else {
device = 'desktop';
}
return <div>you are using a {device}</div>;
};
In closing…
In all three methods shown here – Function as Child Component, Render Props, and Component Injection – the means to render is passed into the component, and the component “calls back” to perform the rendering. All three are implementations of a render callback.
It’s important to note that my opinion on FaCCs is not universally shared.
Many people in the industry, and even some members of my own development team,
don’t see the issue with them.
However, it’s clear to me, and many respected industry leaders, that re-purposing children
goes
against one of the most basic things we’re taught in Computer Science;
that is, “Choose meaningful names for your variables.”
I can only imagine that overloading children
by passing a function was probably discovered by someone who said,
“Cool hack! I wonder what can I do with this?”
Don’t be a hack. Reject the use of Function as Child Components.