Makes using the "render prop" technique a little easier
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
example
src
.eslintrc.js
.gitignore
.prettierrc
README.md
jest.config.js
jest.transform.js
package-lock.json
package.json
webpack.config.js

README.md

Render Prop

Demo available at ashtonsix.com/render-prop

The term "render prop" (reactjs.org/docs/render-props) refers to a simple technique for sharing code between React components using a prop whose value is a function.

This library makes using the technique a little easier. Here the "render prop" is called with the value of this.state:

import React from 'react'
import RenderProp from 'render-prop'

class TimerModel extends RenderProp {
  state = {seconds: 0}
  didMount() {
    this.interval = setInterval(() => {
      this.setState({seconds: this.state.seconds + 1})
    }, 1000)
  }
  willUnmount() {
    clearInterval(this.interval)
  }
}

class TimerView extends React.Component {
  render() {
    const {seconds} = this.props
    return <span>{seconds} seconds</span>
  }
}

const Timer = () => (
  <TimerModel render={({seconds}) => <TimerView seconds={seconds} />} />
)
Integrating with Stores

Stores help manage state. Here we subscribe to updates on a global store (via this.subscribeTo):

import React from 'react'
import RenderProp, {Store} from 'render-prop'

const CounterStore = new Store(
  (state, action) => {
    switch (action.type) {
      case 'INCREMENT':
        return {counter: state.counter + action.payload}
      default:
        return state
    }
  },
  {counter: 0}
)

class CounterModel extends RenderProp {
  state = {counter: 0}
  didMount() {
    this.update = this.update.bind(this)
    this.subscribeTo(CounterStore, this.update)
  }
  update() {
    const {counter} = CounterStore.getState()
    this.setState({counter})
  }
}

class CounterView extends React.Component {
  render() {
    const {counter} = this.props

    return (
      <div>
        Value: {counter}
        <button
          onClick={() => CounterStore.dispatch({type: 'INCREMENT', payload: 1})}
        >
          Increment
        </button>
      </div>
    )
  }
}

const Counter = () => (
  <CounterModel render={({counter}) => <CounterView counter={counter} />} />
)

Store is provided for convenience, however this.subscribeTo also works with Redux.

Using Lifecycle Methods

Models created by render-prop are a simple extension of React components. You can use props, state and lifecycle methods just like you would anywhere else. These methods are available:

  • willMount()
  • didMount()
  • willReceiveProps(nextProps)
  • shouldUpdate(nextProps, nextState)
  • willUpdate(nextProps, nextState)
  • didUpdate(prevProps, prevState)
  • willUnmount()
  • didCatch()

We've removed the word "component" from the lifecycle methods to avoid naming collisions with the library itself.

Improving Performance

We want to update our view when one of our todos changes, but not when changes occur in other (unrelated) parts of the store's state. You can optionally pass a selector (or a function, or an array of selectors) to this.subscribeTo; now the callback will only be activated when this part of the store's state changes:

import React from 'react'
import RenderProp from 'render-prop'

class TodosModel extends RenderProp {
  state = {}
  didMount() {
    this.update = this.bind.update()
    // shallow equality check on every todo in `GlobalStore.getState().todos`
    this.subscribeTo(GlobalStore, this.update, 'todos.[].{}')
  }
  update() {
    /* ... */
  }
}

The compare utility implements the selector-based comparison above. Here's how you can use it independently:

import {compare} from 'render-prop'

// returns `true` if something changed
compare(stateA, stateB, ['todos.[].{}', 'filter'])