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

Example #1 - Counter

Let's start with the most basic of all examples: creating a counter that we can increment and decrement with the push of a button!

The final result should look something like this:

Count: 0
Doublecount: 0

Click the buttons to make sure it works! And then let's begin!

1. Connecting @kea

Everything starts with importing { kea } into your application:

import { kea } from 'kea'

... and connecting it to your React component:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { kea } from 'kea'

@kea({
  // TODO: code goes here
})
export default class Counter extends Component {
  render () {
    // TODO: make these work
    const counter = 0
    const doubleCounter = 0
    const increment = () => {}
    const decrement = () => {}

    return (
      <div className='kea-counter'>
        Count: {counter}
        <br />
        Doublecount: {doubleCounter}
        <br />
        <button onClick={() => increment(1)}>Increment</button>
        <button onClick={() => decrement(1)}>Decrement</button>
      </div>
    )
  }
}

2. Actions, reducers, selectors, oh my!

Kea is built as a wrapper on top of redux, reselect and redux-saga.

We strongly recommend you understand the basics of Redux before continuing, as kea liberally borrows concepts from it.

2.1. Actions

The first thing we do is define our actions:

// counter/index.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { kea } from 'kea'

@kea({
  actions: () => ({
    increment: (amount) => ({ amount }),
    decrement: (amount) => ({ amount })
  }),
  // TODO: add things here
})
export default class Counter extends Component {
  render () {
    const { increment, decrement } = this.actions

    // TODO: make these work
    const counter = 0
    const counter = 0

    return (
      <div className='kea-counter'>
        Count: {counter}
        <br />
        Doublecount: {doubleCounter}
        <br />
        <button onClick={() => increment(1)}>Increment</button>
        <button onClick={() => decrement(1)}>Decrement</button>
      </div>
    )
  }
}

An action in kea is simply a function that takes some arguments and returns a payload. In this case the functions take one argument (amount - the amount to increment with) and return an object with it as the only key/value.

2.2. Reducers

Now that we can dispatch actions, we need to define reducers. This is where your data lives.

Reducers have an initial state and define actions which change this state. They may also include an optional propType.

The latest state of all of the reducers will be passed to your component as props.

Here's an example:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { kea } from 'kea'

@kea({
  actions: () => ({
    increment: (amount) => ({ amount }),
    decrement: (amount) => ({ amount })
  }),

  reducers: ({ actions }) => ({
    counter: [0, PropTypes.number, {
      [actions.increment]: (state, payload) => state + payload.amount,
      [actions.decrement]: (state, payload) => state - payload.amount
    }]
  }),

  // TODO: more code here?
})
export default class Counter extends Component {
  render () {
    const { counter } = this.props
    const { increment, decrement } = this.actions

    // TODO: is there a better way? we need to recompute this every time we render!
    const doubleCounter = counter * 2

    return (
      <div className='kea-counter'>
        Count: {counter}
        <br />
        Doublecount: {doubleCounter}
        <br />
        <button onClick={() => increment(1)}>Increment</button>
        <button onClick={() => decrement(1)}>Decrement</button>
      </div>
    )
  }
}

The most important thing to remember about reducers is that they must consist of pure functions.

This means that your reducer must:

  1. always return the same output for the same input
  2. never change the input! (e.g. when adding something to an array, create and return a new array)
  3. never have any other side effects, such as network requests (we'll get to them later in this guide)

If you understand how reducers work in redux, you should feel right at home here.

2.3. Selectors

The final piece of the puzzle is selectors.

Selectors (via reselect) take as input other selectors and return some output. Selectors are cached and are only updated if their input changes. You should use them to run complex logic that is too costly to run in render() every time.

Adding selectors gives us this final piece of code:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { kea } from 'kea'

@kea({
  actions: () => ({
    increment: (amount) => ({ amount }),
    decrement: (amount) => ({ amount })
  }),

  reducers: ({ actions }) => ({
    counter: [0, PropTypes.number, {
      [actions.increment]: (state, payload) => state + payload.amount,
      [actions.decrement]: (state, payload) => state - payload.amount
    }]
  }),

  selectors: ({ selectors }) => ({
    doubleCounter: [
      () => [selectors.counter],
      (counter) => counter * 2,
      PropTypes.number
    ]
  })
})
export default class Counter extends Component {
  render () {
    const { counter, doubleCounter } = this.props
    const { increment, decrement } = this.actions

    return (
      <div className='kea-counter'>
        Count: {counter}<br />
        Doublecount: {doubleCounter}<br />
        <button onClick={() => increment(1)}>Increment</button>
        <button onClick={() => decrement(1)}>Decrement</button>
      </div>
    )
  }
}

The selectors are defined like this:

selectors: ({ selectors }) => ({
  // add as many as you want
  nameOfSelector: [
    // input selectors
    () => [selectors.firstOtherSelector, selectors.secondOtherSelector],
    // calculate the result
    (firstOther, secondOther) => firstOther + secondOther,
    // type of the result
    PropTypes.number
  ]
})

Note that kea automatically creates selectors for all the reducers that you have defined, so you can use selectors.reducerName immediately as input.

Final Example

Adding all of this together and rendering two <Counter />s gives us the following result:

Count: 0
Doublecount: 0

Count: 0
Doublecount: 0

You might notice that something odd is happening: Incrementing one counter automatically increments the other. Is there something we can do about this?

Yes. That's what the next example is all about: Dynamic counters

Full source

// counter/index.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { kea } from 'kea'

@kea({
  actions: () => ({
    increment: (amount) => ({ amount }),
    decrement: (amount) => ({ amount })
  }),

  reducers: ({ actions }) => ({
    counter: [0, PropTypes.number, {
      [actions.increment]: (state, payload) => state + payload.amount,
      [actions.decrement]: (state, payload) => state - payload.amount
    }]
  }),

  selectors: ({ selectors }) => ({
    doubleCounter: [
      () => [selectors.counter],
      (counter) => counter * 2,
      PropTypes.number
    ]
  })
})
export default class Counter extends Component {
  render () {
    const { counter, doubleCounter } = this.props
    const { increment, decrement } = this.actions

    return (
      <div className='kea-counter'>
        Count: {counter}
        <br />
        Doublecount: {doubleCounter}
        <br />
        <button onClick={() => increment(1)}>Increment</button>
        <button onClick={() => decrement(1)}>Decrement</button>
      </div>
    )
  }
}

// index.js
export default class CounterSingletonScene extends Component {
  render () {
    return (
      <div>
        <Counter />
        <Counter />
      </div>
    )
  }
}

Next page: Dynamic counters