I need to pass payload from fromSaga to toSaga. The problem is it seems that toSaga does not get the payload. However, if I dispatch an action via useEffect, toSaga gets the payload. My observation is that, if you dispatch an action from fromSaga it skips the action creator and goes directly to toSaga. As a result, no payload is sent to toSaga. Here's the real code:
const fetchTokens = function*(action: any) {
try {
const userCreds: any = toSnakeCase(action.payload) // action.payload is undefined here
const response: any = yield call(fetchTokensApi, userCreds)
if (response.status === 200) {
const data: any = response.data.data
const tokens: Tokens = {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiry: extractExpiry(data.access_token),
}
yield put({ type: types.FETCH_TOKENS_SUCCESS, tokens})
}
} catch (error) {
yield put({ type: types.FETCH_TOKENS_FAILURE, error })
}
}
export const fetchTokensSaga = function*() {
yield takeLatest(types.FETCH_TOKENS, fetchTokens)
}
const validateAccessToken = function*() {
try {
const tokens: Tokens = yield select(tokensSelector)
if (!tokens) {
yield put({ type: types.FETCH_TOKENS, USER_CREDS })
}
} catch (error) {
yield put({ type: types.FETCH_TOKENS_FAILURE, error })
}
}
In validateAccessToken, fetchTokensSaga is called via:
yield put({ type: types.FETCH_TOKENS, USER_CREDS })
fetchTokens should then get the USER_CREDS. However, it's not the case as payload is always undefined. What should I do so fetchTokens gets the USER_CREDS payload?
You're directly dispatching a raw object, not using an action creator, so you need to put USER_CREDS under the payload key:
yield put({ type: types.FETCH_TOKENS, payload: USER_CREDS })
If you have an action creator like
function fetchTokensActionCreator(userCreds) {
return {
type: types.FETCH_TOKENS,
payload: userCreds,
};
}
you can instead do yield put(fetchTokensActionCreator(USER_CREDS));
You should have a payload key when dispatching as you are using action.payload within fetchTokens().
yield put({
type: types.FETCH_TOKENS,
payload: USER_CREDS // add payload
})
Related
Hi guys i was using redux saga in my project but i am stuck in part where i have to dispatch an action whenever an error occurs and then update the state in errorReducer
Here is my code
So as soon as user clicks on signup function we have the following code to run
function* signup(action) {
yield put({ type: TOGGLE_LOADING, payload: true });
yield call(userSignup, action.payload);
yield put({ type: TOGGLE_LOADING, payload: false });
}
then we have function userSignup() running
const userSignup = async (userData) => {
try {
console.log("api hittinh");
const response = await axios.post(`${BASE_HEADER_URL}/api/signup`, {
data: userData,
});
console.log({ response });
return response.data;
} catch (err) {
console.log("in catch");
console.log(err.response.data);
yield put({ type: ERROR, payload: err.response.data });
}
};
now as user tries to sign up with the email which already exists, i send status code of 409 'Unauthorized' and then it goes to catch statement, now how can i dispatch an action over here ?
I cant use yield here cause, it can be used inside generator function and neither can i use ''useDispatch' cause it says
Invalid hook call. Hooks can only be called inside of the body of a
function component
yield command can be used only generator functions(function*).
And you need to catch error in saga function.
function* signup(action) {
try {
put({ type: TOGGLE_LOADING, payload: true });
const response = yield call(userSignup, action.payload);
yield put({ type: TOGGLE_LOADING, payload: false });
} catch (err) {
yield put({ type: ERROR, payload: err.response.data});
}
}
const userSignup = async (userData) => {
try {
console.log("api hittinh");
const response = await axios.post(`${BASE_HEADER_URL}/api/signup`, {
data: userData,
});
return response.data;
} catch (err) {
throw new Error(err);
}
};
I have a react saga code here which does not run in sequence:
yield put({ type: 'SHOW_LOADER', loading: 'workflowobject' }) #yield to initiate the loader
const states = yield call(() => axiosInstance.get(`/workflow-object/state/list/${action.workflow_id}/${action.workflow_object_id}/`))
const newState = states.data.map(item => ({ // some operation to shape my data , might this be the issue?
iteration: item.iteration,
...item.state
}));
yield put({ type: "STORE_WORKFLOW_DATA", payload: newState , fetch: 'workflowobject' , label: 'states'})
.....
bunch more of yields here
.....
yield put({ type: 'HIDE_LOADER', loading: 'workflowobject' }) #yield to change loader state
The problem here is that the HIDE_LOADER gets called before some inbetween operations end , leading to a error found in my component
Here is my entire code snippet:
Saga
// This code loads the transitions and states for the detail page //
export function* workerWorkflowObjectDetail(action) {
try{
yield put({ type: 'SHOW_LOADER', loading: 'workflowobject' })
// clears the object states
yield put({ type: 'CLEAR_OBJECT_STATES' })
// store workflow_id
yield put({ type: "STORE_WORKFLOW_DATA", payload: action.workflow_id , fetch: 'workflowobject' , label: 'workflow_id'})
const transitions = yield call(axiosInstance.get , `/workflow-object/transition/list/${action.workflow_id}/${action.workflow_object_id}/`)
// store transitions
yield put({ type: "STORE_WORKFLOW_DATA", payload: transitions.data, fetch: 'workflowobject' , label: 'transitions'})
const newStateMap = {}
transitions.data.forEach(transition => {
if (transition.is_done) {
newStateMap[transition.source_state] = done_class
newStateMap[transition.destination_state] = selected_class
} else if (transition.is_cancelled) {
newStateMap[transition.destination_state] = cancelled_class
} else {
newStateMap[transition.destination_state] = default_class
}
});
// store state_class_mapping
yield put({ type: "STORE_WORKFLOW_DATA", payload: newStateMap, fetch: 'workflowobject' , label: 'state_class_mapping'})
const states = yield call(axiosInstance.get, `/workflow-object/state/list/${action.workflow_id}/${action.workflow_object_id}/`)
const newState = states.data.map(item => ({
iteration: item.iteration,
...item.state
}));
// stores states
yield put({ type: "STORE_WORKFLOW_DATA", payload: newState, fetch: 'workflowobject' , label: 'states'})
// stores object_id
yield put({ type: "STORE_WORKFLOW_DATA", payload: action.workflow_object_id, fetch: 'workflowobject' , label: 'object_identifier'})
// stores current_state
const current_state = yield call(axiosInstance.get,`/workflow-object/current-state/${action.workflow_id}/${action.workflow_object_id}/`)
yield put({ type: "STORE_WORKFLOW_DATA", payload: current_state.data, fetch: 'workflowobject' , label: 'current_state'})
// stores current iteration
const current_iteration = yield call(axiosInstance.get,`/workflow-object/current-iteration/${action.workflow_id}/${action.workflow_object_id}/`)
yield put({ type: "STORE_WORKFLOW_DATA", payload: current_iteration.data, fetch: 'workflowobject' , label: 'current_iteration'})
yield put({ type: 'HIDE_LOADER', loading: 'workflowobject' })
} catch(err){
console.log(err)
}
if (action.message) {
yield put({ type: "CREATE_MESSAGE", message: action.message })
}
}
Reducer
// Helpers
const storeData = (state, action) => {
switch (action.fetch) {
case 'workflowobject': return loadWorkflowObject(state, action)
}
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'STORE_WORKFLOW_DATA': return storeData(state, action);
case 'CLEAR_OBJECT_STATES': return clearObjectStates(state, action);
default:
return state;
}
}
export default reducer;
As per redux-saga documentation, call(fn, ...args) function should be provided with either a normal or a Generator function.
yield call(() => axiosInstance.get(`/workflow-object/state/list/${action.workflow_id}/${action.workflow_object_id}/`))
From you code it looks like the first parameter is neither a normal function that is returning a Promise nor a Generator function. If the result is not an Iterator object nor a Promise, the middleware will immediately return that value back to the saga, so that it can resume its execution synchronously. As per my understanding this could be the issue.
So instead of providing the arrow function which is calling the API, try providing this way,
yield call(axiosInstance.get, `/workflow-object/state/list/${action.workflow_id}/${action.workflow_object_id}/`)
Hope this helps in resolving the issue which you are facing.
I am trying to get myLocation(redux state) variable which is used in next dispatch GET_POSTS_REQUEST. But when i tried to put await to get fully return value, it shows error.
index.js
const testFunc = () => {
const { myLocation} = useSelector(state => state.user);
dispatch({
type: MY_LOCATION_REQUEST,
data: {
lat: position.coords.latitude,
long: position.coords.longitude,
},
});
dispatch({
type: GET_POSTS_REQUEST,
data: {
dong: myLocation.dong,
},
});
};
sagas/location.js
function getLocationAPI(locationInfo) {
return axios.post('/location', locationInfo ,{withCredentials: true});
}
function* getLocation(action) {
try {
const result = yield call(getLocationAPI, action.data);
yield put({
type: GET_LOCATION_SUCCESS,
data: result.data,
});
} catch (e) {
yield put({
type: GET_LOCATION_FAILURE,
error: e,
});
}
}
function* watchGetLocation() {
yield takeLatest(GET_LOCATION_REQUEST, getLocation);
}
export default function* locationSaga() {
yield all([
fork(watchGetLocation),
]);
}
I have to use myLocation for next dispatch action in index.js. But, when i tried to put async/await to my dispatch, it didn't work. Is there any solution for this?
put method kinda dispatches too, for example this part
yield put({
type: GET_LOCATION_SUCCESS,
data: result.data,
});
you can always see as
dispatch({
type: GET_LOCATION_SUCCESS,
data: result.data,
});
So you have to "catch" this with reducer, for example
function yourReducer(state = initialState, action) {
switch (action.type) {
case GET_LOCATION_SUCCESS:
return Object.assign({}, state, {
user: Object.assign({},
state.user,
{
myLocation: action.data
}
]
})
default:
return state
}
}
And this will update your state with data returned from api call
I want to make n number of api call and i am doing as floows
try {
....
yield all(
action.uploadDocsBody.map(u =>
call(uploadDocs, {
...,
}),
),
);
yield put({
type: UPLOADDOCS_COMPLETED,
payload: { statusText: 'Success' },
});
} catch (e) {
yield put({
type: UPLOADDOCS_FAILED,
payload: {
error: true,
},
});
}
the issue is that
UPLOADDOCS_COMPLETED being called only after finishing all the api calls.
I want to yield put after every api call how can use it ?
You can wrap each call with another generator, and yield it:
function *foo(u) {
yield call(uploadDocs, {...});
yield put({ type: UPLOADEDDOCS_COMPLETED, .....});
}
yield all(action.uploadDocsBody.map(u => foo(u)))
function call(uploadDocs,{...},callback){
/// do you task
callback()
}
function *foo(u) {
var put = function({ type: UPLOADEDDOCS_COMPLETED, .....});
yield call(uploadDocs, {...} ,put);
}
I see many questions about this but nothing from answers helps me to solve it. I wanted to see how saga works since I haven't worked with it.
Here is the code
export function* fetchItems() {
try {
yield put({ type: ITEMS_FETCH_REQUEST })
const response = yield call(fetch, 'https://jsonplaceholder.typicode.com/users');
const data = response.json();
yield put({ type: ITEMS_FETCH_SUCCESS, payload: { items: data } })
} catch (error) {
yield put({ type: ITEMS_FETCH_FAILURE, payload: { error: error.message }})
}
}
It calls an infinite loop, I tried a lot of things but nothing helps.
What am I doing wrong here ?
I figured it out, for watchAsyncSagaFunction generator function you need to create another constant that is different than the one that you call at the beginning of asyncSagaFunction.
Example:
export function* asyncSagaFunction() {
try {
yield put({ type: ITEMS_FETCH_REQUEST })
const response = yield call(fetch, 'https://jsonplaceholder.typicode.com/users');
const data = response.json();
yield put({ type: ITEMS_FETCH_SUCCESS, payload: { items: data } })
} catch (error) {
yield put({ type: ITEMS_FETCH_FAILURE, payload: { error: error.message }})
}
}
There is ITEMS_FETCH_REQUEST, you need to create another one, for example,ITEMS_FETCH and in a component call that one.
export function* watchAsyncSagaFunction() {
yield takeLatest(ITEMS_FETCH, fetchItems)
}