Sagas
Kea has first class support for sagas via the kea-saga
plugin.
Read more about Sagas on the redux-saga homepage.
Also read the sections of the guide marked "with kea-saga" to learn more.
Installation
First install the kea-saga
and redux-saga
packages:
yarn add kea-saga redux-saga
npm install --save kea-saga redux-saga
Then you have a few ways to install the plugin:
// the cleanest way
import sagaPlugin from 'kea-saga'
import { getStore } from 'kea'
const store = getStore({
plugins: [ sagaPlugin ]
})
// another way
import sagaPlugin from 'kea-saga'
import { activatePlugin } from 'kea'
activatePlugin(sagaPlugin)
// the shortest way
import 'kea-saga/install'
Use whichever way is most convenient for your setup.
Note that the kea-saga
plugin needs to be installed globally as it needs to add its middleware to the store. You can't use it as a local plugin inside kea({})
calls.
If you have configured your store through getStore()
, you're all set!
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() ...
}
FAQ
My sagas won't start with my component!
kea-saga
injects itself into your component's componentDidMount
function and starts the sagas before returning control to the original componentDidMount
.
Because of the way ES classes work, if you define your componentDidMount
as an arrow function (componentDidMount = () => {}
), it will only be declared after your class is instantiated and it will overwrite modifications by kea-saga
.
Thus, at least for now, keep your componentDidMount
as a regular function. See more details in this issue.