Skip to content

Instantly share code, notes, and snippets.

@jgoux
Last active April 6, 2016 16:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgoux/d7fdf22d63fe27ca8103 to your computer and use it in GitHub Desktop.
Save jgoux/d7fdf22d63fe27ca8103 to your computer and use it in GitHub Desktop.
redux-loop helpers
import { loop, Effects as E, liftState } from "redux-loop"
import {
cond, pathEq, map, T, reduce, toUpper, pipe, transpose,
lensProp, set as lensSet, view as lensView
} from "ramda"
export const mkInit = (f) => pipe(f, liftState)
export const mkActionType = (namespace) => (type) => `${namespace}/${type}`
export const mkAction = (type, payload={}) => ({ type, payload })
// TODO: Research a better API
export const mkUpdate = (cases = []) => (model, action, parentAction=null) => {
const actionEquals = ([actionType, f]) => [pathEq(["action", "type"], actionType), f]
return pipe(
cond([
...(map(actionEquals)(cases)),
[T, ({ model }) => model]
]),
liftState
)({ model, action, parentAction })
}
export const composeUpdates = (updates) => (model, action) => {
const [reducedModel, reducedEffect] = reduce(
([model, effect], update) => {
const [m, e] = update(model, action)
return [m, [ ...effect, e]]
},
[model, []],
updates
)
return loop(reducedModel, E.batch(reducedEffect))
}
// TODO: Remove a bit of magic here, make it more flexible
export const mkChild = (namespace) => (name, childUpdate) => {
const childLens = lensProp(name)
const actionType = mkActionType(namespace)(`MODIFY_${toUpper(name)}`)
const action = (a) => mkAction(actionType, { action: a })
const update = mkUpdate([
[
actionType,
({ model, action: a }) => {
const { payload: { action: hoa } } = a
const [m, e] = childUpdate(lensView(childLens, model), hoa)
return loop(
lensSet(childLens, m, model),
E.lift(e, action)
)
}
]
])
const forwardTo = (dispatch) => (hoa) => dispatch(action(hoa))
return { actionType, action, update, forwardTo }
}
// TODO: Remove a bit of magic here, make it more flexible
export const manageChild = ({ actionType, update }) => (subUpdate) => (model, action) => {
const [m, e] = update(model, action)
if (action.type === actionType) {
const { payload: { action: hoa } } = action
const [sm, se] = subUpdate(m, hoa, action)
return loop(
sm,
E.batch([e, se])
)
}
return loop(m, e)
}
// TODO: Remove a bit of magic here, make it more flexible
export const mkListOf = (namespace) => (name, childUpdate) => {
const listLens = lensProp("list")
const actionType = mkActionType(namespace)(`MODIFY_LIST`)
const action = (id, a) => mkAction(actionType, { id, action: a })
const update = mkUpdate([
[
actionType,
({ model, action: { payload: { id, action: hoa } } }) => {
const loopList = map((item) => {
if (item.id !== id) {
return loop(item, E.none())
}
const [data, e] = childUpdate(item.data, hoa)
return loop(
{ id, data },
E.lift(e, action, id)
)
})(lensView(listLens, model))
const [ms, es] = transpose(loopList)
return loop(
lensSet(listLens, ms, model),
E.batch(es)
)
}
]
])
const forwardTo = (dispatch) => (id) => (hoa) => dispatch(action(id, hoa))
return { actionType, action, update, forwardTo }
}
// Usage of the helpers
import { loop, Effects as E } from "redux-loop"
import { find, allPass, propEq, pathEq, not, pipe, ifElse, isNil, always } from "ramda"
import { mkActionType, mkAction, mkUpdate, mkListOf, manageChild } from "helpers"
import { initialModel } from "./model"
import * as DemandeArchivee from "./components/DemandeArchivee"
const NAMESPACE = "DemandeArchiveeList"
const mkIsolatedListOf = mkListOf(NAMESPACE)
const children = {
list: mkIsolatedListOf("demandeArchivee", DemandeArchivee.update)
}
export const forwardTo = {
list: children.list.forwardTo
}
export const actionTypes = {
MODIFY_LIST: children.list.actionType
}
export const actions = {
modifyList: children.list.action
}
export const update = (model = initialModel, action) =>
manageChild(children.list)(manageList)(model, action)
// Manage = Change the parent's state based on the child's actions
const manageList = mkUpdate([
[
DemandeArchivee.actionTypes.EXPAND,
({ model, parentAction: { payload: { id } } }) => {
const collapsable = allPass([
pipe(propEq("id", id), not),
pathEq(["data", "isExpanded"], true)
])
return pipe(
find(collapsable),
ifElse(
isNil,
always(model),
(item) => loop(
model,
E.constant(actions.modifyList(item.id, DemandeArchivee.actions.collapse()))
)
)
)(model.list)
}
]
])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment