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?
Related
I am using React Native and Redux. In initial state of Redux emailExists is set to null:
const INITIAL_STATE = {
// ...
emailExists: null,
};
when registering user. First, I check if user already exists by sending a request to server and if user already exists, I show a toast.
const registerUser = (values, actions) => {
checkEmail(values.userEmail); // takes time to get result of `emailExists`
if (emailExists) { // `emailExists` is now `null` couldn't wait for response
toastRef.current.show("Email already exists!");
return;
}
}
checkEmail code look like this:
function checkEmail(data) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
dispatch(emailExists(res.data.exists));
dispatch(authError("Email already exists"));
})
.catch((err) => {
console.log(`err`, err);
});
};
}
aftering dispatching dispatch(emailExists(res.data.exists));, the emailExists will be either true or false, but the problem is that since request takes time to get data from server, at first load of application emailExists is always set to null. Which means below condition will always be false in first load:
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
function emailExists(payload){
return {
type: userConstants.EMAIL_EXISTS,
emailExists: payload
}
}
How do I resolve this issue?
THANK YOU
You can modify functions like this to get the expected result.
function checkEmail(data,callback) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
// dispatch(emailExists(res.data.exists)); // You don't need this redux approach now because it will take time and will give you the same error
dispatch(authError("Email already exists"));
callback && callback(res.data.exists) // I am assuming here you got the response that email alreay exists and its value is true.
})
.catch((err) => {
console.log(`err`, err);
});
};
}
Now get the callback when the API request is done and you get the response.
const registerUser = (values, actions) => {
const callback = (isEmailExists) => {
//Here you will get the value of the checkemail API (true/false)
//Now do any action in this block of functions
if (isEmailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
checkEmail(values.userEmail,callback);
}
Mantu's answer is one solution. I would prefer using async/await for this use case. dispatch() returns whatever the function returned by the action creator returns. So in this case you are returning a promise, meaning you can await your dispatch(checkEmail(values.userEmail)) call.
You will need to return whether the email exists from the promise, otherwise even if you weait for your checkEmail action to complete, the emailExists will not be up to date when you access it (since using useSelector will have to rerender the component to reflect the updates in the store).
const registerUser = async (values, actions) => {
const emailExists = await checkEmail(values.userEmail);
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
function checkEmail(data) {
return (dispatch) => {
return api_request
.post("register/check/email", { email: data })
.then((res) => {
dispatch(emailExists(res.data.exists));
dispatch(authError("Email already exists"));
return res.data.exists;
})
.catch((err) => {
console.log(`err`, err);
});
};
}
If you don't want to return the property from the promise, you will need to get the value from the store synchronously via store.getState():
const registerUser = async (values, actions) => {
await checkEmail(values.userEmail);
const emailExists = selectEmailExists(store.getState());
if (emailExists) {
toastRef.current.show("Email already exists!");
return;
}
}
So I am making a connection to a MQTT broker via Redux. I have three actions, one making the connection, another one checking for error and one receiving the message.
Only the first one gets triggered and the other 2 do not trigger. The connection is successful.
Here is my code:
Actions
export const mqttConnectionInit = (topic) => {
return {
type: 'INIT_CONNECTION',
topic:topic
}
}
export const mqttConnectionState = (err = null) => {
return {
type: 'MQTT_CONNECTED',
payload: err
}
}
export const processMessage = (data) => dispatch => {
console.log('Receiving Message')
return {
type: 'MESSAGE_RECEIVED',
payload: data
}
}
Reducer
import { mqttConnectionState} from './mqttActions'
import { processMessage} from './mqttActions'
const initState = {
client: null,
err: null,
message : 'message'
}
const createClient = (topic) => {
const mqtt = require('mqtt')
const client = mqtt.connect('ws://localhost:9001');
client.on('connect', function () {
mqttConnectionState('MQTT_CONNECTED')
client.subscribe(topic, (err, granted) => {
if (err) alert(err)
console.log(`Subscribed to: ` + topic)
console.log(granted)
});
});
//messages recevied during subscribe mode will be output here
client.on('message', function (topic, message) {
// message is Buffer
console.log(message.toString())
processMessage({topic, message})
// client.end() will stop the constant flow of values
})
return client;
}
const mqttReducer = (state = initState, action) =>{
switch (action.type) {
case 'INIT_CONNECTION':
return {
...state,
client: createClient(action.topic)
}
case 'MQTT_CONNECTED':
return {
...state,
err: action.payload
}
case 'MESSAGE_RECEIVED':
return {
...state,
message: action.payload //payload:data
}
default:
return state
}
}
export default mqttReducer
Why mqttConnectionState and processMessage do not get triggered?
You can never call async logic from within a reducer! Your createClient method is entirely async logic, and so it cannot go in a reducer.
In addition, you should not put non-serializable values into the Redux store.
Instead, we recommend that persistent connections like sockets should go into middleware.
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
So I am trying to use redux together with the react-native-fbsdk package to login a user, but no mather how I go about it, the permissions always gets denied, even after granting them in the login screen. The console logs can be seen below:
Here you see my action for the authFailure:
export function authFailure(authError) {
return {
type: AUTH_FAILURE,
action.authError.message
}
}
Here is the function that gets executed onPress dispatching the action authStarted() and then calling the function _fbAuthAPI() which handles the fbsdk. This can be seen below this one.
export function _fbAuth() {
return (dispatch) => {
dispatch(authStarted())
const values = []
_fbAuthAPI().then((result) => {
values.concat(result.accessToken)
return _getUserInformationAPI(result.accessToken)
}).then((profile) => {
values.concat(profile)
dispatch(authSuccess(...values))
}).catch((error) => {
dispatch(authFailure(error))
setTimeout(() => {
dispatch(authFailureRemove())
}, 4000)
})
}
}
export function _fbAuthAPI() {
return new Promise((resolve, reject) => {
LoginManager.logInWithReadPermissions(['public_profile', 'email']).then((result) => {
if (result.isCancelled) {
throw new Error('Login was cancelled')
} else if (result.declinedPermissions) {
throw new Error('Permissions were declined')
} else {
return AccessToken.getCurrentAccessToken()
}
}).then((result) => {
resolve(result)
}).catch((error) => {
reject(error)
})
})
}
As for the reducer:
export default function authReducer(state = initialState, action) {
switch (action.type) {
case AUTH_STARTED:
return Object.assign({}, state, {
authenticating: true
})
break
case AUTH_SUCCESS:
return Object.assign({}, state, {
authenticating: false,
authError: false,
facebookToken: facebookToken,
facebookProfile: facebookProfile
})
break
case AUTH_FAILURE:
return Object.assign({}, state, {
authenticating: false,
authError: authError
})
break
...
default:
return state
}
}
Setup:
React Native 0.45.1
React Native FBSDK "^0.6.1"
Redux "^3.7.1"
MacOS Sierra 10.12.6
I can't help with the Facebook SDK and authentication, but the authError being undefined is because in this section, authError is truly undefined
case AUTH_FAILURE:
return Object.assign({}, state, {
authenticating: false,
authError: authError // Where is this coming from?
})
break
I think what you intended to have there is authError: action.authError
Okey, so I managed to solve the issue.
Basically, the when you call declinedPermissions on the result from the logInWithReadPermissions it always returns true, as it is an array.. Then, even if you dont have any declined permissions, it takes it as true.
The simple way to get around it, is just to see whats in the array and determine what to do based on that:
// Returns an empty array, therefore evaluates to true
if (result.declinedPermissions) {
throw new Error('Permissions were declined')
}
// The first index of the array is empty
if (result.declinedPermissions[0] === "") {
throw new Error('Permissions were declined')
}
I am using React and Redux to create a login system with Google Firebase. I am trying to understand how to use thunks. I am calling my action createUser from my React component however, I'm not able to handle the callback successfully.
Here is the component function I am calling the action from:
registerUser() {
let email = this.state.user.email;
let pw= this.state.user.password;
this.props.actions.createUser(email, pw)
.then((user) => {
debugger; // The async request is successful but execution doesn't pause here
})
.catch((error) => {
debugger; // Instead I receive an error here that says, "Uncaught (in promise) RangeError: Maximum call stack size exceeded"
});
}
Here are the actions:
export function createUserSuccess(user) {
debugger;
return { type: types.CREATE_USER_SUCCESS, payload: { registeredUser: user, registerMsg: 'Successful Registration!' }};
}
export function createUserError(error) {
return { type: types.CREATE_USER_ERROR, payload: { registeredUser: {}, registerMsg: error.message }};
}
export function createUser(email, pw) { // async thunk
debugger;
return (dispatch) => {
debugger;
return firebase.auth().createUserWithEmailAndPassword(email, pw)
.then((user) => {dispatch(createUserSuccess(user))}) // todo: figure out why this won't resolve
.catch(error => dispatch(createUserError(error)));
}
}
And my Reducer:
import * as types from '../actions/actionTypes';
import initialState from './initialState';
export default function registerReducer(state = initialState.registeredUser, action) {
debugger;
switch (action.type) {
case types.CREATE_USER_SUCCESS:
return [
...state, // es6 spread operator - explodes all values in array
Object.assign({}, action.payload)
];
case types.CREATE_USER_ERROR:
return [
...state,
Object.assign({}, action.payload)
];
default:
return state;
}
}
I know the actual request to Google firebase is OK because the createUserSuccess action creator gets fired. Why isn't execution stopping at the appropriate place in my React Component?
You can check here this implementation
The Service when we read the user auth and set the value to Redux
https://github.com/x-team/unleash/blob/develop/app/services/authService.js
The reducer when set the user state to the redux state object
https://github.com/x-team/unleash/blob/develop/app/reducers/userReducer.js
The action creators
https://github.com/x-team/unleash/blob/develop/app/actions/UserActions.js
The most important part is the authService, let me know any question