@zumper/react-redux-ephemeral-store

0.2.1 • Public • Published

@zumper/react-redux-ephemeral-store

Factory methods for creating an ephemeral redux store bound to a react context and a react-redux provider.

If you are excited about the possibilities of using the useReducer hook with createContext then this is for you.

Why?

The primary use-case is to create a redux store that is only available to a specific react component tree. It's called an "ephemeral store" because it only lives as long as the <Provider /> is mounted (just like useReducer).

Why use redux + react-redux instead of useReducer + createContext?

We wanted to share state between some components using redux-like code patterns but our global store was not a good fit. Initially we turned to useReducer and createContext. Later we switched to useEnhancedReducer to enable middleware, like redux-thunk.

Next, we recognized that useReducer provided functionality that looked a lot like a redux store. We started providing our fake redux store using createContext but we needed a good way to connect to it from within our component tree. We were able to wrap it up into a store-like object, which was compatible with react-redux.

Since version 6, react-redux has supported an option to connect to a custom context. Rather than reinvent the wheel, we decided to simply use react-redux with our custom context and fake-store. Since version 7.1.1, react-redux allows for binding the provided hooks with a custom context. Now we had hooks!

Finally, we realized that making a fake redux store with useReducer was way harder than just using redux createStore.

Relying on redux and react-redux under the hood provided us with some significant performance gains and allowed us to keep our existing coding patterns.

NOTE: If you want share a store with your entire app, just use react-redux.

Install

yarn add @zumper/react-redux-ephemeral-store

Usage

You can use the createEphemeralStoreProvider to create a version of react-redux that is bound to a custom context and creates a new store every time the Provider is mounted.

It returns a bound version of the core react-redux interface.

import { createEphemeralStoreProvider } from '@zumper/react-redux-ephemeral-store'

export const {
  connect,
  Context,
  Provider,
  useStore,
  useDispatch,
  useSelector,
} = createEphemeralStoreProvider({
  reducer,
  preloadedState,
})

API

createEphemeralStoreProvider(options)

Creates an ephemeral store provider and returns a react-redux interface that is bound to a custom context. The options you provide are used to create the redux store when the Provider is mounted.

options

  • reducer (Function): Passed to redux createStore as the first argument.
  • preloadedState (any): Passed to redux createStore as the second argument
  • enhancer (Function): Passed to redux createStore as the third argument
  • middleware (Array): Passed to redux applyMiddleware to create the redux store enhancer. This option is ignored if the enhancer option is provided.
  • context (Context): Optional; If you do not pass in a context it will be created automatically

enhancer vs. middleware

It is expected that most ephemeral stores will have simple needs and will probably enjoy the convenience of using the middleware option. However, if you have advanced needs you can use the enhancer option to do anything you normally could with redux.


Example

We can create a custom Provider that binds a custom context to react-redux and creates an ephemeral store when it is first mounted.

Step 1: Create a <MyProvider />

The main point here is to create a Provider that makes a reducer available to a component tree. You can use any reducer and preloadedState that you would with useReducer or createStore.

You can see below that we are using the middleware option to apply redux-thunk middleware to our store. You can supply any middleware that is compatible with applyMiddleware.

NOTE: You should rename the connectFoo and FooProvider variables to suit your needs. You can name them whatever you please. We chose My in these examples for simplicity.

// myStoreProvider.js
import { createEphemeralStoreProvider } from '@zumper/react-redux-ephemeral-store'
import thunk from 'redux-thunk'

import { reducer, preloadedState } from './module'

export const {
  connect: connectMy,
  Context: MyContext,
  Provider: MyProvider,
  useStore: useMyStore,
  useDispatch: useMyDispatch,
  useSelector: useMySelector,
} = createEphemeralStoreProvider({
  reducer,
  preloadedState,
  middleware: [thunk],
})

Step 2: Wrap a component tree with your <MyProvider />

Then we can provide access to the store to our component tree.

// MySection.jsx
import React from 'react'
import { MyProvider } from './myStoreProvider'
import { DeepContainer } from './DeepContainer'

export const MySection = () => {
  return (
    <MyProvider>
      <DeepContainer />
    </MyProvider>
  )
}

Step 3: Use connectMy to interface with your ephemeral store

Finally, we can connect our deeply nested components to the ephemeral store.

// DeepContainer.js
import { connectMy } from './myStoreProvider'
import { Deep } from './Deep'

const mapMyStateToProps = (state) => {
  return {
    foo: state.foo,
  }
}

const mapMyDispatchToProps = (dispatch) => {
  return {
    onClick: () => dispatch({ type: 'FOO', payload: 'bar' }),
  }
}

export const DeepContainer = connectMy(mapMyStateToProps, mapMyDispatchToProps)(
  Deep
)

Using hooks

React-redux now provides a hooks interface. Starting from version 7.1.1 it is possible to bind react-redux hooks to a custom context.

Below you can see a simple example of using the custom useMyStore hook we created above.

Read more about using hooks with react-redux.

import React, { useContext } from 'react'
import { useMyStore } from './myStoreProvider'

export const Deep = () => {
  const myStore = useMyStore()
  const state = myStore.getState()
  const value = state.foo
  return <div>{value}</div>
}

Bonus: mixing with global react-redux

You can also mix connect with your custom connectMy.

// DeepContainer.js
import { compose } from 'redux'
import { connect } from 'react-redux'
import { connectMy } from './myStoreProvider'
import { Deep } from './Deep'

// connects to global redux store
const mapStateToProps = (state) => {
  return {
    bar: state.bar,
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onClick: () => dispatch({ type: 'BAR', payload: 'baz' }),
  }
}

// connects to ephemeral store
const mapMyStateToProps = (state) => {
  return {
    foo: state.foo,
  }
}

const mapMyDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: (event) => {
      // we can intercept and re-bind props
      ownProps.onClick(event)
      dispatch({ type: 'FOO', payload: 'bar' })
    },
  }
}

export const DeepContainer = compose(
  connect(
    mapMyStateToProps,
    mapMyDispatchToProps
  ),
  connectMy(mapMyStateToProps, mapMyDispatchToProps)
)(Deep)

Package Sidebar

Install

npm i @zumper/react-redux-ephemeral-store

Weekly Downloads

0

Version

0.2.1

License

MIT

Unpacked Size

35 kB

Total Files

20

Last publish

Collaborators

  • dloehr