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:
Doublecount: 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
<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 id
s. You can mix and match!
Doublecount: 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