Dispatching an action from another action with props (Redux) - reactjs

So, I have this login() action that redirects the user to the feed if the login is successful. I also have this register() action that creates a new user and calls the login() action after.
The problem is that login() isn't receiving the props when called from register(), so I can't call the this.navigation.navigate('Feed') from there.
userActions.js
function register(user) {
return dispatch => {
dispatch(request(user));
let { username, password } = user
userService.register(user)
.then(
user => {
dispatch(success(user));
dispatch(login(username, password)) //calls login() after the user is created
},
error => {
dispatch(failure(error.toString()));
}
);
};
function request(user) { return { type: userConstants.REGISTER_REQUEST, user } }
function success(user) { return { type: userConstants.REGISTER_SUCCESS, user } }
function failure(error) { return { type: userConstants.REGISTER_FAILURE, error } }
}
function login(username, password) {
return dispatch => {
dispatch(request({ username }));
userService.login(username, password)
.then(
user => {
dispatch(success(user));
this.navigation.navigate('Feed') //this is throwing "undefined is not an object" when login() is dispatched from register()
},
error => {
dispatch(failure(error.toString()));
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
What am I missing here?

I think your code needs to be fixed a little.
It's not good solution to deal navigation in redux actions. To avoid this, Saga is recommended to be used with Redux.
However, with this your code, you didn't pass navigation property to action so you need to pass props variable to register action first.
Please try like this.
function register(user, props) {
...
dispatch(login(username, password, props))
...
function login(username, password, props) {
...
props.navigation.navigate('Feed')
Then navigation will work.
Hope this helps you to understand)
I recommend to use Redux and Saga as a stack according to my experience.

Related

What is wrong with my redux dispatch statement?

I am getting data from local storage and wanna dispatch a redux function.
But I think the action is not calling the try block in the function.
In the redux
export function testLogin(loginStatus) {
return async dispatch => {
try {
alert('me here')
dispatch({
type: TEST_LOGIN_STATUS,
payload: loginStatus,
});
} catch (error) {
console.log('not logged in');
}
};
}
export const authReducer = (state = initialState, action) => {
switch (action.type) {
case LOGGED_IN:
return {...state, token: action.payload, loggedIn: true};
case TEST_LOGIN_STATUS:
return {...state, loggedIn: action.payload};
default:
return state;
}
};
as you can see I am getting the status as param for testLogin action function.
Here is what I am doing in the Home screen.When user open the app. I need to test if the user is logged in or not by checking the local storage
useEffect(() => {
async function getStorageValue() {
let value;
try {
value = await AsyncStorage.getItem('isLoggedIn');
if (value === 'true') {
dispatch(testLogin(true));
} else {
dispatch(testLogin(false));
}
} catch (e) {
// handle here
} finally {
}
}
getStorageValue();
}, []);
Since Async storage accept only strings in am testing the value and returning true or false.
The thing is even if I am logged in. The when I check the redux loginStatus I am always logged out . Is there anything wrong with dispatch function?
It looks like testLogin is a higher order function. i.e. it returns a function that accepts dispatch as an argument.
In your useEffect try block try the following:
testLogin(value === 'true')(dispatch)
(replacing the if/else)
Correct me if I am wrong. But are you targeting the testLogin correctly?
For example I've seen this issue before and people simply forgot to target the Action correctly.
For example an object named LoginActions.
const LoginActions = {... testLogin(loginStatus) { ...} }
And then in the Code where I want to dispatch
import { LoginActions } from "../store/LoginActions" //<-- Import here
useEffect(() => {
async function getStorageValue() {
let value;
try {
value = await AsyncStorage.getItem('isLoggedIn');
if (value === 'true') {
dispatch(LoginActions.testLogin(true)) //<-- Dispatch here.
} else {
dispatch(LoginActions.testLogin(false));
}
} catch (e) {
// handle here
} finally {
}
}
getStorageValue();
}, []);
Again, I might have misunderstood but I was just wondering if this was the case?

redux doesn't update props immediately

I have a functional component in a react native application, and I'm dispatching an HTTP call. If some error occurs, I store it in redux, the problem is when I access to the error value, I get null
function IncomingOrder(props) {
function acceptOrder(orderId, coords) { // I call this from a button
const response = await actions
.accept(token, orderId, coords) //this is the async function
.catch((e) => {
showError(props.error);
});
}
...
}
...
function mapStateToProps(state) {
return {
token: state.auth.token,
error: state.deliveries.error
};
}
function mapDispatchToProps(dispatch) {
return {
actions: {
accept: (token, id, coords) => {
return dispatch(Order.accept(token, id, coords));
}
}
};
}
The problem is most likely on acceptOrder() async action creator.
Redux dispatch wont update immediately after your promise resolves/rejects. So by the time your error handler (Promise.prototype.catch or catch(){}) kicks in, there is no guarantee the action has been dispatched or the state tree updated.
What you want to do instead is to have this logic on the async action creator
// action module Order.js
export const accept = (token, id, coords) => dispatch => {
fetch('/my/api/endpoint')
.then(response => doSomethingWith(response))
.catch(err => dispatch(loadingFailed(err)); // <----- THIS is the important line
}
// reducer.js
// you want your reducer here to handle the action created by loadingFailed action creator. THEN you want to put the error in the state.
// IncomingOrder.js
function IncomingOrder(props) {
function acceptOrder(orderId, coords) { // I call this from a button
actions.accept(token, orderId, coords);
// You don't need to wait for this, as you're not gonna work with the result of this call. Instead, the result of this call is put on the state by your reducer.
}
render() {
const { error } => this.props; // you take the error from the state, which was put in here by loadingFailed()
if (error) {
// render your error UI
} else {
// render your regular UI
}
}
}
function mapStateToProps(state) {
return {
token: state.auth.token,
error: state.deliveries.error // <--- I suppose your error is already here (?)
};
}

Reactjs Redux Login/Registration Alerts

I was searching for a simple Login/Registration System and stumbled about this tutorial:
http://jasonwatmore.com/post/2017/09/16/react-redux-user-registration-and-login-tutorial-example
I tried it and added a real mongoDB Backend to remove the Mock-Backend. It was my first time doing Redux, but after a lot of struggle everything is working finde now! You can see the result here :
https://genko.de/login
Just one thing is not working at all and i have no clue how to solve it. Since I want to avoid bootstrap, I have a big problem with the error handling.
I want to display a snackbar from Material-Ui for following cases:
Username or Password is invalid
Successfull Logged in
Username is already taken (Registration)
In fact there are already some actions and reducers in my redux files, but to be honest, I just copied them from the tutorial and I have no clue how to work with them.
Following function is triggered by the Login button:
handleSubmit(e) {
e.preventDefault();
this.setState({ submitted: true });
const { username, password } = this.state;
const { dispatch } = this.props;
if (username && password) {
dispatch(userActions.login(username, password))
}
}
And this is my login function (user.actions):
function login(username, password) {
return dispatch => {
dispatch(request({ username }));
userService.login(username, password)
.then(
user => {
dispatch(success(user));
history.goBack();
},
error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
alert.actions:
import { alertConstants } from '../_constants';
export const alertActions = {
success,
error,
clear
};
function success(message) {
return { type: alertConstants.SUCCESS, message };
}
function error(message) {
return { type: alert, message };
}
function clear() {
return { type: alertConstants.CLEAR };
}
And finally my alert.constants:
export const alertConstants = {
SUCCESS: 'ALERT_SUCCESS',
ERROR: 'ALERT_ERROR',
CLEAR: 'ALERT_CLEAR'
};
Do you have some hints for me or suggestions?
Best regards :)
EDIT:
I forgot to show you my NodeJS Global Error Handler Middleware from this follow up tutorial to replace the face-backend:
module.exports = errorHandler;
function errorHandler(err, req, res, next) {
if (typeof (err) === 'string') {
// custom application error
return res.status(400).json({ message: err });
}
if (err.name === 'ValidationError') {
// mongoose validation error
return res.status(400).json({ message: err.message });
}
if (err.name === 'UnauthorizedError') {
// jwt authentication error
return res.status(401).json({ message: 'Invalid Token' });
}
// default to 500 server error
return res.status(500).json({ message: err.message });
}
you need to have an entries in your reducer for LOGIN_SUCCESS and LOGIN_FAILURE that will set the state in the redux store to something that you can use back in your component.
function reducer(state = { status: ''}, action) {
switch (action.type) {
case 'LOGIN_SUCCESS':
return { status: 'LOGGED_IN'}
... ETC
default:
return state
}
}
Then via mapStateToProps you will map the state of the redux store to props of the component and be able to use it like -
const mapStateToProps = (state) => ({
status: state.status
})
this.props.status // LOGGED_IN

React - Redux: Testing actions dispatched inside other actions

I'm using redux for a react app, with a thunk middleware.
I have this method:
export function login(username, password) {
return function (dispatch, getState) {
dispatch(loginRequest());
return sessionService.login(dispatch, username, password).then(redirectTo => {
dispatch(handleSuccessfulAuthentication(redirectTo));
}).catch(error => {...}
};
}
So I'm nocking the result of the service, and get the actions ok. But I want to test I'm dispatching the method handleSuccessfulAuthentication but spying on that method does not seems to work, it always return it was not called (besides I see the action returned)
This is basically my test:
describe('WHEN logging in from sessionExpired', () => {
let action;
beforeAll(() => {
...
testHelper.getUnAuthenticatedNock()
.post(`/${endpoints.LOGIN}`, credentials)
.reply(200, {
...loginResponse
});
action = sinon.spy(sessionActions, 'handleSuccessfulAuthentication');
return store.dispatch(sessionActions.login(credentials.username, credentials.password))
}
it('Should redirect to previous visited page', () => {
console.log(action.called); // false
});
You need to call the exported function (please note we're calling handleSuccessfulAuthentication bound to exports):
export function login(username, password) {
return function (dispatch, getState) {
dispatch(loginRequest());
return sessionService.login(dispatch, username, password).then(redirectTo => {
dispatch(exports.handleSuccessfulAuthentication(redirectTo));
}).catch(error => {...}
};
}
And then your test:
it('Should redirect to previous visited page', () => {
console.log(action.called); // false
});
It's a weird issue where you have to call the function bound to exports. More information found here: https://github.com/facebook/jest/issues/936#issuecomment-214939935

Redux dispatch triggering, but store not updated

I've got a bunch of actions that all work except for one. The setFriend function runs but for some reason it never makes a call to reducer and therefore the store does not update.
function mapDispatchToProps(dispatch){
return {
actions: {
authenticate: (userId, password, handler) => {
dispatch(AuthActions.authenticate(userId, password, handler));
},
register: (userId, password, userName, handler) => {
dispatch(AuthActions.register(userId, password, userName, handler));
},
updateGotUserSuccess: (user, isSuccess) => {
dispatch(AuthActions.updateGotUserSuccess(user, isSuccess));
},
setFriend: (friend) => {
debugger;
dispatch(FriendsActions.setFriend(friend));
}
}
};
}
All the functions listed above work, except setFriend which is the problem function. But when I debug, as shown below, it hits my first debugger but not the second one after the call to dispatch.
static setFriend(friend){
return (dispatch) => {
dispatch({type: FriendsActions.GOT_FRIEND_SUCCESS, payload: friend});
};
}
This is my reducer
export default function getFriendReducer(state = {}, action){
switch(action.type){
case FriendsActions.GOT_FRIEND_SUCCESS:
debugger;
return ObjUtils.cloneObj(action.payload);
case FriendsActions.GOT_FRIEND_FAIL:
debugger;
return ObjUtils.cloneObj(action.payload);
default:
return state;
}
}

Resources