I'm trying to wrap my head around Redux or state management in general for the front-end applications.
As far as I know, there are three basic libraries to create complex actions logic: redux-thunk, redux-saga and redux-observable.
I'm using redux-thunk to create a chain of the async operations, but I found them a little bit inappropriate: action creators should create actions, not functions.
In order to get around this, I've created actions (simple action creators) and operations (thunks) with redux-toolkit, but I still see them a little bit confusing:
reducers.js (they also represent actions):
export function add(state, action) { ... }
export function added(state, action) { ... }
operations.js (they are performing complex actions logic):
export function addItem(payload) {
return async (dispatch) => {
dispatch(actions.add(payload))
const { data } = await Items.create(action.payload);
dispatch(actions.added(payload))
}
}
It looks ok, but I can see myself trying to dispatch add directly in the future.
Using redux-saga seems unnatural thanks to the generator syntax. I don't have to say anything about redux-observable, it's over-complicated for this simple task.
So I tried to use simple custom middleware for this kind of work, but I don't really know if it's a "good practice" or "bad practice". However, it allows to use simple actions to fire an observer that dispatches matching function:
middlewares/observer.js (simplified):
let listeners = {}
export const observer = store => next => action => {
const result = next(action)
if (action.type in listeners) {
listeners[action.type](store.dispatch, action)
}
return result
}
export const createListener = (action, listener) => {
listeners[action.type] = listener
}
Code above allows to write "observers" / "listeners" like below:
createListener(actions.add, async (dispatch, action) => {
const { data } = await Items.create(action.payload)
dispatch(actions.added(data))
})
...which allows to dispatch callback attached to the listened actions. Looks very simple and clean for me.
Is this a bad way to solve this problem?
I'm a Redux maintainer and creator of Redux Toolkit.
Thunks exist to allow you to move complex synchronous and semi-complex async logic outside of components. Components typically use action creators to avoid knowing the details of how to define a given action object. Thunk action creators exist to provide parallel syntax, allowing components to kick off logic without needing to know the details of whether it's a simple action dispatch or something more complex.
That said, thunks do not give you a way to respond to dispatched actions, because the only thing the thunk middleware does is look to see if you've passed a thunk function to dispatch, and if so, call it. Sagas and observables both provide APIs that let you run additional logic in response to dispatched actions. See the Redux FAQ entry on "how do I choose between thunks, sagas, and observables?" for more details.
The middleware you've just shown there is a typical example of a simple "action listener" middleware - really a much simpler version of what sagas and observables let you do. In fact, we're hoping to add a similar middleware to Redux Toolkit, but haven't done so yet.
So, yes, the middleware itself is a valid tool to create, but you haven't provided sufficient information on what specific problem you're trying to solve, so I can't say whether it's an appropriate tool for your problem.
Related
react version is 16.13.1.
I wondering if there are some benefits to use redux-saga for async methods.
const component = () => {
const asyncFunc = async() => { // <- this part should be moved out to redux-saga?
await callMethod();
}
return (
<div onClick={asyncFunc}>button</div>
)
}
I have no idea that asyncFunc should be called in redux-saga or in react component.
Which is better or more beneficial?
In my opinion, I prefer to call async method in components.
In simpler words redux-saga is beneficial in the case where we need to achieve some async operation during a redux action.
Now what you are doing is handling the side effect in the component so the action you'll dispatch will only update the store.
It is a very simple use case where you handled it in the component, consider a scenario where you need this same functionality from 2 different components.. you will have to copy the logic in 2 different components.
The testing will become difficult.
Now consider the same scenario again but the problem is since you can trigger the API calls from 2 components, let's consider a scenario that the user triggered the API call from both the components simultaneously, it is wastage of resource to handle both the API calls if the first API call is still pending.
for all this scenario redux-saga provide methods like takeLatest, takeEvery etc.
the benefit of using almost each and everything of redux is to organize the code and keep all the states in store, if you use async function in one component and by chance you want to use that async function again for some other component then you have to write the entire code again and again , in case of redux-saga you will write async one time and can call that action anywhere in your whole react project, for now you might be creating 5-10 components but it might be possible that in future you will create 5000 components at that time redux and its middlewares come into play .
Redux-saga is a middleware to act on an action before it reaches the reducer.
Basically, all side effects will be handled in the middleware and gives you more control over the effects.
This way, it has clear separation of concerns that the middleware is going to handle the side effects and not the component. A saga is not dependent on the lifetime of a component.
In a saga, fetch will look something like this:
function* fetchItems(action) {
try {
const result = yield call(axios.post, ...);
yield put ({ type: 'FETCH_SUCCESS', payload: { result } });
} catch (e) {
yield put ({ type: 'FETCH_FAILED', error: { msg: e } });
}
}
yield takeEvery(FETCH_ITEMS, fetchItems);
However for complex systems with background processing, you can implement different patterns that uses fork() and cancel()
function* doSync() {}
function* main() {
while ( yield take(START_SYNC) ) {
const task = yield fork(doSync) // returns a task
yield take(STOP_SYNC)
yield cancel(task) // cancel a task if syncing is stopped
}
}
Thus, all that said, redux-saga's power lies when your system is getting more complex and event-driven.
I am building a React-Redux project and I am trying to be idiomatic about my usage of Redux, and avoid hacking things together in a way that makes the code difficult to maintain later. I am also new to this ecosystem.
I have a nested state that looks something like this:
{ foo: {stuff}, bar: {baz: {stuff} } }
and I use combineReducers, so that foo and bar and baz all have their own reducers to interpret relevant actions for changing their own state. But I've run into a situation where an action could, depending on the state of baz, have an implication that might be of interest to foo.
I have basically three ideas, where I hate the first one and don't know how to do the other two:
1) Make the reducers for bar/baz have access to the whole state, and ask them to be responsible about it.
This makes this exact situation easy to deal with, but it seems bad from a separation of concerns perspective.
2) Somehow have the baz reducer dispatch a relevant action that foo would then pick up on.
This makes sense to me from a descriptiveness perspective, but I don't actually know how to do it. The fact that it's not obvious makes me think Redux is against this.
3) Import some magic library that makes this simple
I don't know what library would do this, though. It doesn't seem like this is what redux-thunk does (although I'm not sure) and then I really don't know.
I would suggest you keep your reducers simple. They are there to modify the slice of state they care for, and nothing else.
If an "action" affects more than one slice, then it should dispatch multiple, "actions".. The naming can get confusing at this point, but basically redux-thunk allows you to do just this.
If you have regular action creators, eg modifyFoo & modifyBar which simply return action objects which are dispatched to the reducers, with redux-thunk enabled you can create a more complex "action" which dispatches both. eg..
function modifyFoo(options) {
return { type: "MODIFY_FOO", payload: options }};
}
function modifyBar(options) {
return { type: "MODIFY_BAR", payload: options }};
}
function modifyFooBar(options) {
return (dispatch, getState) => {
const appState = getState();
dispatch(modifyFoo(options.foo, appState.baz));
dispatch(modifyBar(options.bar));
}
}
Using getState you have access to the full state of the app if you need it.
tl;dr -- I've decided to use redux-loop, which lets you describe side effects of a reducer without making the reducer pure/stateless.
Why I did not use redux-thunk for this:
The most common approach (based on my googling), represented in #lecstor's answer, is to use redux-thunk. In the typical redux-thunk code organization, you have to figure out everything that's going to happen first, then dispatch some numbers of actions, async operations, etc. through a thunk.
The downside in this case is the second action is (a) conditional on the state after the first action triggers, and (b) cannot be derived from the state itself, but must be derived from the fact that we have this state after this action.
So with redux-thunk you either have to duplicate the logic of the reducer (bad), or do all the business logic before the reducers, then launching a bunch of actions that describe state changes.
This is all to say, redux-thunk does its magic before the reducer happens.
Why I think redux-loop was a better fit:
On the other hand, redux-loop does magic after the reducer happens, allowing the reducer to describe those effects (including kicking off async actions, dispatching further actions, etc.) without breaking statelessness.
For the historical record, this means that in the foo reducer above you can do:
// ... nextState is the new fooState
if (isWeird(nextState)) {
return loop(nextState, Cmd.action(makeWeirdAction()));
} else {
return nextState;
}
so that all other reducers can interpret or ignore weird action as they please, instead of making these decisions all at once in the action creator.
I'm using redux and redux-saga in an application to manage state and asynchronous actions. In order to make my life easier, I wrote a class that acts essentially as a saga manager, with a method that "registers" a saga. This register method forks the new saga and combines it with all other registered sagas using redux-saga/effects/all:
class SagasManager {
public registerSaga = (saga: any) => {
this._sagas.push(fork(saga));
this._combined = all(this._sagas);
}
}
This class is then used by my store to get the _combined saga, supposedly after all sagas are registered:
const store = Redux.createStore(
reducer,
initialState,
compose(Redux.applyMiddleware(sagaMiddleware, otherMiddleware)),
);
sagaMiddleware.run(sagasManager.getSaga());
However, I ran into the problem that depending on circumstances (like import order), this doesn't always work as intended. What was happening was that some of the sagas weren't getting registered before the call to sagaMiddleware.run.
I worked around this by providing a callback on SagasManager:
class SagasManager {
public registerSaga = (saga: any) => {
this._sagas.push(fork(saga));
this._combined = all(this._sagas);
this.onSagaRegister();
}
}
And then the store code can use this as
sagasManager.onSagaRegister = () => sagaMiddleware.run(sagasManager.getSaga());
This seems to work, but I can't find in the docs whether this is safe. I did see that .run returns a Task, which has methods for canceling and the like, but since my problem is only in that awkward time between when the store is constructed and the application is rendered I don't that would be an issue.
Can anyone explain whether this is safe, and if not what a better solution would be?
It may depend on what you mean by "safe". What exactly do you mean by that in this case?
First, here's the source of runSaga itself, and where it gets used by the saga middleware.
Looking inside runSaga, I see:
export function runSaga(options, saga, ...args) {
const iterator = saga(...args)
// skip a bunch of code
const env = {
stdChannel: channel,
dispatch: wrapSagaDispatch(dispatch),
getState,
sagaMonitor,
logError,
onError,
finalizeRunEffect,
}
const task = proc(env, iterator, context, effectId, getMetaInfo(saga), null)
if (sagaMonitor) {
sagaMonitor.effectResolved(effectId, task)
}
return task
}
What I'm getting out of that is that nothing "destructive" will happen when you call runSaga(mySagaFunction). However, if you call runSaga() with the same saga function multiple times, it seems like you'll probably have multiple copies of that saga running, which could result in behavior your app doesn't want.
You may want to try experimenting with this. For example, what happens if you have a counter app, and do this?
function* doIncrement() {
yield take("DO_INCREMENT");
put({type : "INCREMENT"});
}
sagaMiddleware.runSaga(doIncrement);
sagaMiddleware.runSaga(doIncrement);
store.dispatch({type : "DO_INCREMENT"});
console.log(store.getState().counter);
// what's the value?
My guess is that the counter would be 2, because both copies of doIncrement would have responded.
If that sort of behavior is a concern, then you probably want to make sure that prior sagas are canceled.
I actually ran across a recipe for canceling sagas during hot-reloading a while back, and included a version of that in a gist for my own usage. You might want to refer to that for ideas.
I am trying to make multiple changes to the store, but not render till all changes are done. I wanted to do this with redux-thunk.
Here is my action creator:
function addProp(name, value) {
return { type:'ADD_PROP', name, value }
}
function multiGeoChanges(...changes) {
// my goal here is to make multiple changes to geo, and make sure that react doesnt update the render till the end
return async function(dispatch, getState) {
for (let change of changes) {
dispatch(change);
await promiseTimeout(2000);
}
}
}
I dispatch my async action creator like this:
store.dispatch(multiGeoChanges(addProp(1, "val1"), addProp(2, "val2"), addProp(3, "val3")));
However this is causing react to render after each dispatch. I am new to redux-thunk, I never used async middleware, but I thought it could help me here.
#Kokovin Vladislav's answer is correct. To add some additional context:
Redux will notify all subscribers after every dispatch. To cut down on re-renders, either dispatch fewer times, or use one of several approaches for "batching" dispatches and notifications. For more info, see the Redux FAQ on update events: http://redux.js.org/docs/faq/Performance.html#performance-update-events .
I also recently wrote a couple of blog posts that relate to this topic. Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability discusses the pros and cons of using thunks, and summarizes several ways to handle batching of dispatches. Practical Redux Part 6: Connected Lists, Forms, and Performance describes several key aspects to be aware of regarding Redux performance.
Finally, there's several other libraries that can help with batching up store change notifications. See the Store#Store Change Subscriptions section of my Redux addons catalog for a list of relevant addons. In particular, you might be interested in https://github.com/manaflair/redux-batch , which will allow you to dispatch an array of actions with only a single notification event.
There are ways to achieve the goal:
Classic way:
usually:
Actions describe the fact that something happened, but don't specify how the application's state changes in response. This is the job of reducers.
That also means that actions are not setters.
Thus, you could describe what has happened and accumulate changes, and dispatch one action
something like:
const multipleAddProp = (changedProps) =>({
type:'MULTIPLE_ADD_PROP', changedProps
});
And then react on action in reducer:
const geo=(state,action)=>{
...
switch (action.type){
case 'MULTIPLE_ADD_PROP':
// apply new props
...
}
}
Another way When rerendering is critical :
then you can consider to limit components, which could be rerendered on state change.
For example you can use shouldComponentUpdate to check whether component
should be rendered or not.
Also you could use reselect, in order to not rerender connected components
after calculating derived data...
Non standard way:
redux-batched-action
It works something like transaction.
In this example, the subscribers would be notified once:
import { batchActions } from 'redux-batched-actions';
const multiGeoChanges=(...arrayOfActions)=> dispatch => {
dispatch( batchActions(arrayOfActions) );
}
In react-redux 7.0.1+ batching is now built-in. Release notes of 7.0.1:
https://github.com/reduxjs/react-redux/releases/tag/v7.0.1
Batched Updates
React has an unstable_batchedUpdates API that it uses to group
together multiple updates from the same event loop tick. The React
team encouraged us to use this, and we've updated our internal Redux
subscription handling to leverage this API. This should also help
improve performance, by cutting down on the number of distinct renders
caused by a Redux store update.
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment());
dispatch(increment());
})
}
}
By design when the state, which is held by the store, changes the view should render.
You can avoid this by updating the state once.
If you are using promises you can use Promise.all to wait for all the promises to resolve and then dispatch a new action to the store with the calculated result. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Something like this:
Promise.all([p1, p2, p3, p4, p5]).then(changes => {
dispatch(changes)
}, err => {
// deal with error
});
Of course you'll need an action that will deal with many props, something like addManyProps this should update the state once, resulting in one render.
redux-batched-actions
Batching action creator and associated higher order reducer for redux that enables batching subscriber notifications for an array of actions.
Coming to this a bit late, but I think this is a much nicer solution, which enables you to add meta.batch to actions you would like to batch together into a single react update. As a bonus this approach works with asynchronous actions.
import raf from 'raf'
import { batchedSubscribe } from 'redux-batched-subscribe'
let notify = null
let rafId = null
const shouldBatch = action => action?.meta?.batch
export const batchedSubscribeEnhancer = batchedSubscribe(freshNotify => (notify = freshNotify))
export const batchedSubscribeMiddleware = () => next => action => {
const resolved = next(action)
if (notify && rafId === null && !shouldBatch(action)) {
notify()
} else if (!rafId) {
rafId = raf(() => {
rafId = null
notify()
})
}
return resolved
}
Then connect up to your store
mport { applyMiddleware, compose, createStore } from 'redux'
import { batchedSubscribeMiddleware, batchedSubscribeEnhancer } from './batching'
const store = createStore(
reducer,
intialState,
compose(
batchedSubscribeEnhancer,
applyMiddleware(batchedSubscribeMiddleware)
)
)
(Note: My question was not clearly written, and I was thinking about some things wrong. The current version of the question is just an attempt to write something that could make the accepted answer useful to as many people as possible.)
I want to have an action that adds an item to a store and registers it with an external dependency.
I could use the thunk middleware and write
export function addItem(item) {
return dispatch => {
dispatch(_addItemWithoutRegisteringIt(item));
externalDependency.register(item);
};
}
But the subscribers would be notified before the item was registered, and they might depend on it being registered.
I could reverse the order and write
export function addItem(item) {
return dispatch => {
externalDependency.register(item);
dispatch(_addItemWithoutRegisteringIt(item));
};
}
But I track the item in the external dependency by a unique id that it is natural to only assign in the reducer.
I could register the item in the reducer, but I am given to understand that it is very bad form to do side effects in a reducer and might lead to problems down the line.
So what is the best approach?
(My conclusion is: there are a number of approaches that would work, but probably the best one for my use case is to store a handle into the external dependency in Redux rather than a handle into Redux in the external dependency.)
If you use Redux Thunk middleware, you can encapsulate it in an action creator:
function addItem(id) {
return { type: 'ADD_ITEM', id };
}
function showNotification(text) {
return { type: 'SHOW_NOTIFICATION', text };
}
export function addItemWithNotification(id) {
return dispatch => {
dispatch(addItem(id));
doSomeSideEffect();
dispatch(showNotification('Item was added.');
};
}
Elaborating, based on the comments to this answer:
Then maybe this is the wrong pattern for my case. I don't want subscribers invoked between dispatch(addItem(id)) and doSomeSideEffect().
In 95% cases you shouldn't worry about whether the subscribers were invoked. Bindings like React Redux won't re-render if the data hasn't changed.
Would putting doSomeSideEffect() in the reducer be an acceptable approach or does it have hidden pitfalls?
No, putting side effects into the reducer is never acceptable. This goes against the central premise of Redux and breaks pretty much any tool in its ecosystem: Redux DevTools, Redux Undo, any record/replay solution, tests, etc. Never do this.
If you really need to perform a side effect together with an action, and you also really care about subscribers only being notified once, just dispatch one action and use [Redux Thunk] to “attach” a side effect to it:
function addItem(id, item) {
return { type: 'ADD_ITEM', id, item };
}
export function addItemWithSomeSideEffect(id) {
return dispatch => {
let item = doSomeSideEffect(); // note: you can use return value
dispatch(addItem(id, item));
};
}
In this case you'd need to handle ADD_ITEM from different reducers. There is no need to dispatch two actions without notifying the subscribers twice.
Here is the one point I still definitely don't understand. Dan suggested that the thunk middleware couldn't defer subscriber notification because that would break a common use case with async requests. I still don't understand this this.
Consider this:
export function doSomethinAsync() {
return dispatch => {
dispatch({ type: 'A' });
dispatch({ type: 'B' });
setTimeout(() => {
dispatch({ type: 'C' });
dispatch({ type: 'D' });
}, 1000);
};
}
When would you want the subscriptions to be notified? Definitely, if we notify the subscribers only when the thunk exits, we won't notify them at all for C and D.
Either way, this is impossible with the current middleware architecture. Middleware isn't meant to prevent subscribers from firing.
However what you described can be accomplished with a store enhancer like redux-batched-subscribe. It is unrelated to Redux Thunk, but it causes any group of actions dispatched synchronously to be debounced. This way you'd get one notification for A and B, and another one notification for C and D. That said writing code relying on this behavior would be fragile in my opinion.
I'm still in the process of learning Redux; however my gut instinct says that this is could be a potential candiate for some custom middleware?