How to catch changes in redux store after dispatching async action to redux-saga - reactjs

In my Login component i try to login on button click, corresponding function 'handleLogin' is then getting called. In this function i dispatch an async function with my user credentials as a payload. In my saga, i am making a request putting an error to store if the response got such field, else i set a user in there. By default the error field in store is 'false'. In my component where i dispatch an action, i want to know the state of error field in store right after the successfull/unsuccessful response. When i try to login in with wrong credentials and log the state of error into the console, i first get the 'old' (initial) value of error: false, instead of error: true. Only after second login try, the error get set to true. Is there a way to know the actual state of error in store, right after dispatching async action?
Reducer:
const initialState = {
userData: [],
error: false
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case SET_USER:
return {
...state,
userData: action.payload
}
case SET_ERROR:
return {
...state,
error: action.payload
}
default:
return state;
}
};
SAGAS:
function* handleUserLoad(payload) {
try {
const user = yield call(
loginUser,
payload
);
if(user.data.errors) {
yield put(setError(true))
} else {
yield put(setError(false))
yield put(setUser(user.data.data.loginUser));
}
} catch (error) {
console.log(error);
}
}
export default function* userSaga() {
while (1) {
const {payload} = yield take(GET_USER);
yield call(handleUserLoad,payload);
}
}
PART OF LOGIN COMPONENT:
const handleLogin = async() => {
setShouldValidate(true);
if (allFieldsSet) {
try {
loginUser(user)
// LOGIN ERROR HERE DISPLAYS THE PREVIOUS STATE 'false'
// INSTEAD OF ACTUAL TRUE AFTER loginUser
// IF I LOG WITH WRONG CREDENTIALS
console.log(loginError);
} catch (e) {
console.error(e);
}
}
};

loginUser(user) in your component is an async function, so you can't expect console.log(loginError); to log the value of loginError correctly in next line.
Solution:
First:
You can bind your login component to error state in redux. Once the state is modified by Saga based upon successful/unsuccesful response, your login component will re-render. So, in render if you will console.log then you may see the value of loginError coming correctly. So, in render only you can put the component what you want on the basis of "loginError" to be shown.
Second:
You can skip using Saga and directly await for your login API response(call API using fetch/axios from here itself) in Login Component at the current place itself. In that case you will be able to get the correct login response just after the await calls gets completed and than you can continue executing your code

I have made slight changes to default value of error in store. Now it is 'null' instead of false. And thus i can check if it was set to false/true in useEffect and do the other logic.

Related

deauthenticateUser() function not working as expecting in react native

I am trying to understand some socket and props behavior I'm seeing in my react-native app. I am using a redux store in this app. When the user manually signs out, I call the following:
if (this.props.authenticated) {
this.props.deauthenticateUser().then((res) => {
console.log('res: ', res); // {"payload": {"authenticated": false, "error": ""}, "type": "DEAUTHENTICATE_USER"}
console.log('this.props.authenticated 26: ', this.props.authenticated); // is true here, should be false
if (!this.props.authenticated) this.props.navigation.navigate('Login'); // Never happens because authenticated is still true
});
}
}
By the way, the redux section of that page/screen looks like this:
// Redux Mapping
const mapStateToProps = (state) => {
return { ...state.User };
};
const mapDispatchToProps = (dispatch) => ({
deauthenticateUser: async (user) => await dispatch(deauthenticateUser(user)),
});
export default connect(mapStateToProps, mapDispatchToProps)(LogoutScreen);
This is what the deauthenticateUser function looks like:
export const deauthenticateUser = () => {
console.log('deauthenticateUser() firing...'); // I see this
return async (dispatch) => {
await SocketDriver.Deauthenticate(); // This is successful
await LocalDataStore.Invalidate(); // This is successful
// Clear unauthorized application state
dispatch(clearOverview());
dispatch(clearSignature());
dispatch(clearUser());
console.log('finished clearing data...'); // I see this
return {
type: DEAUTHENTICATE_USER,
payload: { authenticated: false, error: '' },
};
};
};
This calls my User reducer, which looks like this:
case 'DEAUTHENTICATE_USER':
return { ...state, ...payload };
Even when I try a setTimeout to give plenty of time for deauthenticateUser() to complete, I find that authenticated never gets to a false state.
What am I potentially missing here? The logic seems to look solid to my eyes. Nothing bombs out, and when I console.log the data out at various points, it all looks as it should, EXCEPT for the props.authenticated property, which remains set to true.
Is it possible that my Logout screen doesn't get the final props update? And, if so, why would that be? Even when I try to call the deauthenticateUser() function multiple times -- just to test it -- I never see this.props.authenticated ever console.log as false.

Use an Saga action as condition for an if statement

I'm actually trying to implement redux-saga into my app. To keep it easy in the beginning, I tried to add saga into my login component first.
But I'm struggling rn by adding an if statement which redirects the UI to an page if my login action loginSuccess() is dispatched. I would like to implement smth like the commented statement below:
const onSubmitForm = (e?: React.FormEvent<HTMLFormElement>) => {
if (e) {
e.preventDefault();
}
dispatch(actions.submitLogin());
// if (actions.loginSuccess() && !actions.loginError) {
// history.push('/');
// }
};
How the actions are passed:
// After the API was fetched:
if (response.message === 'login') {
yield put(actions.loginSuccess());
} else {
yield put(actions.loginError('Username or password is incorrect'));
}
You would handle the "loginSuccess" action in your reducer to change your state, or possibly by another saga listening for the loginSuccess action to perform various other actions when the user logs in. e.g. fetching user data, redirecting the location, etc.

Redux saga - error binding - appropriate way to bind error to local state

We have a ReactJS app that uses redux-saga.
In redux-saga we perform all the data grabbing (http webrequests).
Moreover: We use React hooks.
Scenario:
We have a button that dispatches an action to redux. Redux-saga handles this action and fires a webrequest.
Now, this webrequest fails (for instance server is down).
What we want to do now is to change the text of the button.
So in fact, we want to get back to the "scope" where the action has been dispatched.
Question:
What is the appropriate way to get back from the redux-saga to the button ?
I have found this:
https://github.com/ricardocanelas/redux-saga-promise-example
Is this appropriate to use with hooks in year 2021 ?
That example you posted sounds needlessly convoluted. Error handling with redux-sagas can be relatively straightforward. We'll consider 3 parts of the application - the UI, the store, and the actions/saga chain.
The UI:
const SomeUIThing = () => {
const callError = useSelector(state => state.somewhere.error);
const dispatch = useDispatch();
return (
<button onClick={
dispatch({
type: "API_CALL_REQUEST"
})
}>
{callError ? 'There was an error' : 'Click Me!'}
</button>
)
}
You can see that the UI is reading from the store. When clicked, it will dispatch and API_CALL_REQUEST action to the store. If there is an error logged in the store, it will conditionally render the button of the text, which is what it sounds like you wanted.
The Store
You'll need some actions and reducers to be able to create or clear an error in the store. So the initial store might look like this:
const initialState = {
somewhere: {
error: undefined,
data: undefined
}
}
function reducer(state = initialState, action){
switch(action.type){
case "API_CALL_SUCCESS":
return {
...state,
somewhere: {
...state.somewhere,
data: action.payload
}
}
case "API_CALL_FAILURE":
return {
...state,
somewhere: {
...state.somewhere,
error: action.payload
}
}
case "CLEAR":
return {
...state,
somewhere: initialState.somewhere
}
default:
return state;
}
}
Now your reducer and your store are equipped to handle some basic api call responses, for both failures and successes. Now you let the sagas handle the api call logic flow
Sagas
function* handleApiCall(){
try {
// Make your api call:
const response = yield call(fetch, "your/api/route");
// If it succeeds, put the response data in the store
yield put({ type: "API_CALL_SUCCESS", payload: response });
} catch (e) {
// If there are any failures, put them in the store as an error
yield put({ type: "API_CALL_ERROR", payload: e })
}
}
function* watchHandleApiCall(){
yield takeEvery("API_CALL_REQUEST", handleApiCall)
}
This last section is where the api call is handled. When you click the button in the UI, watchHandleApiCall listens for the API_CALL_REQUEST that is dispatched from the button click. It then fires the handleApiCall saga, which makes the call. If the call succeeds, a success action is fired off the to store. If it fails, or if there are any errors, an error action is fired off to the store. The UI can then read any error values from the store and react accordingly.
So as you see, with a try/catch block, handling errors within sagas can be pretty straightforward, so long as you've set up your store to hold errors.

How to get the value as props in a different component

In my react application, I have three parallel components. In my first component, I am doing an API call and based on the response I am routing the flow to Validated or Non-Validated Component.
Once the user is routed to validated component, there is a button on click of which user should be redirected to another component which should display the data in API response (first component) as key value pair. I am using Redux for state management.
The issue I am facing is the data is dispatched as an empty object from the store. I am not sure where I am going wrong but when I am debugging the app, I see the the action is not getting dispatched to the store and it's always returning me the initial state.
action.js-
export const setPoiData = (poiData) => dispatch => {
console.log('inside actions');
dispatch({
type: SET_POI_DATA,
payload: poiData
})
}
Reducer.js-
const initialState = {
poiData: {},
}
const reducerFunc = (state = initialState, action) => {
switch (action.type) {
case SET_POI_DATA:
console.log('inside poi reducers');
return {...state,poiData: action.payload}
default: return {...state}
}
}
Component 1-
//API call
Detail Component-
To get the data from store I am doing something like below-
componentDidMount() {
console.log(this.props.poiData)
}
function mapStateToProps(state) {
return {
poiData: state.poiData,
}
}
const mapDispatchToProps = dispatch => ({
setPoiData(data) {
dispatch(setPoiData(data));
}
})
I am not sure where I am going wrong. Can someone suggest me how to proceed ahead on this?
inside componentDidMount() you must call action like this this.props.setPoiData(<your data here>);

React Redux-Thunk with Typescript

So I am trying to type my Redux Actions with Redux Thunk
Here is an Action
export function logout() {
return async(dispatch) => {
try {
const value = localStorage.removeItem("token");
dispatch(cons.updateLoginStatus(false));
return true;
} catch (err) {
console.error(err);
}
}
}
Here is the connect file
const mapStateToProps = (store: IAppState) => {
return {
isLoggedIn: store.employee.isLoggedIn
}
}
const mapDispatchToProps = {
logout
}
export default connect(mapStateToProps, mapDispatchToProps)(Auth);
When I call await this.props.logout(); I would like to access the returned true But typescript only sees the function being returned.
So how do I type the function to await for returned value?
Or is there any better way to do this?
What you're attempting seems a bit, anti-pattern.
The function is meant to be fire and forget, so that you don't hang the UI.
The login function should dispatch to your reducer that state has now gone from
loggedIn: false
to
loggedIn: true
If you need some magic with response aka throw an exception if login fails, then I would recommend something like
this.props.login().catch(err => console.log(err)).then(() => doSomething));
However, I strongly recommend you just dispatch and listen to ComponentDidUpdate lifecycle hook to redirect or whatever you may need to do.
You need to invoke the logout function by dispatching the appropriate action

Resources