This documentation is for the old Kea 1.0. To see the latest docs for 2.0, click here!

kea(input)

Create a new kea logic.

Usage

Here is a complete example with all the options available.

import { kea } from 'kea'
import PropTypes from 'prop-types'

import logicToMount from './logicToMount'
import otherLogic from './other-logic'
import logicWithKey from './logic-with-key'

const actionCreatorsObject = {
  someAction: (id) => ({ type: 'bla', payload: { id } })
}

const logic = kea({
  // Connect to other logic or plain Redux and import its actions and values
  // to be used inside this logic.
  // connect can be a regular object (connect: {}) or a function that takes props
  // as an input (connect: props => ({}))
  connect: ({ id }) => ({
    actions: [
      otherLogic, [
        // otherLogic.actions.firstAction --> logic.actions.firstAction
        'firstAction',
        // otherLogic.actions.secondAction --> logic.actions.renamedAction
        'secondAction as renamedAction'
      ],
      actionCreatorsObject, [
        // take someAction from a regular object and wrap it with dispatch()
        'someAction'
      ]
    ],

    values: [
      otherLogic, [
        // otherLogic.values.firstProp --> logic.values.firstProp
        'firstProp',
        // otherLogic.values.secondProp --> logic.values.renamedProp
        'secondProp as renamedProp',
        // { ...otherLogic.values } --> logic.values.allProps
        '* as allProps'
      ],

      // if a logic uses a key to initialize independent instances, pass some
      // props to the logic you're connecting to
      logicWithKey({ id }), [
        'dynamicProp as thatProp'
      ],

      // select from any redux tree node
      state => state.something.that.resolves.to.an.object, [
        'variable',
        'otherVariable'
      ]
    ],

    // in case you want to connect a logic and mount it when this one gets mounted,
    // without importing any values (for example to start some listeners, etc),
    // then connect it like so:
    logic: [logicToMount]
  }),

  // you may *optionally* name your logic and specify where it ends up in redux:
  path: () => ['scenes', 'myRandomScene', 'index'],

  // if you wish to instantiate many independent copies of the same logic, use
  // the key attribute and take the unique key from the props, then *optionally*
  // pass it to the path
  key: props => props.id,
  path: key => ['scenes', 'myRandomScene', 'index', key],

  // you can create constants that will be accessible later in the "constants"
  // object in the format { STRING: 'STRING', OTHER_STRING: 'OTHER_STRING' }
  constants: () => ['STRING', 'OTHER_STRING'],

  // there are a few ways to define your actions
  actions: () => ({
    actionWithStaticPayload: 'payload value',
    anotherActionWithAStaticPayload: { thisIs: 'that' },
    simpleAction: true,

    actionWithDynamicPayload: (id) => ({ id }),
    actionWithManyParameters: (id, message) => ({ id, message }),
    actionWithObjectInput: ({ id, message }) => ({ id, message })
  }),

  // Defaults are specified as the first parameter to the reducers.
  // However you can have a separate "defaults" key that overrides that.
  // Feel free to use selectors in any way you like in this object.
  // Some examples follow:
  defaults: {
    reducerKey: 'yes please'
  },

  defaults: ({ selectors }) => ({
    reducerKey: selectors.firstProp
  }),

  defaults: ({ selectors }) => state => ({
    reducerKey: selectors.allProps(state).firstProp
  }),

  // Reducers store data in logic.
  // You must specify the default value and any actions that change it.

  // Optionally you can also give each reducer a PropType and it will be automatically
  // injected into React class-based Components. This is not useful if you're using hooks.

  // You can also give an options object as the last parameter before the actions.
  // Some plugins (e.g. localStorage) use it to manipulate the reducers
  reducers: ({ actions, constants, props, selectors }) => ({
    reducerKey: ['defaultValue', /* PropTypes.string, */ /* { optoins }, */ {
      // Each action gets 3 parameters: (state, payload, meta)
      // - state = the current value in the reducer
      // - payload = the action.payload
      // - meta = optionally the action.meta value

      // Reducers must NEVER modify the existing object
      // DO __NOT__ DO THIS: (state, payload) => { state[payload.key] = payload.value }

      // They must always return a new object:
      // Do this: (state, payload) => ({ ...state, [payload.key]: payload.value })
      [actions.simpleAction]: (state, payload) => state + payload.value, // return the new state,
      [actions.complexAction]: (state, payload) => {
        // do more things in the block
        return state + payload.value
      },
      [actions.noStateUsed]: (_, payload) => payload.value,
      [actions.setToTrue]: () => true,
      [actions.clearSomething]: () => false,
      'ANY_OTHER_ACTION_TYPE': (state, payload, meta) => 'do whatever you want'
    }],

    // The defaults for reducers can come from props (if you're using a key and passing props)
    defaultFromProps: [props.id, PropTypes.number, {
      [actions.clearSomething]: () => constants.STRING,
      [actions.someOtherAction]: (_, payload) => payload.value
    }],

    // ... or from a selector that you have connected to
    defaultFromSelectors: [selectors.otherValable, PropTypes.number, {
      [actions.clearSomething]: () => constants.STRING,
      [actions.someOtherAction]: (_, payload) => payload.value
    }],

    // ... or from a constant
    constantDefault: [constants.OTHER_STRING, PropTypes.string, {
      [actions.clearSomething]: () => constants.STRING,
      [actions.someOtherAction]: (_, payload) => payload.value
    }]
  }),

  // Selectors take the input of one or more selectors (created automatically for reducers)
  // and return a combined output. Selectors are recalculated only if one of the inputs
  // changes.
  selectors: ({ selectors }) => ({
    computedValue: [
      () => [
        selectors.reducerKey,
        selectors.constantDefault,
        state => state.variable.from.redux,
        (_, props) => props.id // you can access props like this
      ],
      (reducerKey, constantDefault, variable, id) => {
        return expensiveComputation(reducerKey, constantDefault, variable, id)
      },
      PropTypes.object
    ]
  }),

  // Finally, you can hook into the moments when the logic is either
  // mounted onto redux or unmounted from it:
  events: ({ actions, values }) => ({
    beforeMount () {
      // values.reducerKey is a shorthand for selectors.reducerKey(state)
      // It's actually a getter function that returns the value at this moment.
      // Using "values.reducerKey" at a later state might return a different result.
      if (values.reducerKey === 'defaultValue') {
        actions.simpleAction()
      }

      // In case you want to capture a snapshot of the values as they currently are,
      // never use the "values" object directly. Instead make a copy of its data:
      // -> const params = { ...values }  // capture all of them
      // -> const { reducerKey } = values // just capture one
    },
    afterMount () {},
    beforeUnmount () {},
    afterUnmount () {}
  })
})