A simple technique for sharing code between React components using a prop where its value is a function
<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
/>
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.
class Header extends Component {
render() {
const {leftSide, title, renderRightSide} = this.props;
return (
<div>
{leftSide}
<div>{title}</div>
{renderRightSide({user: {name: 'Kristijan'}})}
</div>
);
}
}
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});
}
...
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 });
}
...
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>
);
}
}
<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>
);
}
}
<Header
render={({sticky, theme}) => (
<div style={{backgroundColor: theme === 'dark' ? 'black' : 'white'}}>
<span> Sticky is: {sticky} </span>
</div>
)}
/>
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});
}
}
<FetchData url="user/todos">
{({loading, data}) => <div> You have {data.length} todos! </div>}
</FetchData>
Yes. It is highly possible to do so. Observe below.
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} />;
}
};
};
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});
}
}
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.