Understanding Render Props in React

February 04, 2019 0 Comments

Understanding Render Props in React

 

 

Here comes another technique to avoid the popular D.R.Y problem in software development. In this post, we will be looking into another design pattern in React to avoid a case of logic repetition.

Tip: When working with components use Bit (GitHub). It will help you organize, share and reuse components across apps. This not only keep your code DRY, it lets you build much faster with your team. Try it out.

Reusable React components with Bit
The term render prop refers to a technique for sharing code between React components using a prop whose value is a function. - Reactjs blog

We have learned in the past posts, how we can pass data to child components from parent components. The child components capture the data in the props object argument:

class ChildComp extends React.Component {
constructor(props) { }
render() {
return <div>{this.props.name}</div>
}
}
<ChildComp name="nnamdi" />

Objects, arrays, booleans, strings, Numbers can be passed to child components via props. The child component uses the props argument to know what to render.

Object

<ChildComp obj={ { name: "nnamdi" } } />
class ChildComp extends React.Component {
constructor(props) { }
render() {
return (
<div>
Name: {this.props.obj.name}
</div>
)
}
}

Array

<ChildComp name={["nnamdi", "chidume"]} />
class ChildComp extends React.Component {
constructor(props) { }
render() {
return (
<div>
Name: {this.props.name}
</div>
)
}
}

Boolean

<ChildComp displayName={true} />
class ChildComp extends React.Component {
constructor(props) { }
render() {
if(this.props.displayName) {
return (
<div>
DisplayName: {this.props.displayName}
</div>
)
} else {
return (
<div>
Don't Display Name
</div>
)
}
}
}

String

<ChildComp name="nnamdi" />
class ChildComp extends React.Component {
constructor(props) { }
render() {
return (
<div>
Name: {this.props.name}
</div>
)
}
}

Number

<ChildComp age={45} />
class ChildComp extends React.Component {
constructor(props) { }
render() {
return (
<div>
Age: {this.props.age}
</div>
)
}
}

Now, we can pass a function to the props object.

class ChildComp extends React.Component {
constructor(props) { }
render() {
return <div>{this.props.name()}</div>
}
}
<ChildComp name={() => [ "nnamdi", "chidume" ]} />

You see we passed a function () => [ "nnamdi", "chidume" ] to ChildComponent via name,then it can access it by referencing it as key in the props argument: this.props.name.

This props name bearing a function value is called a render props.

The function if a little bit complex, it is the same thing as this:

function() {
return [ "nnamdi", "chidume" ]
}

It just returns an array. Hope the above will be clearer.

The ChildComponent gets the function through this.props.name and calls the function.

render() {
return (
<div>
{this.props.name()}
</div>
)
}

This will render the array containing my name:

nnamdi chidume

We actually used the name prop(a function prop) to know what to render. The name prop here is called the render prop.

So we can define a Render Prop as a function props that is used by a component to know what to render.

The render prop can return a HTML markup:

<ChildComp name={()=><h1>Nnamdi Chidume</h1>} />
<h1>Nnamdi Chidume</h1>
A render prop is a function prop that a component uses to know what to render — Reactjs Blog

Render props comes handy when we want to share the same state across components.

For example, let’s say we have a component that renders a blog post:

class CommentList extends React.Component {
render() {
return <Comment comment={} />
}
}
class BlogPost extends React.Component {
render() {
return (
<div>
<TextBlock text={} />
<CommentList />
</div>
)
}
}

The BlogPost component renders a blog post. and we know in a blog post, readers can comment on a post. So in the BlogPost component renders the post and renders the comments abstracted to the CommentList.

This pattern makes CommentList tightly coupled to BlogPost. Its valid this way though. If there is no strong use case of having more control over what is rendered within the BlogPost component, we can use it. Like we said, tightly coupled, the BlogPost has to know about the CommentList component. Every time you want to touch the BlogPost component you would have to touch the CommentList component. So what if we can move the CommentList outside the BlogPost?

Here is where render prop comes in: Instead of hard coding CommentList inside BlogPost, we can provide BlogPost with a function prop that uses it to determine what to render.

class CommentList extends React.Componen {
render() {
return <Comment comment={} />
}
}
class BlogPost extends React.Component {
render() {
return (
<div>
<TextBlock text={} />
{this.props.render()}
</div>
)
}
}
<BlogPost render={()=><CommentList/>}/>

This technique makes the behavior in BlogPost extremely configurable. We can render anything apart from comments.

With the name render props, we might come to think that the name of the prop must be render. No, we can specify any name to denote it, so far as it is a function and it returns a JSX markup it is a render prop.

We can rewrite our BlogPost example:

// ...
class BlogPost extends React.Component {
render() {
return (
<div>
<TextBlock text={} />
{this.props.render()}
</div>
)
}
}
<BlogPost render={()=><CommentList/>}/>

to use a different name other than render:

// ...
class BlogPost extends React.Component {
render() {
return (
<div>
<TextBlock text={} />
{this.props.comments()}
</div>
)
}
}
<BlogPost comments={()=><CommentList/>}/>

See we changed render to comments, which is even more declarative, that is it now meaningfully specifies what the prop does.

Following traditon, Whenever we want to pass props to child components we normally use the “attributes” like this:

<BlogPost comments={()=><CommentList/>}/>

Now, according to Reactjs Blog, we can actually put the props attributes inside the JSX element:

<BlogPost>
{()=><CommentList/>}
</BlogPost>

Notice anything? The comments name is no longer there. The render prop ()=><CommentList/> is now a child component to <BlogPost>, it will be passed to <BlogPost> in children props.

what is children props?

While we generally specify props ourselves, React provides some special props for us. In our components, we can refer to child components in the tree using this.props.children.

Let’s look at this:

//...
render() {
return (
<div>
<Container>
This Text
<ChildC />
</Container>
</div>
)
}

<Container> encloses two elements This Text and <ChildC> they are children to <Container> and <Container> parent to them. On execution, This Text and is passed to in children props so it could access its children through: this.props.children. This this.props.children give a component to its children components.

Let’s look behind the hood: We all know React is JSX based, so the JSX markup is transformed to a JS function like this:

<div>
ThisText
<h2>This h2 text</h2>
</div>
|
|
|
v
React.createElement("div", null, 
React.createElement("ThisText", null, null),
React.createElement("h2", null ,
React.createElement("This h2 text", null, null)
)
)

You see React.createElement is appended to all text and elemnt JSX nodes. The React.createElement(...) is responsibble for creating the VDOM and ReactDOM renders the VDOM to the Browser DOM.

Looking at the React.createElement(…) structire

function createElement(type, props, ...children) {
// ...
}
exports.React = {
createElement
}

We see that it accepts three parameters. The first is the type of the node:

The props are the attributes passed to the node, The last param is a spread parameter, it aggregates the rest of the arguments into an array.

React.createElement("div", null, React.createElement(...),
React.createElement(...))

Here, div is the type, null is the props (no attributes was passed to

). The rest of the params is React.createElement(...), they will be merged into a children array. These React.createElement(...)s are children to div. So when creating the div element they will be passed to the props object in the children property.

function createElement(type, props, ...children) {
// ...
if(IS_CLASS(type)) {
let props = props
props.children = children
        const instance = new type(props)
}
// ...
}
// ...

You see the children param is passed to the props children property and the class component is initialized with the props passed to the class constructor. So the component can now access its children tree using this.props.children.

Also, in our Container example, its JSX will be transformed into this:

//...
render() {
return (
React.createElement('div', null,
React.createElement(Container, null,
React.createElement('This Text', null, null),
React.createElement(ChildC, null, null)
)
)
)
}

has

React.createElement(Container, null,
/* children */
React.createElement('This Text', null, null),
React.createElement(ChildC, null, null)
)

You see

/* children */
React.createElement('This Text', null, null),
React.createElement(ChildC, null, null)

We will be merged into an children array and passed to Container instance like this:

let props = props
props.children = children
const container =  new Container(props)

We now know what children props is. Coming back to:

<BlogPost>
{()=><CommentList/>}
</BlogPost>

To access the render prop ()=><CommentList/> we do this this.props.children.

// ...
class BlogPost extends React.Component {
render() {
<div>
<TextBlock text={} />
{this.props.children()}
</div>
}
}
<BlogPost>
{()=><CommentList/>}
</BlogPost>

Since the children props in BlogPost is a function, we should type the children prop to tell React that we are expecting children props to be a function:

// ...
BlogPost.propTypes = {
children: PropTypes.func.isRequired
}

Let’s tick off the points:

  • render prop is a function
  • render prop is a function prop
  • render prop is used for sharing code between components
  • render prop is used by a component to know what to render
  • render prop makes it possible to write reusable components.

If you have any question regarding this or anything I should add, correct or remove, feel free to comment, email or DM me. Thanks !!!


Tag cloud