Patterns

The patterns explained here are not “one or the other”. You can mix and match whatever makes sense to you and your project.

Declarative for the win 

File structure 

src/
  app/
    modules/
      ...
    actions.js
    factories.js
    sequences.js
    errors.js
    index.js
  controller.js

This pattern favors a single file for each type of composable component of a signal. This allows you to create less files and less import statements, though the individual files will have multiple definitions.

Actions 

export function actionA({ state }) {
  state.set('foo', 'bar')
}

export function actionB({ state }) {
  state.set('foo', 'bar')
}

You export multiple actions from each modules actions.js file. You will create an action or a factory for every single piece of logic. This will make your sequences more declarative, though you will need to write more custom logic.

If you prefer arrow functions, you can write:

export const actionA = ({ state }) => state.set('foo', 'bar')

export const actionB = ({ state }) => state.set('foo', 'bar')

Factories 

Factories are similar to actions:

// Normal function
export function setLoadingApp (isLoading) {
  return function setLoadingApp({ state }) {
    state.set('isLoading', isLoading)
  }
}

// Arrow function
export const setLoadingApp = (isLoading) => function setLoadingApp({ state }) {
  state.set('isLoading', isLoading)
}

Sequences 

You import all actions and factories into the sequences.js file. This will give you autosuggestions on available actions and factories. Notice in this pattern that all sequence logic is fully declarative.

import * as actions from './actions'
import * as factories from './factories'

export const initialize = [
  factories.setLoadingApp(true),
  actions.getUser,
  actions.setUser,
  factories.setLoadingApp(false)
]

Modules 

You import all your sequences into the modules file, attaching them to the signals definition.

import { Module } from 'cerebral'
import * as sequences from './sequences'
import * as errors from './errors'

export default Module({
  state: {
    isLoading: false
  },
  signals: {
    initialized: sequences.initialize
  },
  catch: [[errors.AuthError, sequences.catchAuthError]]
})

Clean and easy 

The “Declarative for the win” pattern does not include the operators of Cerebral. The reason is that operators has a cost. The cost is less declarative code in favor of less action implementations. That said operators are still declarative and it ends up being a preference choice. Also the previous pattern puts multiple definitions into one file, you might prefer separating them.

File structure 

src / app / modules / actions / factories / sequences / errors.js
index.js
controller.js

Actions 

Each action is put into its own file.

function actionA({ state }) {
  state.set('foo', 'bar')
}

export default actionA

Factories 

Factories are similar to actions:

function setLoadingAppFactory(isLoading) {
  return function setLoadingApp({ state }) {
    state.set('isLoading', isLoading)
  }
}

export default setLoadingAppFactory

Sequences 

You import individual actions and factories into the sequence file and combine them with operators.

import { set } from 'cerebral/operators'
import { state, props } from 'cerebral/tags'
import getUser from '../actions/getUser'

export const initialize = [
  set(state`isLoading`, true),
  getUser,
  set(state`user`, props`user`),
  set(state`isLoading`, false)
]

Modules 

You import all your sequences into the modules file, attaching them to the signals definition.

import { Module } from 'cerebral'
import * as sequences from './sequences'
import * as errors from './errors'

export default Module({
  state: {
    isLoading: false
  },
  signals: {
    initialized: sequences.initialize
  },
  catch: [[errors.AuthError, sequences.catchAuthError]]
})