Sagas
Kea has first class support for sagas via the kea-saga
plugin.
Read more about Sagas on the redux-saga homepage.
Breaking changes with 1.0! If you're upgrading from 0.x, please read this regarding the breaking change of automatically binding actions to dispatch in Kea. If you just connect
ed to your actions or used local actions inside a logic, everything should work as it did before as long as useLegacyUnboundActions
is set to true
. However if you were using code like yield put(otherImportedLogic.actions.doSomething())
, you need to pay attention, as those actions will now dispatch twice. Replace actions
with actionCreators
in the above code... or set useLegacyUnboundActions
to false
and get rid of yield put()
entirely.
Installation
First install the kea-saga
and redux-saga
packages:
# if you're using yarn
yarn add kea-saga redux-saga
# if you're using npm
npm install --save kea-saga redux-saga
Then you install the plugin:
import sagaPlugin from 'kea-saga'
import { resetContext } from 'kea'
resetContext({
createStore: true,
plugins: [ sagaPlugin({ useLegacyUnboundActions: false }) ]
})
Usage
First, read the docs on the redux-saga homepage to learn how sagas work.
Adding kea-saga
will give your logic stores access to the keys: start
, stop
, takeEvery
, takeLatest
, workers
, sagas
.
import { kea } from 'kea'
export default kea({
// ... see the api docs for more
start: function * () {
// saga started or component mounted
console.log(this)
},
stop: function * () {
// saga cancelled or component unmounted
},
takeEvery: ({ actions, workers }) => ({
[actions.simpleAction]: function * () {
// inline worker
},
[actions.actionWithDynamicPayload]: workers.dynamicWorker
}),
takeLatest: ({ actions, workers }) => ({
[actions.actionWithStaticPayload]: function * () {
// inline worker
},
[actions.actionWithManyParameters]: workers.dynamicWorker
}),
workers: {
* dynamicWorker (action) {
const { id, message } = action.payload // if from takeEvery/takeLatest
// reference with workers.dynamicWorker
},
longerWayToDefine: function * () {
// another way to define a worker
}
},
sagas: [saga1, saga2]
})
start: function * () {}
Saga that is started whenever the component is connected or the saga exported from this component starts
Note: sagas are started before your wrapped component's componentDidMount
. Actions dispatched before this lifecycle method will not be seen inside start
.
// Input
start: function * () {
// saga started or component mounted
console.log(this)
}
// Output
myRandomSceneLogic.saga == function * () {
// saga started or component mounted
console.log(this)
// => { actions, workers, path, key, get: function * (), fetch: function * () }
}
stop: function * () {}
Saga that is started whenever the component is disconnected or the saga exported from this component is cancelled
This function is called right before your wrapped component's componentWillUnmount
lifecycle method.
// Input
stop: function * () {
// saga cancelled or component unmounted
}
// Output
myRandomSceneLogic.saga == function * () {
try {
// start()
} finally {
if (cancelled()) {
// saga cancelled or component unmounted
}
}
}
takeEvery: ({ actions }) => ({})
Run the following workers every time the action is dispatched
Note: sagas are started before your wrapped component's componentDidMount
. Actions dispatched before this lifecycle method will not be seen by takeEvery
.
// Input
takeEvery: ({ actions, workers }) => ({
[actions.simpleAction]: function * () {
// inline worker
},
[actions.actionWithDynamicPayload]: workers.dynamicWorker
})
// Output
myRandomSceneLogic.saga == function * () {
// pseudocode
yield fork(function * () {
yield [
takeEvery(actions.simpleAction.toString(), function * () {
// inline worker
}.bind(this)),
takeEvery(actions.actionWithDynamicPayload.toString(), workers.dynamicWorker.bind(this))
]
})
}
takeLatest: ({ actions }) => ({})
Run the following workers every time the action is dispatched, cancel the previous worker if still running
Note: sagas are started before your wrapped component's componentDidMount
. Actions dispatched before this lifecycle method will not be seen by takeLatest
.
// Input
takeLatest: ({ actions, workers }) => ({
[actions.simpleAction]: function * () {
// inline worker
},
[actions.actionWithDynamicPayload]: workers.dynamicWorker
})
// Output
myRandomSceneLogic.saga == function * () {
// pseudocode
yield fork(function * () {
yield [
takeLatest(actions.simpleAction.toString(), function * () {
// inline worker
}.bind(this)),
takeLatest(actions.actionWithDynamicPayload.toString(), workers.dynamicWorker.bind(this))
]
})
}
workers: {}
An object of workers which you may reference in other sagas.
// Input
workers: {
* dynamicWorker (action) {
const { id, message } = action.payload // if from takeEvery/takeLatest
// reference with workers.dynamicWorker
},
longerWayToDefine: function * () {
// another worker
}
}
// Output
myRandomSceneLogic.workers == {
dynamicWorker: function (action) *
const { id, message } = action.payload // if from takeEvery/takeLatest
// reference with workers.dynamicWorker
}.bind(myRandomSceneLogic),
longerWayToDefine: function () * {
// another worker
}.bind(myRandomSceneLogic)
}
sagas: []
Array of sagas that get exported with this component's saga
// Input
sagas: [saga1, saga2]
// Output
myRandomSceneLogic.saga == function * () {
yield fork(saga1)
yield fork(saga2)
// start() ...
}