Redux logging in - reactjs

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.

Related

How to get data from an API using redux?

I am requesting an API using redux and saga
I the request is working fine I am able to get API returned data in the reducer, but I am not able to get that data in the home.js I am new to redux please help me.
in my home.js this is how I am making a call
const fetchSessionData = () => {
dispatch(
fetchSession({
token: token
})
)
}
action.js
export const fetchSession = data => {
return {
type: Action.SESSION_DATA,
payload: {data},
};
};
this is reducer file
export const SessionData = (state = sessionState, action) => {
console.log('inside session reducer', JSON.stringify(action));
switch (action.type) {
case Action.SESSION_FETCH_SUCCESS:
return {
...state,
isLoading: false,
};
case Action.SESSION_FETCH_ERROR:
return {
...state,
sagaerror: action.error,
isLoading: false,
};
case Action.SESSION_DATA:
return {
...state,
isLoading: true,
};
default:
return state;
}
};
and this is the api
export function fetchSessionData(payload) {
return fetch(`${url}/session/`, {
method: 'GET',
headers: {
Authorization: `Bearer ${payload.token}`,
Accept: 'application/json',
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(res => {
return res;
})
.catch(error => {
throw error;
});
}
how can I get the returning data from api in home.js?
Looks like you are not storing back the response from saga.
Please have an action for storing the response into your reducer.you may use
yield put(storeactionname(payload received from api reponse);
in your saga method and in reducer your action.payload should be stored into a state variable
and in your component you can use useSelector like below
const { yourvariableNameInStateInReducer } =
useSelector((state) => state.yourreducername);
As you say you will able to get data to reducer. It's then fine in that section. In the component you need to select stored data from the store. If you are using functional component you can use useSelector hook in react-redux. If you are using class component then you need to use connect function, you need to pass mapStateToProps and mapDispatchToProps arguments to the connect function. Refer https://react-redux.js.org/api/connect

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

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.

How to Create Middleware for refresh token in Reactjs with axios and redux

i am working with reactjs on front end the issue is after certain time period the accessToken is expired and server send status of 401(unauthorized) then i need to send refresh token back to server it works fine until i manually send the refresh token i set the setInterval function but thats not a good approach how to automatically send it when token is expired.
i also google it but everyone is talking about creating middleware anyone please give me the hint how to create that middleware or any other solution or link any article related to it . i created this but this didnt works for me however when server send status of 401 then middleware ran but it dosent dispatch my refreshToken() function
const customMiddleWare = store => next => action => {
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
if(error.status === 401) {
// do something when unauthorized
store.dispatch(refreshToken());
}
return Promise.reject(error);
});
console.log("Middleware triggered:", action);
next(action);
}
By the way i am using redux, redux-thunk and axios. thanks,
some time ago i used to use the next way:
First of all i created some api folder, where each function returns data for axios requests
// /api.js
export function signIn (data) {
return {
method: 'post',
api: '/sign-in'
data: data
}
}
export function signUp (data) {
return {
method: 'post',
api: '/registration'
data: data
}
}
then i generated action type by specific rule, like: SIN_IN_REQUEST, where: SIGN_IN means signIn function in /api.js; REQUEST means that you need to do api request. As result my middleware looked like the next:
// request middleware
const instance = axios.create({
baseURL: '/api'
});
function camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(word, index) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '');
}
const customMiddleWare = store => next => action => {
if (!action.type.endsWith('_REQUEST')) {
next();
return;
}
const methodName = action.type.replace('_REQUEST', ''); // removed _REQUEST from action type
const camelCaseMethodName = camelize(methodName); // the result is "signIn"
const method = api[camelCaseMethodName];
if (!method) {
next();
return;
}
const dataForRequest = method(action.payload);
try {
const response = await instance(dataForRequest);
const newActionType = action.type.replace('_REQUEST', '_SUCCESS');
dispatch({
type: newActionType,
payload: {
requestPayload: action.payload,
response: response,
}
})
} catch(error) {
if (error.status === '401') {
dispatch(refreshToken());
next();
return;
}
const newActionType = action.type.replace('_REQUEST', '_FAILURE');
dispatch({
type: newActionType,
payload: {
requestPayload: action.payload,
error: error,
}
})
}
next();
}
After that you can easily manage any api request in your application like that:
function someTHunkMethod(username, password) {
return (dispatch, getState) => {
dispatch({
type: 'SIGN_IN_REQUEST',
payload: {
username,
password
}
})
}
}
function oneMoreThunk(data) {
return (dispatch, getState) => {
dispatch({
type: 'GET_USERS_REQUEST',
payload: data
})
}
}
And in reducer do something like that
...
switch (action.type) {
case 'SIGN_REQUEST':
return {
isLoading: true,
user: null
}
case 'SIGN_SUCCESS':
return {
isLoading: false,
user: action.payload.response.data
}
case 'SIGN_FAILURE':
return {
isLoading: false,
user: null
}
default:
return state
}

Should component re-render after form submit and action dispatches?

I have a component, which has a form, when a user clicks the submit button and after the validation is successful, an action dispatches (this all happens within the submit event).
Should the component re-render after the action dispatches?
Currently, I don't see this happening, what could be the reason?
Let me add, what's happening here.
1) form is submitted;
2) the validation is run;
3) the action dispatches (it sends the data to backend to save it into the database);
In the reducer, I have defined:
const messages = (state = INITIAL_STATE, action) => {
switch (action.type) {
case ADD_MESSAGE_SUCCESS:
return { ...state, message: action.message, error: null, isMessageLoading: false };
case ADD_MESSAGE_FAILURE:
return { ...state, message: null, error: action.error, isMessageLoading: false };
default:
return state;
}
};
And in the console log, I can clearly see that the state has changed.
But no re-render occurs at this stage.
const mapStateToProps = (state) => {
return {
message: state.messages.message,
isMessageLoading: state.messages.isMessageLoading
}
};
So when the action dispatches, the props gets updated.
This is the code of an action
export const addMessage = (message) => {
return (dispatch) => {
dispatch({ type: ADD_MESSAGE });
return fetch(`http://localhost:8080/api/contacts`, {
credentials: 'include',
method: 'POST',
body: JSON.stringify(message),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}).then(response => {
return response.json();
}).then((data) => {
dispatch(addMessageSuccess(data));
}).catch(error => {
console.log(`An error occurred while adding contact message: ${error}`);
dispatch(addMessageFailure(error));
});
};
};
Connected components will re-render if the state changes (and the changed state is being selected in mapStateToProps)
In your case, both message or isMessageLoading can trigger the update.
Note that if the message is a string and you dispatch the actions that contain the same message string, your component will not re-render. For example, the current message state is success, and you dispatch another action which also contains message: success:
{
type: ADD_MESSAGE_SUCCESS,
message: 'success'
}
Then your component won't update. That's because there are some optimization in React-Redux's connect
A simple example here: https://jsfiddle.net/njk5teoh/ (I also add error to the component), you can click the submit button several times and see the console log.
If this doesn't solve your problem, you may need to provide a reproducible jsfiddle/codepen or repo.

Thunks in React-Redux - Not Resolving?

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

Resources