slikts / immer-wieder

✨ React 16 context wrap with redux semantics powered by immer

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Patreon donate button Build Status npm version

npm install immer-wieder

immer-wieder behaves like your generic react 16 context provider/consumer with the distinction that you can:

  • provide actions (which support setState reducers or Immer-drafts, where you don't need to write reducers any longer)
  • and select state (where components only render if the state they subscribe to changes).

If you look at the code, it should become clear that it lets react do all the work in order to create as little surface for maintenance and bugs as possible.

Usage

import createContext from 'immer-wieder'

const { Provider, Consumer } = createContext((setState, getState) => ({
  // Everything in here is your state
  bands: {
    0: { name: 'Flipper' },
    1: { name: 'Melt Banana' },
  },
  ids: [0, 1],
  // ... including actions
  // You can wrap and nest them, too, makes it easier to access them later ...
  actions: {
    // Actions do not have to mutate state at all, you can use getState to fetch state
    cacheState: id => getState(state => fetch(`/backend?cache=${state.stringify()}`),
    // Otherwise setState behaves like always
    removeAll: () => setState({ bands: {}, ids: [] }),
    // With the distinction that you can use Immer semantics
    changeName: (id, name) =>
      setState(state => {
        // You are allowed to mutate state in here ...
        state.bands[id].name = name
        // Or return a reduced shallow clone of state like always
        // return { ...state, users: { ...state.users, [id]: { ...state.users[id], name } } }
      }),
  },
}))

const EditDetails = ({ id }) => (
  // Select is optional, if present the component renders only when the state you select changes
  // Actions can be fetched right from the store
  <Consumer select={store => ({ ...store.bands[id], ...store.actions })}>
    {({ name, changeName }) => (
      <div>
        <h1>{name}</h1>
        <input value={name} onChange={e => changeName(id, e.target.value)} />
      </div>
    )}
  </Consumer>
)

const App = () => (
  <Provider>
    <Consumer select={store => store.ids}>
      {ids => ids.map(id => <EditDetails key={id} id={id} />)}
    </Consumer>
  </Provider>
)

Demo: Provider & Consumer

Demo: Middleware

Inline mutations using void

Draft mutations usually warrant a code block, since a return denotes a overwrite in Immer. Sometimes that can stretch code a little more than you might be comfortable with. In such cases you can use javascripts void operator, which evaluates expressions and returns undefined.

// Single mutation
setState(state => void (state.user.age += 1))

// Multiple mutations
setState(state => void ((state.user.age += 1), (state.user.height = 186)))

What about HOCs?

Sometimes you need to access render props in lifecycles or you just don't like them at all.

import createContext from 'immer-wieder'

const { Provider, hoc } = createContext((setState, getState) => ({ ... }))

@hoc((store, props) => ({ item: store.items[props.id] }))
class Item extends Component {
  render() {
    return <div>{this.props.item</div>
  }
}

const App = () => (
  <Provider>
    <Item id={1} />
  </Provider>
)

Contributions

All my open source projects are done in my free time, if you like any of them, consider helping out, all contributions are welcome as well as donations, for instance through Patreon.

About

✨ React 16 context wrap with redux semantics powered by immer

License:MIT License


Languages

Language:JavaScript 100.0%