Note: this is a modified version of the original ducks proposal that also uses the Flux Standard Action specification.
A duck module...
- MUST
export
all of the following:- A
reducer
function. - An
actionCreators
object.
- A
- MAY
export
any of the following:- An
actionTypes
object. - An
initialState
object.
- An
- MUST have action types defined as
UPPER_SNAKE_CASE
constants, with each value structured as follows:package-or-app-name/duck-name/ACTION_TYPE
Import your ducks and export a combined initialState
object and reducer
function:
// store/ducks/index.js
import { combineReducers } from 'redux'
import * as widgetsDuck from './widgets'
import * as loginDuck from './login'
export const initialState = {
widgets: widgetsDuck.intialState,
login: loginDuck.initialState,
}
export const reducer = combineReducers({
widgets: widgetsDuck.reducer,
login: loginDuck.reducer,
})
Combine the ducks reducer with any third-party reducers you want to use and create your store:
// store/index.js
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { reducer as formReducer } from 'redux-form'
import { reducer, initialState } from './ducks'
const rootReducer = combineReducers({
app: reducer,
form: formReducer,
})
const enhancer = applyMiddleware(thunk)
export default createStore(rootReducer, initialState, enhancer)
// store/ducks/widgets.js
const LOAD = 'my-app/widgets/LOAD'
const CREATE = 'my-app/widgets/CREATE'
const UPDATE = 'my-app/widgets/UPDATE'
const DELETE = 'my-app/widgets/DELETE'
export const actionTypes = { LOAD, CREATE, UPDATE, DELETE }
export const actionCreators = {
loadWidgets() {
return {
type: LOAD,
}
},
createWidget(widget) {
return {
type: CREATE,
payload: widget,
}
},
updateWidget(widget) {
return {
type: UPDATE,
payload: widget,
}
},
removeWidget(widget) {
return {
type: REMOVE,
payload: widget,
}
},
}
export function reducer(state = {}, action = {}) {
switch (action.type) {
// do reducer stuff
default: {
return state
}
}
}
// store/ducks/login.js
const REQUEST = 'my-app/login/REQUEST'
const SUCCESS = 'my-app/login/SUCCESS'
const FAILURE = 'my-app/login/FAILURE'
export const actionTypes = { REQUEST, SUCCESS, FAILURE }
// Note that these are only used internally
// and therefore do not need to be exported.
function loginRequest() {
return {
type: REQUEST,
}
}
function loginSuccess(data) {
return {
type: SUCCESS,
payload: data,
}
}
function loginFailure(error) {
return {
type: FAILURE,
payload: error,
error: true,
}
}
export const actionCreators = {
// Async action creator (requires Redux Thunk middleware)
login(email, password) {
return (dispatch) => {
dispatch(loginRequest())
const fetchInit = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
}
return fetch('/login', fetchInit)
.then(data => dispatch(loginSuccess(data))
.catch(error => dispatch(loginFailure(error))
)
}
},
}
export const initialState = {
user: null,
}
export function reducer(state = initialState, action = {}) {
// do reducer stuff
default: {
return state
}
}
/**
* @jsx
* components/MyComponent/index.js
*/
import React, { PureComponent } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { actionCreators } from '../store/ducks/widgets'
import WidgetList from '../WidgetList'
function mapDispatchToProps(dispatch) {
return {
boundActionCreators: bindActionCreators(actionCreators, dispatch),
}
}
@connect(null, mapDispatchToProps)
export default class MyComponent extends PureComponent {
render() {
const { boundActionCreators } = this.props
return (
<WidgetList {...boundActionCreators} />
)
}
}