I've been using React 16.5.2 context instead of Redux in the super secret app I'm working on, and I appreciate the simplicity improvement. The learning curve is pretty minimal, and there are fewer levels of abstraction. The one thing that was annoying me was the non-optimal ergonomics of the API when consuming the context.

The first thing that was annoying was the standard consumer wrap:

import React, { Component, createContext } from 'react'

const timerContext = createContext()

class Provider extends Component {
  constructor() {
    super()
    this.state = {
      store: {
        active: false,
        stop: this.stop.bind(this)
      },
    }
  }

  start() {
    let { store } = this.state
    store = { ...store, active: true }
    this.setState({ store })
  }

  stop() {
    let { store } = this.state
    store = { ...store, active: false }
    this.setState({ store })
  }

  render() {
    return (
      <timerContext.Provider value={this.state}>
        {this.props.children}
      </timerContext.Provider>
    )
  }
}

class Timer extends Component {
  constructor () {
    super()
    this.state = {
      activeTab: 'timer'
    }
  }

  buttonClick (event) {
    const { store } = this.props.context

    if (store.active) {
      store.stop()
    } else {
      store.start()
    }

    event.stopPropagation()
  }

  changeTab (value) {
    this.setState({ activeTab: value })
  }

  render () {
    const { store } = this.props.context

    return (
      <div>
        <ul>
          <li active={this.state.activeTab === 'timer'}
               onClick={this.changeTab.bind(this, 'timer')}>
               Timer
          </li>
          <li active={this.state.activeTab === 'manual'} 
               onClick={this.changeTab.bind(this, 'manual')}>
               Manual
          </li>
        </ul>

        <button active={store.active}
                onClick={(event) => { this.buttonClick(event) }}>
          {store.active ? 'Stop' : 'Start' }
        </button>
      </div>
    )
  }
}

// Not an actual layout, provider and consumer 
// are not that near each other.
class Layout extends Component {
  render() {
    return (
     <Provider>
       { /* This is the annoying part. */ }
       <timerContext.Consumer>
         {({ store }) => (
           <Timer store={store}/>
         )}
       </timerContext.Consumer>
     </Provider>
    )
  }
}

The second thing that was annoying was the fact that I had to wrap the consumer wrap in a higher-order component, to be able to use the context outside a render:

import React, { Component, createContext } from 'react'

// Our good old context.
const timerContext = createContext()

class Provider extends Component {
  constructor() {
    super()
    this.state = {
      store: {
        active: false,
        stop: this.stop.bind(this)
      },
    }
  }

  start() {
    let { store } = this.state
    store = { ...store, active: true }
    this.setState({ store })
  }

  stop() {
    let { store } = this.state
    store = { ...store, active: false }
    this.setState({ store })
  }

  render() {
    return (
      <timerContext.Provider value={this.state}>
        {this.props.children}
      </timerContext.Provider>
    )
  }
}

class Timer extends Component {
  constructor () {
    super()
    this.state = {
      activeTab: 'timer'
    }
  }

  buttonClick (event) {
    const { store } = this.props.context

    if (store.active) {
      store.stop()
    } else {
      store.start()
    }

    event.stopPropagation()
  }

  changeTab (value) {
    this.setState({ activeTab: value })
  }

  render () {
    const { store } = this.props.context

    return (
      <div>
        <ul>
          <li active={this.state.activeTab === 'timer'}
               onClick={this.changeTab.bind(this, 'timer')}>
               Timer
          </li>
          <li active={this.state.activeTab === 'manual'} 
               onClick={this.changeTab.bind(this, 'manual')}>
               Manual
          </li>
        </ul>

        <button active={store.active}
                onClick={(event) => { this.buttonClick(event) }}>
          {store.active ? 'Stop' : 'Start' }
        </button>
      </div>
    )
  }
}

// HOC we're introducing just to
// allow usage of context outside of render.
// Not great.
const withContext = (Component) => {
  return (props) => (
    <timerContext.Consumer>
      {(context) => (
        <Component {...props} context={context}/>
      )}
    </timerContext.Consumer>
  )
}


// The Timer component is wrapped in HOC now.
const TimerWithContext = withContext(class Timer extends Component {
  constructor () {
    super()
    this.state = {
      activeTab: 'timer'
    }
  }

  buttonClick (event) {
    const { store } = this.props.context

    if (store.active) {
      store.stop()
    } else {
      store.start()
    }

    event.stopPropagation()
  }

  changeTab (value) {
    this.setState({ activeTab: value })
  }

  render () {
    const { store } = this.props.context

    return (
      <div>
        <ul>
          <li active={this.state.activeTab === 'timer'}
               onClick={this.changeTab.bind(this, 'timer')}>
               Timer
          </li>
          <li active={this.state.activeTab === 'manual'} 
               onClick={this.changeTab.bind(this, 'manual')}>
               Manual
          </li>
        </ul>

        <button active={store.active}
                onClick={(event) => { this.buttonClick(event) }}>
          {store.active ? 'Stop' : 'Start' }
        </button>
      </div>
    )
  }
})

class Layout extends Component {
  render() {
    return (
     <Provider>
       { /* No longer that annoying, but we've introduced HOC. */ }
       <TimerWithContext />
     </Provider>
    )
  }
}

I've managed to get rid of those annoyances by upgrading to the latest React16.8.2, converting my class components to function components and replacing my nasty HOC with a simple hook.

Here's the result:

import React, { useContext, useState, Component, createContext } from 'react'

// The context yet again.
// Boring.
const timerContext = createContext()

class Provider extends Component {
  constructor() {
    super()
    this.state = {
      store: {
        active: false,
        stop: this.stop.bind(this)
      },
    }
  }

  start() {
    let { store } = this.state
    store = { ...store, active: true }
    this.setState({ store })
  }

  stop() {
    let { store } = this.state
    store = { ...store, active: false }
    this.setState({ store })
  }

  render() {
    return (
      <timerContext.Provider value={this.state}>
        {this.props.children}
      </timerContext.Provider>
    )
  }
}

// The Timer class has been converted to function.
function Timer (props) {
  const { store } = useContext(timerContext)

  const [tab, setTab] = useState('timer')

  const buttonClick = (event) => {
    if (store.active) {
      store.stop()
    } else {
      store.start()
    }

    event.stopPropagation()
  }

  return (
    <div>
      <ul>
        <li active={tab === 'timer'} 
             onClick={() => setTab('timer')}>
             Timer
        </li>
        <li active={tab === 'manual'}
             onClick={() => setTab('manual')}>
             Manual
        </li>
      </ul>

      <button active={store.active} 
              onClick={(event) => { buttonClick(event) }}>
        {store.active ? 'Stop' : 'Start' }
      </button>
    </div>
  )
}

class Layout extends Component {
  render() {
    return (
     <Provider>
       { /* Very slick now */ }
       <Timer />
     </Provider>
    )
  }
}

Nothing is better than removing code, so this is a win. Upgrading to a newer version of React was worth it.

Hrvoje Šimić

@shime_sh I'm a Ruby developer focused on building MVPs with Ruby on Rails.