Never write a reducer, an action, or worry about immutability again!
- Usage (with React)
- Integrations
- A note on Middleware
- Migrating to
no-boilerplate-redux
- API
Most of the following you'll recognize from setting up Redux. This assumes you weren't using Redux previously. Please see the migration section for details on how to use vanilla reducers with no-boilerplate-redux
.
-
Install no-boilerplate-redux
npm install --save no-boilerplate-redux
-
Create your store using from
no-boilerplate-redux
in your app, and use it like redux'smakeStore
.// import store import { makeStore, makeReducer } from 'no-boilerplate-redux' // create your store so you can attach it to your app const myStore = makeStore()
If you already have reducers, you can use them like so:
import { makeStore, makeReducer } from 'no-boilerplate-redux' import { baseReducers } from './reducers' const myStore = makeStore({ reducer: makeReducer(baseReducers) })
-
Connect your React components to your state like you normally would. This causes the "magic" auto-update we're familiar with from React. (example uses
react-redux
)// Provide the store to your app. render( <Provider store={myStore}> <App /> </Provider>, document.getElementById('root') )
For your
Function
components:import { useSelector } from 'react-redux' export const MyFunctionalComponent = () => { // useSelector subscribes your component the a part of state you return from the interior function const count = useSelector(state => state.count) // ... }
For your
Class
components:import { connect } from 'react-redux' // ... const mapStateToProps = ({ count }) => ({ count }) // connect subscribes your component to the parts of state you return from mapStateToProps export default connect(mapStateToProps)(MyClassComponent)
-
Import
store
and use update and get your store./* MyComponent.jsx or MyService.js or AnythingElse.really */ import { store } from 'no-boilerplate-redux' // store() gets your global store // .set an optional path and a value (to replace state with) or a function (which should return new state) store() .set('count', count => ++count) store() .set('users["Nathaniel Hutchins"].title', 'Web Developer') store() .set(store => { store.username = "MynockSpit" return store })
Integrating with redux
(and no-boilerplate-redux
) often as simple as customizing the initial configuration. Where vanilla redux uses createStore
, no-boilerplate-redux
uses makeStore
. The parameters these two functions take are fundamentally the same. In cases where only initial configuration is need, no-boilerplate-redux is no harder to integrate with than vanilla redux.
See the docs on makeStore
for details on what's different.
Redux Dev Tools integrates by providing an enhancer. Use makeStore
's enhancer
prop to set it.
const myStore = makeStore({
enhancer: window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
});
import { applyMiddleware, compose } from 'redux';
import { makeStore } from 'no-boilerplate-redux';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
export const myStore = makeStore({
enhancer: composeEnhancers(
applyMiddleware(...middleware)
),
})
See DevTools with Redux for more info.
This example uses connected-react-router
to integrate react-router
with redux
and no-boilerplate-redux
.
import { applyMiddleware, compose } from 'redux'
import { makeStore, makeReducer } from 'no-boilerplate-redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import { createBrowserHistory } from 'history'
import { todoReducer } from './todos'
import { userReducer } from './users'
export const history = createBrowserHistory()
const reducerObject = {
todoReducer,
userReducer
}
export const myStore = makeStore({
// wrap the store w/ react-router
reducer: makeReducer({ // note that this combineReducer is imported from `no-boilerplate-redux`
router: connectRouter(history),
...reducers
}),
// enhance the store w/ react-router
enhancer: compose(applyMiddleware(routerMiddleware(history)))
})
no-boilerplate-redux sets the nbpr
property on the meta
object of your actions. If you use a middleware and overwrite the meta tag or change the nbpr
property, your no-boilerplate-redux actions won't fire. Be careful you don't overwrite or remove this tag!
// example action
{
type: "SET_DEVELOPERS",
payload: {
value: 2
path: "Nathaniel Hutchins"
},
meta: {
nbpr: 'update' // don't overwrite this!
}
}
The main difference between redux
's createStore
and no-boilerplate-redux
's makeStore
is that createStore
takes positional arguments, and makeStore
takes object arguments. See the example below for the corresponding calls in each. Similarly, we use makeReducer
instead of combineReducer
. makeReducer
is only necessary if you want to start with an object of reducers.
// redux
import { createStore, combineReducers } from 'redux'
export const myStore = createStore(
combineReducers({ todos: todoReducer, users: userReducer }), // reducer
{ todos: [], users: []}, // preloadedState
window.__REDUX_DEVTOOLS_EXTENSION__() // enhancer
)
// no-boilerplate-redux
import { makeStore, makeReducer } from 'no-boilerplate-redux'
export const myStore = makeStore({
reducer: makeReducer({ todos: todoReducer, users: userReducer }), // reducer
preloadedState: { todos: [], users: []}, // preloadedState
enhancer: window.__REDUX_DEVTOOLS_EXTENSION__() // enhancer
})
Creates the Redux store for use with no-boilerplate-redux
. See the migration section above for quirks and caveats.
[key] (String)
: A string representing the key of this Allows you to use multiple stores.
[reducer] (Function|Object)
: The reducer function. Not necessary if you have no standard Redux reducers. Identical to the reducer
argument in Redux's createStore
.
[preloadedState] (Object)
: The initial state. Identical to the preloadedState
argument in Redux's createStore
.
[enhancer] (Function)
: The store enhancer. Identical to the enhancer
argument in Redux's createStore
.
store
: the store object created. This is normally used for things like passing to a provider.
-
Do not use
redux
'scombineReducers
! UsemakeReducer
instad. If you forget and usecombineReducers
, parts of your store not in your initialreducers
object will disappear when an action is fired. Example below.// Bad import { combineReducers } from 'redux' import { makeStore } from 'no-boilerplate-redux' // Good import { makeStore, makeReducer } from 'no-boilerplate-redux'
-
If you use
combineReducers
fromno-boilerplate-redux
, all top-level keys will always be set tonull
. This is the same behavior found in vanilla redux. If you do NOTE usecombineReducers
fromno-boilerplate-redux
there is no restriction on the values you can set. -
If you want to use multiple stores, the second create call must provide a
key
property. Multiple stores is not recommended.
import { makeStore } from 'no-boilerplate-redux'
const myStore = makeStore()
// use store e.g. in a react-redux <Provider> component
import { makeStore, makeReducer } from 'no-boilerplate-redux'
import baseReducers from './reducers' // an object of [key]: [reducer fn]
const myStore = makeStore({
reducer: makeReducer(baseReducers)
})
// create a new store with vanilla reducer for 'albums' and default values for 'albums' and 'artists'
import { makeStore, makeReducer } from 'no-boilerplate-redux'
import albums from './albums/reducer'
let myReducers = { albums: albums }
let myPreloadedState = {
albums: [{
title: 'Talking Heads: 77',
artist: 'Talking Heads',
released: 'September 16, 1977'
}, {
title: 'Little Creatures',
artist: 'Talking Heads',
released: 'June 10, 1985'
}],
artists: {
'Talking Heads': {
formed: '1975',
activeUntil: '1991'
}
}
}
const myStore = makeStore({
reducer: makeReducer(myReducers),
preloadedState: myPreloadedState
})
// use store e.g. in a react-redux <Provider> component
import { makeStore, makeReducer } from 'no-boilerplate-redux'
import myReducers from './reducers'
import { applyMiddleware, compose } from 'redux'
import { routerMiddleware, connectRouter } from 'connected-react-router'
const myStore = makeStore({
reducer: connectRouter(history)(makeReducer(myReducers)),
enhancer: compose(applyMiddleware(routerMiddleware(history)))
})
// use store e.g. in a react-redux <Provider> component
reducers (Object)
: An object whose values correspond to different reducer functions that need to be combined into one. One handy way to obtain it is to use ES6 import * as reducers
syntax. The reducers may never return undefined for any action. Instead, they should return their initial state if the state passed to them was undefined, and the current state for any unrecognized action. Identical to the reducer object you'd pass into redux
's combineReducers
.
- @param {Function} [combiner] A function used to combine the reducers object into
[combiner] (Function)
: A function used to combine the reducers in thereducers
object. If not set, defaults toredux
'scombineReducers
.
reducer
(Function): A reducer function that invokes every reducer inside the passed object, adds a null reducer for state keys not provided in the initial object, and builds a state object with the same shape.
// rootReducer.js
import { makeReducer } from 'no-boilerplate-redux'
import albumsReducer from './albums'
import artistsReducer from './artists'
import songsReducer from './songs'
export const rootReducers = makeReducer({
albums: albumsReducer,
artists: artistsReducer,
songs: songsReducer
})
Get a store. While it is possible to use the generated store directly, this is the recommended way to interact with stores b/c it prevents store from becoming singletons.
[storeKey] (string OR Array)
: The name of the store you want to access. If not provided, returns the default global store.
storeObject
: the store object with chaining methods (see below)
import { store } from 'no-boilerplate-redux'
const storeObject = store()
Sets the store to an arbitrary value. Takes an optional path to scope the changes. NOTE: It is recommended to use store()
(see above) to get reference your store. All examples use this method.
[path] (string OR Array)
: The lodash-style path (string or array) representing where in the store to modify data. If not specified, the selection is the entire store.
payload (value OR Function)
: If this is a value, replace the selected state with this value. If this is a function, it is run, and the value returned is the new state. Function is passed the old state as an argument.
// initial store = {
// developers: null
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
// replace the entire developers store
storeObject.set({
developers: {
"1": { name: "Nathaniel", title: "Web Developer" }
"2": { name: "Eddie", title: "Web Developer" }
}
})
// final store = {
// developers: {
// "1": { name: "Nathaniel", title: "Web Developer" }
// "2": { name: "Eddie", title: "Web Developer" }
// }
// }
// initial store = {
// todos: [
// { text: "write documentation", complete: true },
// { text: "add support for middleware", complete: false },
// { text: "take a break from writing code", complete: false }
// ]
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
// remove completed todos from the store
storeObject.set(store => {
store.todos = store.todos.filter((todo) => !todo.complete)
return store
})
// final store = {
// todos: [
// { text: "add support for middleware", complete: false },
// { text: "take a break from writing code", complete: false }
// ]
// }
// initial store = {
// todos: [
// { text: "write documentation", complete: true },
// { text: "add support for middleware", complete: false },
// { text: "take a break from writing code", complete: false }
// ]
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
// entirely re-write store
storeObject.set(store => {
return {
username: "MynockSpit"
}
})
// final store = {
// todos: null,
// username: "MynockSpit"
// }
Sets the store to an arbitrary value using an action / action creator. Takes an optional path to scope the changes.
[path] (string OR Array)
: The lodash-style path (string or array) representing where in the store to modify data. If not specified, the selection is the entire store.
action (Object OR Function)
: If this is an Object, treat it like a redux action, and fire it. If this is a Function, treat it like a action creator, run it, then fire the resulting action. Function is passed the old state as an argument.
// initial store = {
// developers: null
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
// replace the entire developers store
storeObject.action({
type: "ADD_NATHANIEL_EDDIE",
payload: {
developers: {
"1": { name: "Nathaniel", title: "Web Developer" }
"2": { name: "Eddie", title: "Web Developer" }
}
}
})
// action type = "ADD_NATHANIEL_EDDIE"
// final store = {
// developers: {
// "1": { name: "Nathaniel", title: "Web Developer" }
// "2": { name: "Eddie", title: "Web Developer" }
// }
// }
// initial store = {
// todos: [
// { text: "write documentation", complete: true },
// { text: "add support for middleware", complete: false },
// { text: "take a break from writing code", complete: false }
// ]
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
// remove completed todos from the store
storeObject.action(store => {
store.todos = store.todos.filter((todo) => !todo.complete)
return {
type: "DELETE_COMPLETED_TODOS",
payload: store
}
})
// action type = "DELETE_COMPLETED_TODOS"
// final store = {
// todos: [
// { text: "add support for middleware", complete: false },
// { text: "take a break from writing code", complete: false }
// ]
// }
// initial store = {
// todos: [
// { text: "write documentation", complete: true },
// { text: "add support for middleware", complete: false },
// { text: "take a break from writing code", complete: false }
// ]
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
// entirely re-write store
// type is not required
storeObject.action(store => {
return {
payload: {
username: "MynockSpit"
}
}
})
// final store = {
// username: "MynockSpit"
// }
Get the store, or a part of the store.
[path] (string OR Array)
: The lodash-style path (string or array) representing where in the store to look for state. If not specified, the entire store is returned.
[defaultValue] (*)
: The value to default to if there is no value at the path. Only valid if a path is specified.
*
: The value at the store
// developers: {
// "1": { name: "Nathaniel", title: "Web Developer" }
// "2": { name: "Eddie", title: "Web Developer" }
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
storeObject.get()
// Note, the above is identical to store().get()
{ developers: {
"1": { name: "Nathaniel", title: "Web Developer" }
"2": { name: "Eddie", title: "Web Developer" }
} }
// developers: {
// "1": { name: "Nathaniel", title: "Web Developer" }
// "2": { name: "Eddie", title: "Web Developer" }
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
storeObject.get('developers[1]')
{ name: "Nathaniel", title: "Web Developer" }
// developers: {
// "1": { name: "Nathaniel", title: "Web Developer" }
// "2": { name: "Eddie", title: "Web Developer" }
// }
import { store } from 'no-boilerplate-redux'
const storeObject = store()
storeObject.get('developers[4]', 'DEFAULT VALUE')
'DEFAULT VALUE'