I have a redux-saga which is called once, but it is executing twice.
This is the action that starts the saga:
export function createRequest (data) {
return {
type: CREATE_REQUEST,
payload: {data}
};
}
and my sagas.js file looks this way:
export function* create (x) {
try {
const response = yield call(request, URL_TO_API, Object.assign({}, buildBaseHeaders('en'), {
method: 'post',
body: JSON.stringify(x.payload.data)
}));
yield put(createSuccess(response));
} catch (error) {
yield put(createFailure(error));
}
}
... my other sagas
export default function* defaultSaga () {
yield takeLatest(CREATE_REQUEST, create);
... my other calls
}
The way I'm injecting the sagas into my React component is this:
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({key: 'myComponent', reducer});
const withSaga = injectSaga({key: 'myComponent', saga});
export default compose(withReducer, withSaga, withConnect) MyComponent;
But the saga is injected twice. So, what am I missing here? How can I inject the saga only once no matter on how many times MyComponent gets rendered?
But the saga is injected twice. So, what am I missing here?
Solution is dependent on how redux-sagas-injector npm library works. In general case, asynchronous loading and applying for sagas is difficult thing, because saga is consists of "live" process manager, which can not be disposed on some function call or object deletion.
It implies from saga's ability to launch custom tick callback domains (Promises, AJAX/XHR, setImmediate, etc), which can not be disposed from custom external code (That's also reason, why HMR does not work with sagas in partial mode and should reload whole page).
So, if you perform saga injection on router switching, check two things: that old saga has implicit action to dispose from outer side, like special inner technical dispose action, and that there is not misconfiguration in router side - maybe same page had been launched, for example, twice.
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've been working on a react project, using redux and sagas.
So far I was able to create a structure where my component would dispatch an action requesting a list, then sagas takes this action and calls a function that would make the request to the backend. I then store this list on redux, and my component reads the list from redux.
Now I need to load a single object from backend. I thought about not using sagas for that request, but I guess it makes sense to make this request using sagas and storing this single object on redux.
I can't figure how to make the rootSaga function accept to take my function with 'id' as parameter.
To ilustrate the scenario, here is some code:
this is the root saga function, it would make the API request and call the action to store on redux
export function* loadEvent({ id: number }) {
try {
console.log('load single event');
yield put(loadEventSuccess(event));
} catch (err) {
yield put(loadFailure());
}
}
This is the rootSaga function that is supposed to get the action I dispatched and trigger my saga function. The problems is that it does not accept me to call the function with a parameter. Even if I could, I don't know how to pass a parameter to it.
export default function* rootSaga() {
return yield all([
takeLatest(EventsTypes.LOAD_EVENT_REQUEST, loadEvent),
]);
}
Of course, before all that there is an action I dispatched from my function component.
Another thing that has been confusing me is if the approach I've been using is ok. I could make this request from my function component and store the data in its state. By doing that I wouldn't involve redux nor sagas. Would that make sense?
Your approach is completely right as you should try to make every api call throw your sagas, but you just made a small mistake
function* loadEvent({ payload }) {
const { id } = payload;
try {
const response = yield call(api.get, `localhost:4000/api/${id}`);
yield put(loadEventSuccess(event));
} catch (err) {
yield put(loadFailure());
}
};
You also don't need to export the generator functions, and in my example I'm simulating an api get call using axios. And don't forget to import { call } from "redux-saga/effects";
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 have some "ChangeMainItem" Action (in my case it is dispatched by external system or possibly one of components). This action (e.g. {type:Change_Main_Item, itemId:5}) updates only one property of state in reducer (e.g. mainItemId).
My Component A and B needs to react on this state change: show loading indicator, fetch additional data and show results. Sequential actions can be done via some async action library - but where should I place dispatching async action? Obviously, i can't dispatch async action in reducer of Component A and B, neither i want to change original Action to async so it can make any necessary requests for my components.
What is the right way to achieve this?
I suggest using sagas to listen to your defined actions and manage your async calls/actions from there. Redux-saga is awesome.
import { put, takeEvery } from 'redux-saga/effects'
import {Change_Main_Item, ANOTHER_ACTION, THIRD_ACTION} from '../actions'
function* doSomething() {
yield put({type: "ANOTHER_ACTION", payload: data});
}
function* doAnotherThing() {
yield put({type: "THIRD_ACTION", payload: data});
}
function* mySaga() {
yield takeEvery("Change_Main_Item", [doSomething, doAnotherThing]);
}
Please see https://github.com/redux-saga/redux-saga
Well, you have multiple approaches to such a question.
You can use a redux-thunk so you can dispatch multiple actions and have your state react to all such dispatches. Thunk middleware is useful when you need to perform async actions.
example:
function changeMainItem(id) {
return (dispatch, getState) {
dispatch(requestChangeMainItem); //This tells the state that it's loading some ajax; you presumably want to add some boolean, indicating a loading icon.
return makeSomeRequest(id)
.then(data => {
dispatch(changeMainItem(data.id)) //presumably, this is where you update your item
// perhaps dispatch another function to do more stuff.
})
}
}
You then will need to figure out which components need to be subscribed/connected to certain properties in your state.
Read about async action, redux-thunks, and this article on how to connect your components to your state
I would like to modify the state before next() is called so every reducer applied after the middleware gets the new state. Is it possible? How?
The only idea that comes to my mind is very hacky and would be something like this:
export const myMiddleware = (store) => (next) => (action) => {
const oldReducer = ????
store.replaceReducer(myReducer);
store.dispatch(action);
const newState = store.getState();
store.replaceReducer(oldReducer);
return next(newState);
}
As I haven't seen any method to get the current reducer, it should be given to the middleware in any manner:
export const myMiddleware = (oldReducer) => (store) => (next) => (action) => {
...
}
const store = createStore(originalReducer, applyMiddleware(myMiddleware(originalReducer)));
Which seems even more hacky!
The main purpose is to build a package that maps an action object (action.payload) and a path (action.meta) in store state. In this scenario, the reducer is distributed in an npm package, so it should be "chained" somehow. So right now the reducer is detecting if there is a path and an object inside payload, and tries to reduce the new state from it.
The worst solution is to instruct the user to call the reducer from their own reducer, just before any other action inside the reducer. This is not a solid pattern. So at first, I was thinking in an as much agnostic as possible middleware that automatically does the work. And that's why I'm trying to modify state from middleware if possible.
You probably don't want to be calling individual reducers from within middleware. It sounds like you're condensing what should be multiple sequential actions into a single action, which is what's causing you problems. If you use something like redux-saga to manage chains of actions, you can likely accomplish what you're looking for pretty easily.
Here's a basic example of managing a sequence of actions with redux-saga:
import { takeEvery } from 'redux-saga'
import { put } from 'redux-saga/effects'
export function * watchForSomeAction () {
// Every time SOME_ACTION is dispatched, doSomethingElse() will be called
// with the action as its argument
yield * takeEvery('SOME_ACTION', doSomethingElse)
}
export function * doSomethingElse (action) {
// put() is redux-saga's way of dispatching actions
yield put({ type: 'ANOTHER_ACTION', payload: action.payload })
}
This example simply watches for SOME_ACTION, and when it happens, it dispatches ANOTHER_ACTION. With something like this, you can ensure that ANOTHER_ACTION's reducers are dealing with the new state resulting from SOME_ACTION's reducers.