Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/benadamstyles/pure-render-prop-component
Use the render prop pattern without sacrificing component purity
https://github.com/benadamstyles/pure-render-prop-component
react react-component react-native reactjs render-callback render-prop render-props
Last synced: 4 days ago
JSON representation
Use the render prop pattern without sacrificing component purity
- Host: GitHub
- URL: https://github.com/benadamstyles/pure-render-prop-component
- Owner: benadamstyles
- License: mit
- Created: 2018-02-16T15:18:15.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2019-05-01T16:04:21.000Z (over 5 years ago)
- Last Synced: 2024-05-01T09:20:29.768Z (8 months ago)
- Topics: react, react-component, react-native, reactjs, render-callback, render-prop, render-props
- Language: JavaScript
- Homepage:
- Size: 2.08 MB
- Stars: 6
- Watchers: 2
- Forks: 0
- Open Issues: 13
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# PureRenderPropComponent
[![npm version](https://badge.fury.io/js/pure-render-prop-component.svg)](https://www.npmjs.com/package/pure-render-prop-component)
[![Build Status](https://travis-ci.org/Leeds-eBooks/pure-render-prop-component.svg?branch=master)](https://travis-ci.org/Leeds-eBooks/pure-render-prop-component)
[![Greenkeeper badge](https://badges.greenkeeper.io/Leeds-eBooks/pure-render-prop-component.svg)](https://greenkeeper.io/)```sh
npm install --save pure-render-prop-component
# or
yarn add pure-render-prop-component
```The [render prop pattern](https://reactpatterns.com/#render-callback) in React allows us to build highly functional components, but it doesn’t play well with how React currently manages re-rendering.
```js
import React, {Component} from 'react'class CurrentTime extends Component {
state = {currentTime: Date.now()}
render() {
return this.props.children(this.state.currentTime)
}
}class App extends Component {
render() {
return (
{// *
currentTime => (
{this.props.pageTitle}
{currentTime}
)}
)
}
}
```Here, our `CurrentTime` component will re-render every time our `App` component renders, even if neither `CurrentTime`’s `state` nor its `props` (in this case, its `children` function) have changed.
However, changing `CurrentTime` to [inherit from `PureComponent`](https://reactjs.org/docs/react-api.html#reactpurecomponent) doesn’t help. [The React docs explain why](https://reactjs.org/docs/render-props.html#be-careful-when-using-render-props-with-reactpurecomponent): `PureComponent` compares `state` and `props`, and **only** if a property of `state` or of `props` has changed, does it re-render. In the above case, every time `App` re-renders, the render prop supplied to `CurrentTime` (marked `*`) is recreated. Two functions which look the same are still two different functions, so `CurrentTime#props.children` has changed, and `CurrentTime` re-renders.
We can solve this by, [as the React docs put it](https://reactjs.org/docs/render-props.html#be-careful-when-using-render-props-with-reactpurecomponent), defining the function “as an instance method”, in other words, moving the function out of our `App` component’s `render` method.
```js
import React, {Component, PureComponent} from 'react'class CurrentTime extends PureComponent {
state = {currentTime: Date.now()}
render() {
return this.props.children(this.state.currentTime)
}
}class App extends Component {
currentTimeCallback = currentTime => (
{this.props.pageTitle}
{currentTime}
)render() {
return (
{this.currentTimeCallback}
)
}
}
```Now, `currentTimeCallback` is only created once. `PureComponent` compares `props` before and after the re-render of `App`, finds that the `children` function hasn’t changed, and aborts the re-render of `CurrentTime`. Performance improved!
**But there is a big problem waiting to happen.** Our `currentTimeCallback` doesn’t just depend on the `currentTime` argument passed down from our `CurrentTime` component. It also renders `App`’s `props.pageTitle`. But with the above setup, when `pageTitle` changes, `currentTimeCallback` will not re-render. It will show **the old `pageTitle`**.
I struggled with this problem, trying all sorts of horrible hacks, until I came across [this Github issue on the React repo](https://github.com/facebook/react/issues/4136), and the [suggestion](https://github.com/facebook/react/issues/4136#issuecomment-112168425) by a React developer of a possible solution. `PureRenderPropComponent` is my implementation of that solution.
## Usage
```js
import React, {Component} from 'react'
import PureRenderPropComponent from 'pure-render-prop-component'class CurrentTime extends PureRenderPropComponent {
state = {currentTime: Date.now()}
render() {
return this.props.children(this.state.currentTime, this.props.extraProps)
// NOTE: PureRenderPropComponent also supports a prop named 'render' ☟
return this.props.render(this.state.currentTime, this.props.extraProps)
}
}class App extends Component {
render() {
return (
{(currentTime, extraProps) => (
{extraProps.pageTitle}
{currentTime}
)}
{
// NOTE: PureRenderPropComponent also supports a prop named 'render'
// (instead of 'children') ☟
}
(
{extraProps.pageTitle}
{currentTime}
)}
/>
)
}
}
```Now, our render prop will always re-render when, and **only** when, `CurrentTime#state.currentTime` or `App#props.pageTitle` change.
You can also pass other props into your render prop component and they will be treated in the same way.
```js
import React, {Component} from 'react'
import PureRenderPropComponent from 'pure-render-prop-component'class CurrentTime extends PureRenderPropComponent {
state = {currentTime: Date.now()}format(timestamp) {
return String(new Date(timestamp))
}render() {
const time = this.props.format
? this.format(this.state.currentTime)
: this.state.currentTime
return this.props.children(time, this.props.extraProps)
}
}class App extends Component {
render() {
return (
{(currentTime, extraProps) => (
{extraProps.pageTitle}
{currentTime}
)}
)
}
}
```Here, our render prop will also re-render when the boolean passed into `CurrentTime`’s `format` prop changes.
## Caveats & Assumptions
* `PureRenderPropComponent` assumes you will either use a `props.children` prop:
```js
{(val, extraProps) => }
```or a “render prop”:
```js
} />
```Using either one for a purpose other than [the render prop pattern](https://reactpatterns.com/#render-prop) will lead to unexpected behaviour, including but not limited to a stale UI due to missed renders.
## How does it work?
```js
shouldComponentUpdate(nextProps, nextState) {
const {props, state} = this
const omitKeys = ['extraProps', 'children', 'render']
return (
!shallowEqual(state, nextState) ||
!shallowEqual(omit(props, omitKeys), omit(nextProps, omitKeys)) ||
!shallowEqual(props.extraProps, nextProps.extraProps)
)
}
```