await of generator completing in redux-saga - reactjs

I have code in component,
I need to get updated authorizedError value in function, but i get old value authorized error
// login component
const authorizedError = useSelector((state: RootState) => state.user.authorizedError);
const onSignInPress = useCallback(async () => {
await dispatch(userActions.postLoginUser({username: email, password}));
if (authorizedError) {
setNotificationErrors(['Wrong login or password'])
showNotification();
}
}, [authorizedError, validate, email, password]);
// postLoginUserSaga.js
export default function* postLoginUserSaga({
payload,
}: PayloadAction<UserCredentialsPayload>) {
try {
yield put(setSignInError(false));
const {
data: {
payload: { access_token },
status,
},
} = yield transport.post(URLS.postLoginUserURL, payload);
if (status !== "Ok") {
throw new Error(status);
}
yield setItemAsync(ACCESS_TOKEN_KEY, access_token);
yield put(setSignIn(true));
} catch (error) {
console.error("User login failed", error);
yield put(setSignInError(true));
}
}
// sagaRoot file
export default function* userRootSaga() {
yield all([
checkAuthSaga(),
takeEvery(actions.postLoginUser, postLoginUserSaga),
takeEvery(actions.postRegistrationUser, postRegistrationUserSaga),
takeEvery(actions.getProfileData, getProfileDataSaga),
]);
}

Redux actions don't return a promise, you can't use them like this.
If you want to use the promise API you can use the redux-thunk middleware which supports it.
If you want to use sagas you can add a callback action property instead.
// in component callback
dispatch(userActions.postLoginUser({username: email, password, callback: (authorizedError) => {
if (authorizedError) {
setNotificationErrors(['Wrong login or password'])
showNotification();
}
}));
// in saga
try {
...
action.callback();
} catch (err) {
action.callback(err);
}
Although that has its own issues.
Usually you communicate from sagas back to components by changing the redux state, so you can e.g. have a state for redux error, and based on that field show the error message or show different component if the login was succesful.

Related

how to pass parameter to generator function in redux saga from jsx?

I have 3 generator function first is "loginUserStart" where the actual request comes then the second one is "LoginUserAsync" which is called in the "loginUserStart" and third is api call function
so I am trying to pass the parameter from my signin component to the loginUserStart function but whenever I console.log(arguments) it is showing nothing
Code:-
Sign-in component
const login = async () => {
arr.userEmail = "sample_email";
arr.userPassword = "sample_password";
console.log(arr);
signinUserStart(arr);
};
const logSubmit = () => {
login();
};
const mapDispatchToProps = (dispatch) => ({
signinUserStart: (data) => dispatch(signinUserStart(data))
});
Action file code
export const signinUserStart = (data) => ({
type: UserActionTypes.Set_SigninUser_Start,
payload: data
})
saga File code
API generator function code
export async function fetchUser(info) {
console.log(info);
const email = 'Admin#gmail.com'; //sample_email
// const passwords = info.userPassword;
const password = 'Admin#123'; //sample_password
try {
const user = await axios.post("http://localhost:5050/sign", {
data: {
email: email,
password: password,
},
});
console.log(user);
return user;
} catch (error) {
console.log(error);
return error;
}
}
LoginUserAsync function
export function* LoginUserAsync(data) {
console.log("in saga");
console.log(data);
try {
let userInfo = yield call(fetchUser, data)
console.log(userInfo);
yield put(setUserId('62b1c5ee515317d42239066a')); //sample_token
yield put(setCurrentUserName(userInfo.data.userName));
} catch (err) {
console.log(err);
}
}
loginUserStart function
export function* loginUserStart(action) {
console.log(action.payload);//not logging anything for showing in console
yield takeLatest(UserActionTypes.Set_SigninUser_Start, LoginUserAsync(action));
}
I can't be sure without seeing more code, but assuming that loginUserStart is either root saga or started from root saga it means there is no action for it to receive.
The main issue I think is this line
yield takeLatest(UserActionTypes.Set_SigninUser_Start, LoginUserAsync(action));
In the second parameter you are calling the generator function which is wrong, instead you should be passing the saga itself (as reference).
So it should look like this:
yield takeLatest(UserActionTypes.Set_SigninUser_Start, LoginUserAsync);
This way, the Redux Saga library will then call LoginUserAsync when Set_SigninUser_Start is dispatched with first param correctly set to the action object.

How to display a message on screen depending on the response from a saga handler

Contact Saga handler
export function* handlePostContactUser(action) {
try {
yield call(axios.post, '*endpoint*', action.data);
} catch (error) {
throw error;
}
};
Front-end form handleSubmit function:
let handleContactFormSubmit = () => {
let name = input.name;
let email = input.email;
let message = input.message;
dispatch({ type: 'POST_CONTACT_USER', data: {name, email, message, date}});
}
RootSaga
export function* watcherSaga() {
yield all([
takeEvery("POST_CONTACT_USER", handlePostContactUser)
]);
};
Based on this code, how could I display a message on the front end after the form submits, based on if it was successful or not? If it was, then just redirect/refresh the page, if not, display an error on the screen for the user to see
Since sagas are basically generators, what you need to do is fire an action after the yield call(axios). Change the store this way and get the result in your component.
In order to handle an error, put an action in the catch block doing the same thing.
export function* handlePostContactUser(action) {
try {
yield call(axios.post, '*endpoint*', action.data);
yield put('MY_SUCCESS_ACTION')
} catch (error) {
yield put('MY_ERROR_ACTION')
}
};
Update the store like this and then get the store properties that you need in the component.

React and Redux toolkit - reject after promise

I'm working on a React Native app. I have a signup screen which has a button, onclick:
const handleClick = (country: string, number: string): void => {
dispatch(registerUser({ country, number }))
.then(function (response) {
console.log("here", response);
navigation.navigate(AuthRoutes.Confirm);
})
.catch(function (e) {
console.log('rejected', e);
});
};
The registerUser function:
export const registerUser = createAsyncThunk(
'user/register',
async ({ country, number }: loginDataType, { rejectWithValue }) => {
try {
const response = await bdzApi.post('/register', { country, number });
return response.data;
} catch (err) {
console.log(err);
return rejectWithValue(err.message);
}
},
);
I have one of my extraReducers that is indeed called, proving that it's effectively rejected.
.addCase(registerUser.rejected, (state, {meta,payload,error }) => {
state.loginState = 'denied';
console.log(`nope : ${JSON.stringify(payload)}`);
})
But the signup component gets processed normally, logging "here" and navigating to the Confirm screen. Why is that?
A thunk created with createAsyncThunk will always resolve but if you want to catch it in the function that dispatches the thunk you have to use unwrapResults.
The thunks generated by createAsyncThunk will always return a resolved promise with either the fulfilled action object or rejected action object inside, as appropriate.
The calling logic may wish to treat these actions as if they were the original promise contents. Redux Toolkit exports an unwrapResult function that can be used to extract the payload of a fulfilled action or to throw either the error or, if available, payload created by rejectWithValue from a rejected action:
import { unwrapResult } from '#reduxjs/toolkit'
// in the component
const onClick = () => {
dispatch(fetchUserById(userId))
.then(unwrapResult)
.then(originalPromiseResult => {})
.catch(rejectedValueOrSerializedError => {})
}

How to make await work with redux Saga in React?

The await does not seem to work with Redux saga. I need to wait for my API call to finish and then execute the remaining code. What happens now is that AFTER CALL gets printed before the RESPONSE which means await does not seem to work at all. I'm using async calls but not sure what needs to be done extra from the redux saga side?
async componentWillMount() {
console.log("BEFORE CALL")
await this.props.getUserCredit()
console.log("AFTER CALL")
}
mapDispatchToProps = (dispatch) => {
return {
getUserCredit: () => dispatch(getUserCredit()),
}
};
connect(null, mapDispatchToProps)(MyComponent);
Action
export const getUserCredit = () => {
return {
type: GET_USER_CREDIT,
};
};
Redux Saga
const getUserCreditRequest = async () => {
const response = await Api.get(getCreditUrl)
console.log("REPONSE!!!")
console.log(response)
return response
}
function* getUserCredits() {
try {
const response = yield call(getUserCreditRequest);
if (response.status === okStatus) {
yield put({
userCredit: response.data.userCredit
}
));
}
} catch (error) {}
}
export function* getUserCredit() {
yield takeLatest(GET_USER_CREDIT, getUserCredits);
}
export default function* rootSaga() {
yield all([fork(getUserCredit)]);
}
Normally, init / fetching takes place during componentDidMount and don't use async or await inside components. Let the saga middleware do its thing via yield.
// In your component
componentDidMount() { // not async
this.props.getUserCredit(); // dispatch `GET_USER_CREDIT` action
}
mapDispatchToProps = (dispatch) => {
return {
getUserCredit: () => dispatch(getUserCredit()),
}
};
connect(null, mapDispatchToProps)(YourComponent);
You shouldn't be using async/await pattern. As redux-saga handles it by the yield keyword. By the time call is resolved you will have the value available in response.
in actions.js, you should have an action that will carry your data to your reducer:
export function getUserCredits(userCredit) {
return {
type: types.GET_USER_CREDIT_SUCCESS,
payload: userCredit
};
}
Your saga should handle the API call like so:
function* getUserCredits() {
try {
const response = yield axios.get(getCreditUrl); <--- This should work
// No need for if here, the saga will catch an error if the previous call failed
yield put(actions.getUserCredits(response.data.userCredit));
} catch (error) {
console.log(error);
}
}
EDIT: example of using axios with redux-saga

how to setstate after saga async request

I'm using redux-saga in my project.
I used redux-thunk before, so i can't setState ends of some async request. like
this.props.thunkAsync()
.then(){
this.setState({ '' });
}
Since thunk returns promise, i could use 'then'.
But i can't do this with saga, because saga doesn't return promise.
So i did it in componentWillReceiveProps by checking flag props (like REQUEST_SUCCESS,REQUEST_WAITING...) has been changed.
I think it's not good way to solve this problem.
So.. My question is how can i do some works when async request ends in redux-saga!
But i can't do this with saga, because saga doesn't return promise
Redux-saga is slightly different from thunk since it is process manager, not simple middleware: thunk performs reaction only on fired actions, but saga has its own "process" (Formally callback tick domain) and can manipulate with actions by effects.
Usual way to perform async actions with redux-saga is splitting original actions to ACTION_REQUEST, ACTION_SUCCESS and ACTION_FAILURE variants. Then reducer accepts only SUCCESS/FAILURE actions, and maybe REQUEST for optimistic updates.
In that case, your saga process can be like following
function* actionNameSaga(action) {
try {
const info = yield call(fetch, { params: action.params }
yield put('ACTION_NAME_SUCCESS', info)
} catch(err) {
yield put('ACTION_NAME_FAILURE', err)
}
function* rootSaga() {
yield takeEvery('ACTION_NAME', actionNameSaga)
}
Keep in mind that yield operation itself is not about promise waiting - it just delegates async waiting to saga process manager.
Every api call you make is processed as an async request but handled using a generator function in a saga.
So, After a successful api call, you can do the following possible things.
Make another api call like
function* onLogin(action) {
try {
const { userName, password } = action;
const response = yield call(LoginService.login, userName, password);
yield put(LoginService.loginSuccess(response.user.id));
const branchDetails = yield call(ProfileService.fetchBranchDetails, response.user.user_type_id);
yield put(ProfileActions.fetchBranchDetailsSuccess(branchDetails));
} catch (error) {
yield put(ProfileActions.fetchUserDetailsError(error));
}
}
Pass a Callback after successfull api
onLoginClick() {
const { userName, password } = this.state;
this.props.login(userName, password, this.onLoginSuccess);
}
onLoginSuccess(userDetails) {
this.setState({ userDetails });
}
function *onLogin(action) {
try {
const { userName, password, onLoginSuccess } = action;
const response = yield call(LoginService.login, userName, password);
if (onLoginSuccess) {
onLoginSuccess(response);
}
yield put(LoginService.loginSuccess(response.user.id));
const branchDetails = yield call(ProfileService.fetchBranchDetails,
response.user.user_type_id);
yield put(ProfileActions.fetchBranchDetailsSuccess(branchDetails));
} catch (error) {
yield put(ProfileActions.fetchUserDetailsError(error));
}
}
Update Reducer State and get from props by mapStateToProps
yield put(LoginService.loginSuccess(response.user.id));
#connect(
state => ({
usedDetails: state.user.get('usedDetails'),
})
)
static getDerivedStateFromProps(nextProps, prevState) {
const { usedDetails } = nextProps;
return {
usedDetails
}
}
I was stuck with the same problem...
My solution was wrapping the dispatch in a promise and call the resolve and reject in a saga function...
I created a hook to wrap the dispatch. You can see my example here:
https://github.com/ricardocanelas/redux-saga-promise-example
I hope that can help somebody.
You can do it this way. From component props you call the saga method and pass the function you want to execute after success or failure, like below
export function* login({ payload }) {
const url = 'localhost://login.json';
try {
const response = yield call(App_Service, { url, method: 'GET' });
if (response.result === 'ok' && response.data.body) {
yield put(fetchDataActionCreators.getLoginSuccess(response.data.body));
//function passed as param from component, pass true if success
payload.callback(true);
}
} catch (e) {
//function passed as param from component, pass false if failure
payload.callback(false);
console.log(e);
}
}
export function* watchLogin() {
while (true) {
const action = yield take(LOGIN);
yield* login(action);
}
}
export default function* () {
yield all([
fork(watchLogin)
]);
}
In component call call setState method in the function you pass to saga as param
login() {
// store
this.props.getServiceDetails({
callback:(success) => this.onLoginSuccess(success)
})
}
onLoginSuccess = (success) => {
this.setState({
login:success
})
alert('login '+success)
}

Resources