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

Example #2 - Dynamic Counter

This example demonstrates dynamically created actions and reducers.

As we saw in the previous example, if you render multiple instances of the same connected component, they will share the state.

The guide below shows how to create multiple instances of one component with separate states:

Count: 0
Doublecount: 0

Count: 0
Doublecount: 0

1. Key and path

The code for this example is almost the same as for the previous counter.

The big difference is that we must manually tell our component instances where to store their data:

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

@kea({
  key: (props) => props.id,

  path: (key) => ['scenes', 'counterDynamic', 'counter', key],

  // ...
})
export default class Counter extends Component {
  // ...
}

The key function receives your component instance's props as input and must return a key that's unique for the life of the component. Usually it's something like key = (props) => props.id if your component is rendered as <Component id='somethingUnique' />

The optional path function specifies where the data for your component will live in Redux. It takes just one argument, the key from the previous step.

Note! You may also use path with non-dynamic components (like the previous example) if you wish to specify where they will store their data. Manually specifying a path makes your redux tree more readable and helps with debugging. Obviously, if you will not use the key in your path, skip the key: (props) => props.id line.

2. Limiting the reducers

There's one last thing you need to do.

While the reducers are unique for each component instance, the actions are shared.

No matter which instance of your component triggers the action, all the reducers will receive it. So if counter #1 dispatches the increment action, counter #2 will also receive it.

In order to prevent this, you must check for the key in your reducers, like so:

@kea({
  key: (props) => props.id,
  path: (key) => ['scenes', 'counterDynamic', 'counter', key],

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

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

  // selectors: ...
})

This way you can choose if your actions are processed by each instance of the component or by all instances simultaneously.

Final Example

While the following counters have separate data, they are still connected to the counters on top of this page, as they share the same ids. You can mix and match!

Count: 0
Doublecount: 0

Count: 0
Doublecount: 0

Next, check out the sliders demo to see how to add side effects to your code.

Full source

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

@kea({
  key: (props) => props.id,
  path: (key) => ['scenes', 'counterDynamic', 'counter', key],

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

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

  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 CounterDynamicScene extends Component {
  render () {
    return (
      <div>
        <Counter id={1} />
        <Counter id={2} />
      </div>
    )
  }
}

Next page: Sliders