How to use Redux Sagas to load single object? - javascript

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";

Related

Any benefits to use Redux-Saga instead of writing async func in react components?

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.

when I map my redux state with component state I am getting an error

when you click advanced sports search button I need to display drawer with my api values.
but right now when I map my redux state with component state I am getting an error.
Actions must be plain objects. Use custom middleware for async actions.
can you tell me how to map my state.
so that in future I can fix all my redux issues by myself.
providing code snippet and sandbox below.
all my map state is done in tab-demo.js
https://codesandbox.io/s/rlpv50q8qo
getSportsPlayerHistory = values => {
this.props.fetchHistorySportsDatafromURL();
};
toggleDrawer = (side, open) => () => {
if (open === true) {
this.getSportsPlayerHistory();
}
this.setState({
[side]: open
});
};
const mapDispatchToProps = dispatch => {
return {
onDeleteAllSPORTS: () => {
// console.log("called");
dispatch(deleteAllPosts());
},
addFavoriteSPORTSs: data => {
dispatch(addFavoriteSPORTSs(data));
},
fetchHistorySportsDatafromURL: () => {
dispatch(fetchHistorySportsDatafromURL());
}
};
};
Actions need to return plain objects, your fetchHistorySportsDatafromURL action returns a function. If you make your history reducer function async then you can make an async function to make your API call there and return the result to state.
API call in reducer
This works, but isn't ideal as you want your reducers to be pure functions, as-in, no side-effects, same input always produces the same output
You can also make the API request in the component's callback handler asynchronously and pass the result to the dispatched action.
API call in component then dispatched in action
This is a good solution and works great for small projects, but couples network business logic into your UI display components, which also isn't as ideal since it reduces code re-usability.
If you still want to keep your API logic separate from your component (which is a good thing), redux-thunk is a way to create asynchronous action creators, which is very similar to the pattern of your original code.
API call in action using redux-thunk
This is the most ideal as it completely de-couples business logic from your UI, meaning you can change back-end requests without touching front-end UI, and other components can now also use the same action. Good DRY principal.
Not really sure what you wanted to do with the new state, but this should get you to a good spot to handle that in your mapStateToProps function.

redux-saga injected twice

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.

How React Redux component can subscribe on state change

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

Set variable to return of callback listener

I'm using firebase to handle auth in my application. Firebase has a listener that executes a callback anytime the authentication status changes. I have that in a firebase utils file:
export default {
...
authChanged: (callback) =>{
return firebaseAuth.onAuthStateChanged(callback);
},
...
}
When a user logs in/out the callback executes. I'm trying to handle this observer in redux-saga,sending an event to my reducer whenever firebase tells me the status changes. The problem I'm running into is that I can't determine how to corral the dynamic return value of that observer. Right now I'm doing this:
export function* loginState(){
Firebaseutils.authChanged(function(user){
if(user){
yield put({type: 'LOGGED_IN', user: {user.email, user.uid}})
}else{
yield put({type: 'LOGGED_OUT'})
}
});
}
I've confirmed the if/else conditionals work (just logging to console when status changes), but I get an error if I try to use yields. It's either a bug or I'm going against a rule I'm not clear on. Any other way to set this up? It seems like from a best practice perspective this really needs to be handled by redux-sagas.
You can only use yield inside a generator function. And unlike variables and functions, yield is not available inside a nested function, which your callback function is.
You can just dispatch the actions with normal dispatch.

Categories

Resources