Skip to content

Instantly share code, notes, and snippets.

@jordanrios94
Last active October 9, 2018 20:30
Show Gist options
  • Save jordanrios94/a18b42b7faf6e66e399653b763b3d7ed to your computer and use it in GitHub Desktop.
Save jordanrios94/a18b42b7faf6e66e399653b763b3d7ed to your computer and use it in GitHub Desktop.
React Render Props

Render Props

A "render" prop?

A simple technique for sharing code between React components using a prop where its value is a function

What can we pass as a prop?

<Header
  isDark={true} //boolean
  title="Hello World" //string
  height={40} //int
  menuItems={['Home', 'About', 'Contact']} //array
  user={{
    //object
    name: 'Kristijan',
    surname: 'Ristovski'
  }}
  leftSide={<img src="logo.png" />}
  onCollapse={collapsed => alert(collapsed)} //a function
  renderRightSide={user => <div> Hello {user.name} </div>}
  // ☝️ a function that returns jsx
/>

Why make props functions?

Because you can (period). In all honesty it is to be able to use reusuable business code that live in a react component with arguments to tell it how to render.

How can the component use those props?

class Header extends Component {
  render() {
    const {leftSide, title, renderRightSide} = this.props;
    return (
      <div>
        {leftSide}
        <div>{title}</div>
        {renderRightSide({user: {name: 'Kristijan'}})}
      </div>
    );
  }
}

Two ways to pass a function to a component.

Using a prop

Parent.js

...
render() {
  <Child url="user" render={({ data, loading }) => <div>data.name</div>} />
  <Child url="todos" render={({ data, loading }) => {return data.map(item => <li>item.title</li>)}} />
}
...

Child.js

state = {loading: false, data: null};

// Code for fetching data

...
render() {
  const {loading, data} = this.state;
  return this.props.render({loading, data});
}
...

or... Using a function as a child

Parent.js

...
render() {
  <Child url="user" />
    {(data) => <div>data.name</div>} 
  </Child>
  <Child url="todos">
     {(data) => {return map.(item => <li>item.title</li>)}}
  </Child>
}
...

Child.js

state = {loading: false, data: null};

// Code for fetching data

...
render() {
  const { loading, data } = this.state;
  return this.props.children({ loading, data });
}
...

Examples

1. A component that renders something, but also accepts one render prop.

class Header extends Component {
  render() {
    return (
      <div>
        <div className="left-side">Logo</div>
        <div> {this.props.renderMiddle()} </div>
        <div className="right-side">Menu items</div>
      </div>
    );
  }
}

Usage

<Header renderMiddle={() => <div> Hello user! </div>} />

2. A component that renders something, but also accepts a render prop and passes some of its state to it

class Header extends Component {
  state = {sticky: false, theme: 'dark'};
  render() {
    const {sticky, theme} = this.state;
    return (
      <div>
        <div className="left-side">Logo</div>
        <div> {this.props.render({sticky, theme})} </div>
        <div className="right-side">Menu items</div>
      </div>
    );
  }
}

Usage

<Header
  render={({sticky, theme}) => (
    <div style={{backgroundColor: theme === 'dark' ? 'black' : 'white'}}>
      <span> Sticky is: {sticky} </span>
    </div>
  )}
/>

A component that has some logic but doesn't render anything except its render prop

class FetchData extends Component {
  state = {loading: false, data: null};
  componentDidMount() {
    this.setState({loading: true});
    api.fetchData(this.props.url).then(data => {
      this.setState({data, loading: false});
    });
  }
  render() {
    const {loading, data} = this.state;
    return this.props.children({loading, data});
  }
}

Usage

<FetchData url="user/todos">
  {({loading, data}) => <div> You have {data.length} todos! </div>}
</FetchData>

Can we replace Higher Order Components with normal components that use Render Props?

Yes. It is highly possible to do so. Observe below.

HOC example

const fetchData = (url, options) => Component => {
  return class ModifiedComponent extends Component {
    state = {loading: false, data: []};
    async componentDidMount() {
      this.setState({loading: true});
      const data = await api.fetch(`api/${url}`, options);
      this.setState({data, loading: false});
    }
    render() {
      const {data, loading} = this.state;
      return <Component data={data} loading={loading} {...this.props} />;
    }
  };
};

Same example with render props

class FetchData extends Component {
  state = {loading: false, data: null};
  async componentDidMount() {
    this.setState({loading: true});
    const data = await api.fetchData(this.props.url);
    this.setState({data, loading: false});
  }
  render() {
    const {loading, data} = this.state;
    return this.props.children({loading, data});
  }
}

So why Render Props?

Render props pros

  • It's only a component
  • Explicit and transparent
  • Doesn't hide any magic
  • Using render props for composition happens dynamically during render at runtime (composition of HOCs happen at complie time)
  • Letting us take advantage of the full React lifecycle
  • Can be passed props and stata from the parent component
  • Easily composable
  • Better support for TypeScript/Flow
  • Name collisions or prop overrides cannot happen

Remember, high order components are overkill when there is composition taking place.

Real production example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment