@zumper/react-redux-ephemeral-store
v0.2.1
Published
Factory methods for creating an ephemeral redux store bound to a react context and a react-redux provider
Downloads
5
Maintainers
Readme
@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.
connect
: see docs onconnect
Context
: see docs oncreateContext
Provider
: see docs onProvider
useStore
,useDispatch
anduseSelector
: see docs on hooks
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 reduxcreateStore
as the first argument.preloadedState
(any): Passed to reduxcreateStore
as the second argumentenhancer
(Function): Passed to reduxcreateStore
as the third argumentmiddleware
(Array): Passed to reduxapplyMiddleware
to create the redux store enhancer. This option is ignored if theenhancer
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)