Making Sense of React's Render Props
React's render props is easy if you understand FP
I came across render props when checking out Apollo GraphQL’s tutorial. Now I think I finally figured it out, I try my best to explain it as easy as possible here in my own words. The highlight is in the last part where I shed a FP light onto it. So stay tuned!
First, stop trying to understand the name render props. Instead, look at the following example.
I want to build a React Component which has 2 buttons cat and dog. When the user click on the cat button, it shows the text “cat” somewhere, and by clicking the dog button, shows the text “dog”.
This should be simple enough, let’s create-react-app and write the App.js as follows:
import React from "react";
import "./App.css";
class Animal extends React.Component {
state = { animal: "Dog" };
onDogClicked = () => this.setState({ animal: "Dog" });
onCatClicked = () => this.setState({ animal: "Cat" });
render() {
return (
<div>
<button onClick={this.onDogClicked}>Dog</button>
<button onClick={this.onCatClicked}>Cat</button>
<div>{this.state.animal}</div>
</div>
);
}
}
function App() {
return (
<div className="App">
<Animal />
</div>
);
}
export default App;
This is like React 101. The Animal
component has a state
called animal
. It has 2 buttons, by clicking them set the state animal
to “cat” or “dog”. And there is a div to display the state in text.
Now thinking about it, it feels a bit boring to just display text, I want to change it to display emojis. I write some code in my head and have to admit that it is not much different from the above, only to replace the div
with some logic to show a cat or dog emoji based on the state.
Being a professional programmer, I think it only makes sense to create a separate component for displaying the animal state.
const TextAnimalView = props => <div>{props.animal}</div>;
const EmojiAnimalView = props => (
<div>{props.animal === "Dog" ? "🐶" : "🐱"}</div>
);
class Animal extends React.Component {
state = { animal: "Dog" };
onDogClicked = () => this.setState({ animal: "Dog" });
onCatClicked = () => this.setState({ animal: "Cat" });
render() {
return (
<div>
<button onClick={this.onDogClicked}>Dog</button>
<button onClick={this.onCatClicked}>Cat</button>
<TextAnimalView animal={this.state.animal} />
<EmojiAnimalView animal={this.state.animal} />
</div>
);
}
}
Now I have TextAnimalView
to display the animal in text, and EmojiAnimalView
to display it in emoji. But they are inside the Animal
component. And I really want to be able to pass the view into the Animal
component so that it can use it to display its animal
state like this:
Only if only I know how …
The problem here is that the animal
state is only visible from inside the Animal
component. If I move TextAnimalView
and EmojiAnimalView
outside of it, then how can they access the state animal
?
We can play a trick
As you probably guessed it, to pass the view component into the Animal
component, it has to go in as a property, sort of.
<Animal render={<EmojiAnimalView ... />}>
But wait, this cannot work, how to pass in the animal
state?
Here I want to quote from the FP world:
If you have a problem, you probably can solve it by using a lambda.
Instead of passing the view directly, we can pass in a function that when called returns the view.
<Animal render={animal => <EmojiAnimalView animal={animal} />}>
and we have to change the render
function in Animal
accordingly:
render() {
return (
<div>
<button onClick={this.onDogClicked}>Dog</button>
<button onClick={this.onCatClicked}>Cat</button>
{this.props.render(this.state.animal)}
</div>
);
}
Aha! The state is passed in by calling the render function!
Now you should understand why it is called render props.
One more trick
Some people want to pass in the view not as a prop but as a child of the Animal
component, in that case you can also do it like this:
<Animal>
{animal => <TextAnimalView animal={animal} />}
</Animal>
This can also work because you can still access the child component from the props
:
render() {
return (
<div>
<button onClick={this.onDogClicked}>Dog</button>
<button onClick={this.onCatClicked}>Cat</button>
{this.props.children(this.state.animal)}
</div>
);
}
I promised you that the highlight is in the last part, now it comes.
To look at the core of what is happening here, turns out we do not even need react.
const dog = view => console.log(view("Dog"));
const cat = view => console.log(view("Cat"));
const textView = animal => `This is a ${animal}`;
const emojiView = animal => `This is a ${animal === "Dog" ? "🐶" : "🐈"}`;
dog(textView);
cat(textView);
dog(emojiView);
cat(emojiView);
That’s it.
Thanks for reading.
Happy coding!
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Email