In a React app, I do not understand why the Yield line in a generator function is not 'waiting'? Specifically, in the LOGIN function below, I would expect the Yield line immediately following console.log("Step 3") to pause until it was completed; however it does NOT pause and Step 8 is immediately processed. I would expect the STEPS in the console.log to follow the logical numerical order. The actual order that is printed out in the browser console window is: 1,2,3,8,9,10,4,5,6,7. Can someone explain why it is NOT pausing?
export function* LOGIN({ payload }) {
const { email, password } = payload
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
},
})
let userCog
try {
console.log("Step 1")
userCog = yield call(login, email, password)
console.log("Step 2")
} catch (err) {
if (err.code === 'UserNotConfirmedException') {
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
email,
},
})
yield history.push('/system/verification')
}
}
console.log("Step 3")
yield put({
type: 'user/LOAD_CURRENT_ACCOUNT',
})
console.log("Step 8")
if (userCog) {
console.log("Step 9")
yield history.push('/dashboard/analytics')
console.log("Step 10")
}
}
export function* LOAD_CURRENT_ACCOUNT() {
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
},
})
console.log("Step 4")
const response = yield call(currentUser)
console.log("Step 5")
if (response) {
const { username } = response
yield put({
type: 'user/SET_STATE',
payload: {
id: '123',
name: 'Administrator',
email: username,
role: 'admin',
authorized: true,
},
})
}
console.log("Step 6")
yield put({
type: 'user/SET_STATE',
payload: {
loading: false,
},
})
console.log("Step 7")
}
EDIT: Here is the redux dispatch from the Login UI Component
onSubmit = event => {
event.preventDefault()
const { form, dispatch } = this.props
form.validateFields((error, values) => {
if (!error) {
dispatch({
type: 'user/LOGIN',
payload: values,
})
}
})
}
Related
I am using Firestore to store data for my Reactjs app. I have a function as such:
export async function batchAddProduct(data) {
const productRef = doc(collection(db, "product"));
const batch = writeBatch(db);
for (const datum of data) {
batch.set(productRef, datum);
}
return await batch
.commit()
.then(() => {
return { data: true, error: null };
})
.catch((err) => {
return { data: null, error: err };
});
}
So basically, I want to add lots of data at once. Hence, I'm using the writeBatch method. I see from an answer in SO where they use doc(collection(db, "product") to generate an empty doc first then use batch.set() to fill the doc. So I'm doing that here, and I'm passing up to 500 data at once (which is the maximum limit of a batch write), but somehow only up to 3 data is being written into the database. Why is that? Am I missing something?
Update:
According to the comment:
When I console.log(data), it basically prints out an array with 500 objects in it (which I definitely can't paste in here). But I can assure you that it is receiving the correct data.
batchAddProduct is called in a redux sagas as such:
function* BATCH_ADD_PRODUCT(input) {
yield put({
type: actions.SET_STATE,
payload: {
loadingUpdate: true,
},
});
const { data, error } = yield call(batchAddProduct, input.payload.data);
if (data) {
yield put({
type: actions.GET_PRODUK,
});
yield put({
type: actions.SET_STATE,
payload: {
loadingUpdate: false,
alert: {
type: "success",
message: "Product is added successfully.",
},
},
});
}
if (error) {
console.log(error);
yield put({
type: actions.SET_STATE,
payload: {
loadingUpdate: false,
alert: {
type: "error",
message: error.message || "Error occured.",
},
},
});
}
}
and I use this in a dispatch as such:
dispatch({
type: actions.BATCH_ADD_PRODUK,
payload: {
data: data, // WHICH CONTAINS UP TO 500 OBJECTS
},
});
I haven't tried the generator function with a batched write yet but try the following:
const myArray: any = []
const batches: WriteBatch[] = []
myArray.forEach((doc, i) => {
if (i % 500 === 0) {
batches.push(writeBatch(db))
}
const productRef = doc(collection(db, 'colName'))
const batch = batches[batches.length - 1]
batch.set(productRef, { ...data })
})
await Promise.all(batches.map((batch) => batch.commit()))
console.log('done')
When I login into the user, it has success response, but the action is not passed to the reducer. But while registering a user, it's working fine.
//saga.js
import { put, takeEvery, all, call } from 'redux-saga/effects';
import {getRequest, postRequest} from '../../helpers/axiosClient';
import actions from "./actions";
function* registerUserAsync(params) {
try {
const response = yield call(() => postRequest('users/register', params.payload));
yield put({
type: actions.ON_REGISTER_SUCCESS,
payload: response.data,
});
} catch (error) {
yield put({
type: actions.ON_REGISTER_FAILURE,
payload: "Something Went Wrong",
});
}
}
function* loginUserAsync(params){
try {
const response = yield call(() => postRequest('users/login', params.payload));
yield put({
type: actions.ON_LOGIN_SUCCESS,
})
} catch (error) {
yield put({
type: actions.ON_LOGIN_FAILURE,
payload:error.response.data.message
})
}}
function* forgetPasswordUser(params){
try {
const response = yield call(() => getRequest(`users/restorepassword/${params.payload}`));
yield put({
type: actions.ON_PASSWORD_RECOVERY_SUCCESS,
payload: "PASSWORD RECOVER",
});
} catch (error) {
}}
function* registerUser() {
yield takeEvery('ON_REGISTER', registerUserAsync)
}
function* loginUser(){
yield takeEvery('ON_LOGIN', loginUserAsync)
}
function* forgetPassword(){
yield takeEvery('ON_FORGET_PASSWORD',forgetPasswordUser )
}
export default function* rootSaga() {
yield all([
registerUser(),
loginUser(),
forgetPassword()
])
}
actions.js
const actions={
ON_REGISTER: "ON_REGISTER",
ON_REGISTER_SUCCESS: "ON_REGISTER_SUCCESS",
ON_REGISTER_FAILURE: "ON_REGISTER_FAILURE",
ON_LOGIN: "ON_LOGIN",
ON_LOGIN_SUCCESS: "ON_LOGIN_SUCCESS",
ON_LOGIN_FAILURE: "ON_LOGIN_FAILURE",
ON_PASSWORD_RECOVERY_SUCCESS: "ON_PASSWORD_RECOVERY_SUCCESS",
ON_FORGET_PASSWORD:"ON_FORGET_PASSWORD",
}
export default actions;
reducer.js
import actions from "./actions";
const stateInit={
registerUser: {
first_name:"",
last_name: "",
email: "",
password:"",
confirm_password:"",
},
loadRegister:false,
signedUp:false,
loginUser: {
email: "",
password: "",
},
}
export default function auth(state=stateInit, action){
switch (action.type){
case actions.ON_REGISTER:{
return {
...state,
registerUser: action.payload,
loadRegister: true
}
}
case actions.ON_REGISTER_SUCCESS: {
return {
...state,
loginUser: action.payload,
loadRegister: false,
signedUp: true,
}
}
case actions.ON_REGISTER_FAILURE:{
alert(action.payload)
return {
...state,
loadRegister: false,
signedUp :false,
}
}
case action.ON_LOGIN :{
alert("on login")
return {
...state,
}
}
case action.ON_LOGIN_SUCCESS:{
alert("ogin suceess")
return {
...state
}
}
case action.ON_LOGIN_FAILURE :{
return {
...state,
}
}
case action.ON_FORGET_PASSWORD: {
alert("FORGET password")
return {
...state
}
}
case action.ON_PASSWORD_RECOVERY_SUCCESS:{
alert("password recovered")
}
default:{
return {
...state
}
}
}
}
Here I have the code, which I have. the same structure which for login as in the register, but when I have success response it dies not go to that actions.
I think there is a small typo in your switch statement. All the actions with their types should be actions.TYPE
For the case of login, you put action.ON_LOGIN instead of actions.ON_LOGIN
If an error occurs in the yield call(refresh) of the getPost function, the GETPOST_REQUEST action continues regardless of whether an error has occurred.
However, if an error occurs in the getPost function I don’t want the action to run anymore and stop and end like
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
this is my code how can i fix?
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") {
yield call(refresh); // if this error i want to stop .
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function refreshAPI() {
// console.log('data::', data);
return axiosInstace.post("/kakao/refresh");
}
function* refresh() {
try {
const result = yield call(refreshAPI);
yield AsyncStorage.setItem(
"accesstoken",
`${result.data.accessToken}`,
() => {
// console.log('accesstoken 재발급 저장 완료');
console.log("accesstoken3333333333333333333", result.data.accessToken);
}
);
yield put({
type: REFRESH_SUCCESS,
data: result.data,
});
} catch (err) {
console.log("refresh err.response.data:", err.response.data);
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
}
}
You can make a small update to the code by doing the following:
return a boolean value from your refresh generator function.
The boolean would be:
true if refresh was successful
false if refresh failed.
set yield call(refresh); to a const declaration to capture this returned boolean value
if this captured boolean value is false, exit the generator function by returning early
Try the code below.
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 refreshSuccess = yield call(refresh);
if (!refreshSuccess) {
return;
}
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function refreshAPI() {
return axiosInstace.post('/kakao/refresh');
}
/**
* #returns {boolean}
* - `true` if refresh is successful
* - `false` if refresh failed
*/
function* refresh() {
let refreshSuccess;
try {
const result = yield call(refreshAPI);
yield AsyncStorage.setItem(
'accesstoken',
`${result.data.accessToken}`,
() => {
// console.log('accesstoken 재발급 저장 완료');
console.log('accesstoken3333333333333333333', result.data.accessToken);
}
);
yield put({
type: REFRESH_SUCCESS,
data: result.data,
});
refreshSuccess = true;
} catch (err) {
console.log('refresh err.response.data:', err.response.data);
yield put({
type: REFRESH_FAILURE,
error: err.response.data,
});
refreshSuccess = false;
} finally {
return refreshSuccess;
}
}
After receiving the result value of the refresh function, axiosInstace is executed before saving the accesstoken to AsyncStorage, so the updated token cannot be retrieved in axios.js through AsyncStorage.getItem. i want to save accesstoken first in refresh and get acecesstoken in axios.js and send to axiosInstace
How can I solve this problem?
this is my code
(saga.js)
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') {
yield put({
type: REFRESH_REQUEST,
// data: action.data,
});
yield put({
type: GETPOST_REQUEST,
data: action.data,
});
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
function refreshAPI() {
return axiosInstace.post('/kakao/refresh');
}
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,
});
}
}
(axios.js)
AxiosInstance.interceptors.request.use(async (cfg) => {
const acecesstoken = await AsyncStorage.getItem('accesstoken');
const refreshtoken = await AsyncStorage.getItem('refreshtoken');
if (acecesstoken) {
cfg.headers.Authorization = `Bearer ${acecesstoken} ${refreshtoken}`;
}
return cfg;
});
export default AxiosInstance;
A simple solution would be to call your refresh() generator directly:
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') {
yield call(refresh);
// you could also redispatch the original action
yield put(action);
} else {
yield put({
type: GETPOST_FAILURE,
error: err.response.data,
});
}
}
}
Alternatively your can start a race between REFRESH_SUCCESS and REFRESH_FAILURE:
const { success, failure } = yield race({
success: take('REFRESH_SUCCESS'),
failure: take('REFRESH_FAILURE'),
});
if(success) {
// continue
} else {
// handle refresh failure
}
I'm implementing get all image by type with redux-saga. I have 2 types, let's say, type kristik and type motif.
When I'm implementing type kristik, it got successful response, but when it comes to type motif, the response is error.
here my code that has the error in console
componentWillMount() => {
const { dispatch } = this.props;
dispatch(getAllMotif());
}
I got error in dispatch(getAllMotif()); in commponentWillMount()
Here my getAllMotif() code
getAllMotif(token) {
const path = `motif`;
const method = 'get';
return request.process(method, path, null, token);
},
Here my sagas getAllMotif code
export function* getAllMotif() {
try {
let { detail } = yield select(state => state.user);
const result = yield call(API.getAllMotif, detail.api_token);
yield put({
type: types.GET_ALL_MOTIF_SUCCESS,
payload: result,
});
} catch (err) {
yield put(handleError(err));
yield put({
type: types.GET_ALL_MOTIF_FAILURE,
payload: err,
});
}
}
here my reducer
case types.GET_ALL_MOTIF_SUCCESS:
return {
...state,
motif: [
...action.payload.data.data
]
};
here my request code
internals.process = (method, path, payload, token, contentType=internals.contentType) => {
const request = {
url: `${API_URL}/${path}`,
method: method,
headers: {
'Content-Type': contentType,
'Accept': 'application/json',
},
};
if (token) {
request.params = {
token: token,
};
}
if (payload) {
request.data = payload;
}
return axios.request(request)
.then(res => {
if (![200, 201].includes(res.status)) {
throw new Error(res.status);
}
return res.data;
})
.catch((error) => {
console.error(method, path, error);
return Promise.reject({
message: error.response.data.error,
code: error.response.status
});
});
};
I don't know why in this type get error, because in type kristik also have very similar code.
You didn't dispatch an action that wasn't a plain object, your function getAllMotif not return a plain object. That lead to the error here.
You should dispatch an normal action
getAllMotifAction(token) {
const path = `motif`;
const method = 'get';
return { type: 'GET_ALL_MOTIF', data: { path, method } };
},
Then in in saga, you catch this action and handle it with your saga function
takeLatest('GET_ALL_MOTIF', getAllMotif);