Skip to content

hamlim/react-state-reducer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React-State-Reducer

A simple (non-feature complete) clone reimplementation of Redux built using React 16.3's createContext.

Example:

import React, { Fragment } from 'react'
import createStore from 'react-state-reducer'

const INITIAL_STATE = {
  count: 0,
}

const counterReducer = action => (
  state = INITIAL_STATE,
) => {
  switch (action.type) {
    case 'INC':
      return { count: state.count + 1 }
    case 'DEC':
      return { count: state.count - 1 }
    default:
      return state
  }
}

const { Provider, Consumer } = createStore(counterReducer)

export default () => (
  <Provider>
    <Consumer>
      {({ count, dispatch }) => (
        <Fragment>
          <button onClick={() => dispatch({ type: 'DEC' })}>
            -
          </button>
          {count}
          <button onClick={() => dispatch({ type: 'INC' })}>
            +
          </button>
        </Fragment>
      )}
    </Consumer>
  </Provider>
)

API:

There are two core parts of react-state-reducer's API that should be kept in mind.

Reducers:

The first is the reducer. A reducer is a higher order function (meaning it returns a function), that first accepts an action and returns a function that accepts the current state (and optionally any props provided to the <Provider>). This returned function should then handle returning the updated state.

That was a lot of different words describing what this is, lets look at some code to walk through this.

  1. Lets start with the higher order function:
const myReducer = someAction => {
  return (state) => {
    ...
  }
}

This function accepts a single argument (someAction) and returns a function. Which in turn accepts a single argument called state. This is often written as the following:

const myReducer = action => state => { ... };
  1. Handling updates within the returned function:
const myReducer = action => state => {
  if (action === 'SOME_UPDATE') {
    return {
      ...state,
      someKey: '🆒',
    }
  }
}

Some notes with this implementation above:

  • It doesn't follow the generic Redux reducer concept of using a switch you can handle writing this function however you would like (using switch, or using an if/else)

  • Right now we are implicitly returning undefined if action does not equal 'SOME_UPDATE', while this is valid, you should make sure to at least handle returning the initial state if the action is the following shape:

{type: '@@INIT', payload: null}

What this looks like in practice:

const todoReducer = action => (state = { todos: [] }) => {
  switch (action.type) {
    case 'ADD_TODO': {
      return {
        todos: [
          ...state.todos,
          {
            id: state.todos.length,
            completed: false,
            text: action.payload,
          },
        ],
      }
    }
    case 'CHECK_TODO': {
      return {
        todos: todos.map(todo => {
          if (todo.id === action.payload) {
            return {
              ...todo,
              completed: !todo.completed,
            }
          } else {
            return todo
          }
        }),
      }
    }
    default:
      return state
  }
}

createStore:

createStore is the main export of react-state-reducer, it is a function that accepts a reducer as an argument. It calls the reducer with the initialization action:

yourReducer({ type: '@@INIT', payload: null })(null)

and then uses the result of calling the reducer as the initial state for the store. createStore then returns an object with the following:

{
  Provider: Node,
  Consumer: Node
}

Provider

The Provider component exposes two props, the first is children which can be a single component, or many components.

The second prop is onUpdate which is a function that will be called after each state update happens, it is called with the updated state.

<Provider
  onUpdate={newState => {
    /* Maybe sync state to localStorage, or something else */
  }}
>
  <App />
</Provider>

Consumer

The Consumer component has one core prop, children, children is a function that gets called with an object with the following keys:

  • dispatch
  • top level keys from state

For example if your state shape looks like this:

{
  todos: [],
  text: ''
}

the consumer children function would look like the following:

<Consumer>
  {({
    dispatch,
    todos,
    text
  }) => (
    ...
  )}
</Consumer>

Consumer also optionally supports a selector prop, which can either take in an array of functions or a single function to select your slice of state you care about:

<Consumer selector={s => s.todos}>
  {({ dispatch }, todos) => {
    /* */
  }}
</Consumer>

Redux

This library is highly influenced by all the great work put into Redux and React-Redux. For some more information on the differences between this library and Redux see redux.