React, Firebase - signInWithEmailAndPassword - cannot use the user variable outside .then() block - reactjs

I am trying to set up a react-app which uses firebase authentication only with email and password.
When you have a look at googles documentation for signing in with email and password, you find the following code:
firebase.auth().createUserWithEmailAndPassword(email, password)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
// ..
});
In my application, I get auth and the submitted email / password via action.formState.values.email / action.formState.values.password.
initialState is the default user object, which I am then trying to modify and return for the function sessionReducer.
I have implemented it the following way:
import * as actionTypes from 'actions';
import { auth } from '../firebase';
const initialState = {
loggedIn: false,
user: {
first_name: 'First Name',
last_name: 'Second Name',
email: 'email#email.com',
avatar: '/images/avatars/avatar_11.png',
bio: 'Titel/Bio',
role: 'ADMIN'
}
};
const sessionReducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SESSION_LOGIN: {
auth.signInWithEmailAndPassword(action.formState.values.email, action.formState.values.password)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
return user;
})
.catch((error) => {
// Print error message
});
// ------ Cannot access user object from firebase here ------ //
return {
loggedIn: true,
user: {
...initialState.user,
email: user.email // <- Here I need the Email out of the user object from firebase
}
};
}
}
};
export default sessionReducer;
When I print the user object from firebase directly in the .then() block, I get everything I need but as soon as I want to use this user variable outside, after the the then block block, I don't have access to it.
I think the problem is, that the return statement runs too early... The firebase-call has not yet finished but the return statement already tries to access the user variable from firebase.
If you need any more information, just ask as I am not sure how much I have to provide...
Thanks for your help!

As Doug mentioned in the comment you can't use async code in a pure redux. To make your code work try to use something like redux-thunk or redux-saga. This example should explain a little bit how it works:
function makeASandwich(forPerson, secretSauce) {
return {
type: 'MAKE_SANDWICH',
forPerson,
secretSauce,
};
}
function apologize(fromPerson, toPerson, error) {
return {
type: 'APOLOGIZE',
fromPerson,
toPerson,
error,
};
}
function withdrawMoney(amount) {
return {
type: 'WITHDRAW',
amount,
};
}
// Even without middleware, you can dispatch an action:
store.dispatch(withdrawMoney(100));
// But what do you do when you need to start an asynchronous action,
// such as an API call, or a router transition?
// Meet thunks.
// A thunk in this context is a function that can be dispatched to perform async
// activity and can dispatch actions and read state.
// This is an action creator that returns a thunk:
function makeASandwichWithSecretSauce(forPerson) {
// We can invert control here by returning a function - the "thunk".
// When this function is passed to `dispatch`, the thunk middleware will intercept it,
// and call it with `dispatch` and `getState` as arguments.
// This gives the thunk function the ability to run some logic, and still interact with the store.
return function(dispatch) {
return fetchSecretSauce().then(
(sauce) => dispatch(makeASandwich(forPerson, sauce)),
(error) => dispatch(apologize('The Sandwich Shop', forPerson, error)),
);
};
}
What we are doing here is just enabling redux to handle async code.

Related

Redux Saga authentication with router 6

I am porting an application from Router v2 to Router v6.
I am attempting to get authentication to work.
This bit of code gets hit when the user hits login
sendLoginRequest(event) {
// Prevent the page from unloading from the form submit
event.preventDefault();
// Don't do anything if the form is not valid
if (!this.isFormValid()) {
return;
}
// Make the request to the server to login
this.props.login({
emailAddress: this.state.emailAddress,
password: this.state.password,
onLoginSuccess: () => {
ReactGA.event({
category: 'Login',
action: 'success',
label: 'From Login Page'
});
// Login was successful, send the user to the main page
this.props.navigate('/');
},
onLoginFailure: (error) => {
ReactGA.event({
category: 'Login',
action: 'failure',
label: 'From Login Page'
});
// Set an error message depending on the type of error
this.setErrorMessage(error);
}
});
}
mapDisptachToProps
static mapDispatchToProps(dispatch) {
return {
login: (options) => dispatch({type: LOGIN, ...options}),
getSettings: () => dispatch({type: GET_SETTINGS})
};
}
It gets to this.props.login, and goes through the reducers, as I would expect.
However, none of the reducers I have match the above layout with the exception of the below, which exists in the sagas\authenticator.js
function* login() {
while (true) {
const {emailAddress, password, onLoginSuccess, onLoginFailure} = yield take(actions.LOGIN);
try {
// Make the request to login only if the user is not currently logged in
const isLoggedIn = yield select(_isLoggedIn);
if (!isLoggedIn) {
yield call(actions.login, emailAddress, password);
yield put({type: actions.GET_PROFILE});
// Call the success callback if one is provided
if (onLoginSuccess) {
onLoginSuccess();
}
}
} catch (error) {
// Call the failure callback if one is provided
if (onLoginFailure) {
onLoginFailure(error);
}
}
}
}
This contains some sort of reference to the above code blocks calls, this also however never gets hit.
What am I missing here? I get no errors of any kind, just a lack of functionality.
What I needed to do in this scenario was ensure my fork strategy for Redux was changed from
export default function* root() {
yield [
fork(login),
fork(logout),
fork(isLoggedIn),
fork(getProfile),
fork(forgotPassword),
fork(resetPassword)
];
}
to
export default function* root() {
yield fork(login);
yield fork(logout);
yield fork(isLoggedIn);
yield fork(getProfile);
yield fork(forgotPassword);
yield fork(resetPassword);
}
and now the proper bit of code is being called and the API is being hit.
However I am unsure if this has other implications I'm perhaps unaware of, and will update this post when I learn more.

How to mock out async actions

I'm trying to test this function:
function login(username, password) {
let user = { userName: username, password: password };
return dispatch => {
localStorageService.login(username, password).then((response) => {
dispatch(resetError());
dispatch(success( { type: userConstants.LOGIN, user} ));
}, (err) => {
dispatch(error(err));
});
};
function success(user) { return { type: userConstants.LOGIN, payload: user } };
};
Here is my test
const mockStore = configureStore([thunk]);
const initialState = {
userReducer: {
loggedInUser: "",
users: [],
error: ""
}
};
const store = mockStore(initialState);
jest.mock('./../../services/localStorageService');
describe("Login action should call localstorage login", () => {
let localStorage_spy = jest.spyOn(localStorageService, 'login');
store.dispatch(userActions.login(test_data.username, test_data.password)()).then( () => {
expect(localStorage_spy).toHaveBeenCalled();
});
});
The error I get:
Actions must be plain objects. Use custom middleware for async actions.
A lot of resources online keep telling me to use thunk in my test for these actions but it's not working. The last thing it calls is dispatch(resetError()); and it breaks. I've never really found a resource online which is similar enough to my problem. My function returns a dispatch which returns a promise which returns another dispatch when the promise resolves. I'm just trying to get the function to return. I've put a spy on localStorageService.login and also mocked it out and I have an expect to make sure it was called. But of course the function is not returning

set user details in state after successful api call - Redux saga

I have a "My Profile" form that displays the details of the user.
Api call to fetch user data is as follows.
componentDidMount() {
this.props.getUserDetails();
}
Saga file is as follows
function* fetchUserDetails() {
try {
const response = yield call(userDetailsApi);
const user = response.data.user;
// dispatch a success action to the store
yield put({ type: types.USER_DETAILS_SUCCESS, user});
} catch (error) {
// dispatch a failure action to the store with the error
yield put({ type: types.USER_DETAILS_FAILURE, error });
}
}
export function* watchUserFetchRequest() {
yield takeLatest(types.USER_DETAILS_REQUEST, fetchUserDetails);
}
Reducer is as follows
export default function reducer(state = {}, action = {}) {
switch (action.type) {
case types.USER_DETAILS_SUCCESS:
return {
...state,
user: action.user,
loading: false
};
default:
return state;
}
}
Now i need to set the user details in state so that when the form values are changed, i can call the handleChange function to update the state.
If i had used redux thunk, i could have used something like as follows
componentDidMount() {
this.props.getUserDetails().then(() => {
this.setState({ user });
});
}
so that the user state contains all details of user and if a user property changes then the state can be updated using handleChange method .
That is,
After the api call, i need is something like
state = {
email: user#company.com,
name: 'Ken'
}
How to achieve the same using redux saga?

Integrating API middleware with Redux-Thunk

This is stemming off of this SO Question
I am trying to integrate redux-thunk with my API middleware. The current flow of logic is like this:
action dispatched from component this.props.onVerifyLogin();
=>
action goes to action creator which creates an API call to the middleware like so:
// imports
const verifyLoginAC = createAction(API, apiPayloadCreator);
export const verifyLogin = () => dispatch => {
return verifyLoginAC({
url: "/verify/",
method: "POST",
data: {
token: `${
localStorage.getItem("token")
? localStorage.getItem("token")
: "not_valid_token"
}`
},
onSuccess: result => verifiedLogin(dispatch, result),
onFailure: result => dispatch(failedLogin(result))
});
};
const verifiedLogin = (dispatch, data) => {
console.log("verifiedLogin");
const user = {
...data.user
};
dispatch(setUser(user));
dispatch({
type: IS_LOGGED_IN,
payload: true
});
};
// failedLogin function
const setUser = createAction(SET_USER);
apiPayloadCreator in utils/appUtils:
const noOp = () => ({ type: "NO_OP" });
export const apiPayloadCreator = ({
url = "/",
method = "GET",
onSuccess = noOp,
onFailure = noOp,
label = "",
isAuthenticated = false,
data = null
}) => {
return {
url,
method,
onSuccess,
onFailure,
isAuthenticated,
data,
label
};
};
and then the middleware intercepts and performs the actual API call:
// imports
// axios default config
const api = ({ dispatch }) => next => action => {
next(action);
console.log("IN API");
console.log("Action: ", action);
// this is where I suspect it is failing. It expects an action object
// but is receiving a function (see below for console.log output)
if (action.type !== API) return;
// handle Axios, fire onSuccess/onFailure, etc
The action is created but is a function instead of an action creator (I understand this is intended for redux-thunk). But when my API goes to check action.type it is not API so it returns, never actually doing anything including call the onSuccess function. I have tried to also add redux-thunk before api in the applyMiddleware but then none of my API actions fire. Can someone assist?
Edit:
This is the received data to the API middleware:
ƒ (dispatch) {
return verifyLoginAC({
url: "/verify/",
method: "POST",
data: {
token: "" + (localStorage.getItem("token") ? localStorage.getItem("token") : "not_valid_toke…
Status Update:
Still unable to get it work properly. It seems like redux-saga has a pretty good following also, should I try that instead?
My API was interferring. I switched to redux-saga and got everything working like so:
/**
* Redux-saga generator that watches for an action of type
* VERIFY_LOGIN, and then runs the verifyLogin generator
*/
export function* watchVerifyLogin() {
yield takeEvery(VERIFY_LOGIN, verifyLogin);
}
/**
* Redux-saga generator that is called by watchVerifyLogin and queries the
* api to verify that the current token in localStorage is still valid.
* IF SO: SET loggedIn = true, and user = response.data.user
* IF NOT: SET loggedIn = false, and user = {} (blank object}
*/
export function* verifyLogin() {
try {
apiStart(VERIFY_LOGIN);
const token = yield select(selectToken);
const response = yield call(axios.post, "/verify/", {
// use redux-saga's select method to select the token from the state
token: token
});
yield put(setUser(response.data.user));
yield put(setLoggedIn(true));
apiEnd(VERIFY_LOGIN);
} catch (error) {
apiEnd(VERIFY_LOGIN);
yield put(setLoggedIn(false));
yield put(setUser({})); // SET USER TO BLANK OBJECT
}
}

Redux logging in

I have been looking for an elegant (and correct) way to handle logging in for a Redux application. Currently the the user is directed to a login component/container where he see's a form with username and password. Then when he clicks on submit I call an async login function and when the promise is fulfilled then I dispatch a loginSuccess and also a replacePath call. The following is some pseudo code:
submit(e) {
login(username, password)
.then(function (user) {
dispatch(loginSuccess(user));
dispatch(replacePath('/');
});
}
This works but I'm not sure it's best practice. Anyone have any better implementations?
Its generally considered bad practice to call dispatch within a component unless its a top-level container connected to the store.
I'd recommend following the examples that Dan Abramov gives in the docs, most notably the async Reddit post fetching example. Take a look at how he handles the interim of the request with posts.isFetching.
Since I know StackOverflow doesn't like links, here's a simplified example (in ES6):
These are the actions:
// Actions
import fetch from 'isomorphic-fetch';
import * as types from '../constants/actionTypes.js';
var requestAuth = function() {
return {
type: type.REQUEST_AUTH
}
};
var authSuccess = function(response) {
return {
type: type.AUTH_SUCCESS,
response: response
}
};
var authFail = function(response) {
return {
type: type.AUTH_FAIL,
response: response
}
};
var authenticate = function(username, password) {
var fetchOptions = {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({username: username, password: password})
};
var uri = '/api/path/to/your/login/backend';
return dispatch => {
dispatch(requestAuth);
return fetch(uri, fetchOptions)
.then(response => {
if (resopnse.status === 200) {
dispatch(authSuccess(response));
// Do any other login success work here
// like redirecting the user
} else {
dispatch(authFail(response));
}
}
}
};
Next the reducer:
// Reducer
import { combineReducers } from 'redux';
import { REQUEST_AUTH, AUTH_SUCCESS, AUTH_FAIL } from '../actions/login';
function login(state = {
isAuthenticating: false,
isLoggedIn: false,
authenticationToken: '',
authError: null
....., // whatever other state vars you need
.....
}, action) {
switch(action.type) {
case REQUEST_AUTH:
return Object.assign({}, state, {
isAuthenticating: true
});
break;
case AUTH_SUCCESS:
return Object.assign({}, state, {
isAuthenticating: false,
isLoggedIn: true,
authenticationToken: action.response.token
});
break;
case AUTH_FAIL:
return Object.assign({}, state, {
isAuthenticating: false,
authError: action.response.error
});
break;
default:
return state;
}
}
And finally the component method
// Component Method
// authenticate() should be wrapped in bindActionCreators()
// and passed down as a prop
function handleSubmit(username, password) {
if (isValid(username) && isValid(password) {
authenticate(username, password);
}
}
tl;dr Your user types in their credentials which should be part of state (not pictured here). An onClick in the component calls handleSubmit(), which dispatches authenticate(). Authenticate dispatches requestAuth() which updates state to show your user that the request is being processed (a loading spinner displays or something). Once your AJAX call to the backend returns with the authentication results, you dispatch either authSuccess() or authFail() to update state and inform the user whether their request succeeded or not.

Resources