An alternative side effect model for Redux apps
MIT License
Bot releases are hidden (Show)
The release adds 2 helper functions takeEvery
and takeLatest
to handle the common case concurrency scenarios
For the reasons behind this addition see https://github.com/yelouafi/redux-saga/issues/70#issuecomment-182401477
You can find the docs of the functions here
http://yelouafi.github.io/redux-saga/docs/basics/UsingSagaHelpers.html
http://yelouafi.github.io/redux-saga/docs/advanced/Concurrency.html
http://yelouafi.github.io/redux-saga/docs/api/index.html#saga-helpers
The path import for Effects has changed from redux-saga
to redux-saga/effects
import { take, put, call, ... } from 'redux-saga'
import { take, put, call, ... } from 'redux-saga/effects'
added a helper function isCancelError
to simplify test of Cancellation Exception
Instead of
import { isCancelError } from 'redux-saga'
function* saga() {
try {
...
} catch(error) {
if(isCance(error)
// error is an instance of SagaCancellationException
}
}
This release adds new support to dynamically running Sagas after the applyMiddleware
phase.
In previous releases, the library provided the runSaga
method to start Sagas dynamically. However, there were some issues due to how runSaga
was implemented (related issues #48, #76)
In this release, the library provides a new method (thanks to @gaearon) to attach Sagas dynamically to the Store.
(see API docs for detailed infos)
In previous releases, you had to create a storeIO
and passes it to runSaga
configureStore.js
import createSagaMiddleware from 'redux-saga'
import reducer from './path/to/reducer'
import startupSagas from './path/to/sagas'
export default function configureStore(initialState) {
// Note: passing middleware as the last argument to createStore requires redux@>=3.1.0
return createStore(
reducer,
initialState,
applyMiddleware(/* other middleware, */createSagaMiddleware(...startupSagas ))
)
}
someModule.js
import {runSaga, storeIO} from 'redux-saga'
import configureStore from './configureStore '
import dynamicSaga from './path/to/dynamicSaga'
const store = configureStore()
const task = runSaga(
dynamicSaga(store.getState),
storeIO(store)
)
Starting from this release, you export the saga middleware itself and uses its run
method
configureStore.js
import createSagaMiddleware from 'redux-saga'
import reducer from './path/to/reducer'
import startupSagas from './path/to/sagas'
// export the middleware
export const sagaMiddleware = createSagaMiddleware(...startupSagas)
export default function configureStore(initialState) {
// Note: passing middleware as the last argument to createStore requires redux@>=3.1.0
return createStore(
reducer,
initialState,
applyMiddleware(/* other middleware, */ sagaMiddleware)
)
}
someModule.js
import { sagaMiddleware } from './configureStore'
import dynamicSaga from './path/to/dynamicSaga'
sagaMiddleware.run(dynamicSaga)
cancel
methodThis release include a new generator driver core that doesnt rely on promises. This should get rid of annoying issues related to Saga missing events.
The new implementation uses only callback notification to handle synchronous actions. It means now even when you dispatch multiple actions concecutively in a synchrnous way, Sagas waiting for those action wont miss anyone. It means also that Saga starts immediately and do not miss actions fired at startup (no more requestAnimationFrame tricks are required).
For more background see the explanation on this issue. This release should fixe #50 and #55
Other Changes
yield cancel(...)
have true non blocking semantics. Once we cancel a task, we move immediately to the next step. If the cancelled task omits to catch its cancellation error, a warning message will be printed on the console (only in dev mode).process.env.NODE_ENV
set to 'development'
)createMockTask
to mock results of fork
effects. With this function, it is now possible to test generators using fork
like thisimport test from 'tape';
import { take, fork, cancel } from 'redux-saga'
import { createMockTask } from 'redux-saga/lib/testUtils'
const types = { GET_DATA: 'GET_DATA'}
function callApi() { ... }
export function* getData () {
let task
while (true) {
let { data } = yield take(types.GET_DATA)
task && task.isRunning() ? yield cancel(task) : null
task = yield fork(callApi, data)
}
}
test('getData', assert => {
const it = getData()
assert.deepEqual( it.next().value, take(types.GET_DATA) )
const mockData = {data: 'xyz'}
const mockAction = {type: types.GET_DATA, data: mockData }
// resume the generator with mockAction
// since task is null, we expect yield null
assert.deepEqual( it.next(mockAction).value, fork(callApi, mockData ) )
// mock fork result
const mockTask = createMockTask()
// resume the generator with mock task
assert.deepEqual( it.next(mockTask).value, take(types.GET_DATA) )
// simulate a task ending
mockTask.setRunning(false)
// now expect a cancel
assert.deepEqual( it.next(mockAction).value, cancel(mockTask) )
assert.end()
})
call
, cps
and fork
both support providing 'this' context for invoking instance methodstake
will now throw an error if provided with an argument and that argument is undefined. (see #35. thanks @tgriesser)Also migrated examples to webpack. All build infrastructure use webpack
LOG_EFFECT
action. You can play with the repo examples by typing store.dispatch({type: 'LOG_EFFECT'})
in the console.Below a screenshot from the counter example.
yield apply(context, fn, args)
effect for calling function with this
context (issue #27)