Infinite loop in Redux Saga - reactjs

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)
}

Related

How can i catch error? when i use redux-saga?

How can i catch error? when i use redux-saga?
if error occred in const refreshresult = yield call(refresh);
i want to stop and throw this error action
i don't know what can i do
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
how can i do that? how should i stop and send error action?
this is my code
function getPostAPI(data) {
return axiosInstace.post('/kakao/getpost', data);
}
function* getPost(action) {
try {
const result = yield call(getPostAPI, action.data);
yield put({
type: GETPOST_SUCCESS,
data: result.data,
});
} catch (err) {
if (err.response.data === 'jwtEx') {
const refreshresult = yield call(refresh); // <<<< if error occure refresh() i want to stop
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function* refresh() {
try {
const result = yield call(refreshAPI);
yield AsyncStorage.setItem(
'accesstoken',
`${result.data.accessToken}`,
() => {
// console.log('accesstoken 재발급 저장 완료');
},
);
yield put({
type: REFRESH_SUCCESS,
data: result.data,
});
} catch (err) {
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
}
}
I guess you're doing it the right way. Try and catch will work.
Check out this link for more info Redux-saga documentation

dispatch an action after api call in redux-saga if some error occurs

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);
}
};

How to use yield put inside yield all

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);
}

Pass payload from one saga to another saga

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
})

Redux saga multiple api calls

I have a bunch of action watchers of my api calls using redux-saga. The thing is I would like to make ONE action watcher which fire all these action watchers to fetch all the api without having to repeat the codes I already have. If one of the watcher return a rejected Promise, it should cancel all the other watchers. What's the best way of doing this?
function* watchFetchUsers() {
while(true) {
yield take([FETCH_USERS]);
try {
const users = yield call(api.fetchUserData);
yield put({ type:FETCH_USERS, payload: users });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
function* watchFetchDepartments() {
while(true) {
yield take([FETCH_DEPARTMENTS]);
try {
const departments = yield call(api.fetchDepartmentData);
yield put({ type:FETCH_DEPARTMENTS, payload: departments });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
function* watchFetchPositions() {
while(true) {
yield take([FETCH_POSITIONS]);
try {
const positions = yield call(api.fetchPositionData);
yield put({ type:FETCH_POSITIONS, payload: positions });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
function* watchFetchBanks() {
while(true) {
yield take([FETCH_BANKS]);
try {
const banks = yield call(api.fetchBankData);
yield put({ type:FETCH_BANKS, payload: banks });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
function* watchFetchAuthenticatedUser() {
while(true) {
yield take([FETCH_AUTHENTICATED_USER]);
try {
const user = yield call(api.fetchAuthenticatedUser);
yield put({ type:FETCH_AUTHENTICATED_USER, payload: user });
} catch (err) {
yield put({ type:SIGNOUT, status: err });
return err;
}
}
}
export default function* fetchData() {
yield [
fork(watchFetchUsers),
fork(watchFetchDepartments),
fork(watchFetchPositions),
fork(watchFetchBanks),
fork(watchFetchAuthenticatedUser)
];
}
How about this
export function* watchFetchAll() {
while(true) {
// const {type} = yield take(['FETCH_A', 'FETCH_B', ...]);
const {type} = yield take(action => /^FETCH_/.test(action.type));
console.log('type %s', type);
try {
const data = yield call(api.fetch, type);
console.log('data', data);
yield put({type, payload: data})
}
catch (error) {
console.log('error', error);
yield put({ type: 'SIGNOUT', status: error })
}
}
}
export default function* fetchData() {
yield *watchFetchAll();
}
The simple api implementation:
const api = {
fetch(type) {
switch (type) {
case 'FETCH_A': return Promise.resolve({result: 'Fetched A type'});
case 'FETCH_B': return Promise.resolve({result: 'Fetched B type'});
// other cases
default: console.log(`Unknown type ${type}`);
}
}
};
The forked task's error is propagated to parent tasks.
I'm not sure if the below is what you want. But maybe it will work.
function* watchFetchUsers() {
while(true) {
yield take([FETCH_USERS]);
const users = yield call(api.fetchUserData);
yield put({ type:FETCH_USERS, payload: users });
}
}
function* watchFetchDepartments() {
while(true) {
yield take([FETCH_DEPARTMENTS]);
const departments = yield call(api.fetchDepartmentData);
yield put({ type:FETCH_DEPARTMENTS, payload: departments });
}
}
function* watchFetchPositions() {
while(true) {
yield take([FETCH_POSITIONS]);
const positions = yield call(api.fetchPositionData);
yield put({ type:FETCH_POSITIONS, payload: positions });
}
}
function* watchFetchBanks() {
while(true) {
yield take([FETCH_BANKS]);
const banks = yield call(api.fetchBankData);
yield put({ type:FETCH_BANKS, payload: banks });
}
}
function* watchFetchAuthenticatedUser() {
while(true) {
yield take([FETCH_AUTHENTICATED_USER]);
const user = yield call(api.fetchAuthenticatedUser);
yield put({ type:FETCH_AUTHENTICATED_USER, payload: user });
}
}
export default function* fetchData() {
while (true) {
let tasks;
try {
tasks = yield [
fork(watchFetchUsers),
fork(watchFetchDepartments),
fork(watchFetchPositions),
fork(watchFetchBanks),
fork(watchFetchAuthenticatedUser)
];
yield join(...tasks)
} catch (e) {
yield cancel(...tasks);
yield put({ type:SIGNOUT, status: err });
}
}
}
Or if you don't want to restore tasks,
//....
export default function* fetchData() {
try {
yield [
fork(watchFetchUsers),
fork(watchFetchDepartments),
fork(watchFetchPositions),
fork(watchFetchBanks),
fork(watchFetchAuthenticatedUser)
];
} catch (e) {
yield put({ type:SIGNOUT, status: err });
}
}
The answer called Parallel Task wherein you call 2 or more api at the same time, also efficient to handle than to this (example below)
// wrong, effects will be executed in sequence
const users = yield call(fetch, '/users')
const repos = yield call(fetch, '/repos')
//instead, try this one
import { all, call } from 'redux-saga/effects'
const [users, repos] = yield all([
call(fetch, '/users'),
call(fetch, '/repos')
])
for more information regards with your issue, I prefer to read more about sync and async of redux saga, Redux-Saga Parallel task and Non-Blocking Call

Resources