I am working with React and Jwt Tokens and so far a user can stay logged in after going to a login page. I have a problem when I reload the page that the user won't stay logged in. Currently when a user logs in, I store the jwt Token in localStorage. For some reason, I don't understand why I'm not returning a valid user even when I have the jwt Token in localStorage. Any help is appreciated!
import React, { useReducer, createContext } from 'react'
import jwtDecode from "jwt-decode";
const initialState = {
user: null
}
if(localStorage.getItem('jwtToken')) {
const decodedToken = jwtDecode(localStorage.getItem('jwtToken'))
if(decodedToken.exp * 1000 < Date.now()) {
localStorage.removeItem('jwtToken')
}
else {
initialState.user = null;
}
}
const AuthContext = createContext({
user:null,
login: (userData) => {},
logout: () => {}
})
function authReducer(state, action) {
switch(action.type) {
case 'LOGIN':
return {
...state,
user: action.payload
}
case 'LOGOUT':
return {
...state,
user:null
}
default: return state
}
}
function AuthProvider(props) {
const [state, dispatch] = useReducer(authReducer, initialState)
function login(userData) {
localStorage.setItem("jwtToken", userData.token)
dispatch({
type:'LOGIN',
payload:userData
})
}
function logout() {
localStorage.removeItem("jwtToken")
dispatch({type:'LOGOUT'})
}
return (<AuthContext.Provider
value={{ user: state.user, login, logout}}
{...props} /> )
}
export { AuthContext, AuthProvider }
^^^ My Authentication file
^^ My menu bar component renders dynamically based on if there is a user or not. It's not rendering a logged-in user after reloading so my authentication file must be doing something wrong.
Thanks! Any help is appreciated.
Related
I have a session context for my NextJS application where anyone accessing /app/ directory pages have to go through an authorization check prior to allowing the user to access the page.
While my logic works in redirecting users without proper authentication, it is a bit glitchy because when someone navigate to the URL, /app/profile/ the page briefly loads before being redirected by Router.
I am wondering what is the best way to have this check happen prior to router loading the unauthorized page and redirecting them to the /login/ page.
Here are the steps in the authorization check:
Check is the user object has a property, authorized
Query the server for a session token
if the object from the server request comes back with authorized = false, then redirect user to /login/
Here is the code:
import React, { createContext, useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import axios from 'axios'
export const SessionContext = createContext(null);
const AppSession = ({ children }) => {
const router = useRouter()
const routerPath = router.pathname;
const [user, setUser] = useState({ user_id: '', user_email: '', user_avatar: ''})
useEffect(()=> {
// Check for populated user state if pages are accessed with the path /app/
if (routerPath.includes("/app/")){
if (user){
if(user.authenticated === undefined){
// Check if user session exists
axios.get('/api/auth/session/')
.then(res => {
const data = res.data;
// Update user state depending on the data returned
setUser(data)
// If user session does not exist, redirect to /login/
if (data.authenticated === false){
router.push('/login/')
}
})
.catch(err => {
console.log(err)
});
}
}
}
}, [])
return (
<SessionContext.Provider value={{user, setUser}}>
{children}
</SessionContext.Provider>
)
}
export const getUserState = () => {
const { user } = useContext(SessionContext)
return user;
}
export const updateUserState = () => {
const { setUser } = useContext(SessionContext)
return (user) => {
setUser(user);
}
}
export default AppSession;
Since user.authenticated isn't defined in the initial user state you can conditionally render null or some loading indicator while user.authenticated is undefined. Once user.authenticated is defined the code should either redirect to "/login" or render the SessionContext.Provider component.
Example:
const AppSession = ({ children }) => {
const router = useRouter();
const routerPath = router.pathname;
const [user, setUser] = useState({ user_id: '', user_email: '', user_avatar: ''});
...
if (user.authenticated === undefined) {
return null; // or loading indicator/spinner/etc
}
return (
<SessionContext.Provider value={{ user, setUser }}>
{children}
</SessionContext.Provider>
);
};
Check out getServerSideProps, redirects in getServerSideProps and this article.
In your client-side, if you export the NextJS function definition named getServerSideProps from a page, NextJS pre-renders the page on each request using the data returned by getServerSideProps.
In other words, you can use getServerSideProps to retrieve and check the user while pre-rendering the page and then choose to redirect instead of render if your condition is not met.
Here is an example.
function Page({ data }) {
// Render data...
}
export async function getServerSideProps(context) {
const { req, res } = context;
try {
// get your user
if (user.authenticated === undefined) {
return {
redirect: {
permanent: false,
destination: `/`,
},
};
}
return {
props: {
// any static props you want to deliver to the component
},
};
} catch (e) {
console.error("uh oh");
return;
}
}
Good luck!
Consider the code below:
Ignore all the axios request:
Login.js
const login = async () => {
let newLogin = {
email,
password,
};
await axios
.post("http://localhost:5000/login", newLogin)
.then((response) => {
if (response.data === 404) {
setUserError("User not found. Please Sign Up");
} else if (response.data === 403) {
setPasswordError("Incorrect Password");
} else {
dispatch({type:'LOG_IN',payload:{LoggedIn:true , currentUser:"Curent user"}})
navigate("/");
}
})
.catch((err) => console.log(err));
};
This pages updates the GlobalContext by changing loggedIn to true and currentUser to "fa".
Profile.js
import React , {useContext} from 'react'
import '../App.css'
import {Context} from '../GlobalState/Store'
const Profile = () => {
const [state,dispatch] = useContext(Context);
console.log(state);
return (
<div className="profile-page">
<div className="personal">
<h2>First Name:</h2>
<h2>Last Name:</h2>
<h2>Resistered Email:</h2>
<h2>User Name:</h2>
</div>
<div className="info">
<h2>Father Name:</h2>
<h2>Gender:</h2>
<h2>CNIC:</h2>
<h2>Blood Group:</h2>
<h2>Contact:</h2>
</div>
<div className="uni-info">
<h2>Designation:</h2>
<h2>Department:</h2>
<h2>Batch:</h2>
<h2>Roll No:</h2>
<h2>Enrolement:</h2>
</div>
</div>
);
}
export default Profile
This gets the state and logs it.
Here are the Reducer.js and GlobalContext.js:
const Reducer = (state,action) =>{
switch(action.type){
case 'LOG_IN':
return {
userEmail: action.payload.currentUser,
loggedIn: action.payload.LoggedIn
}
case 'LOG_OUT':
return {
userEmail: action.payload.currentUser,
loggedIn: action.payload.LoggedIn
}
default:
return state;
}
}
export default Reducer;
import React,{useReducer , createContext} from 'react'
import Reducer from './Reducer';
const initialState ={
loggedIn: false,
currentUser:''
}
const Store = ({children}) => {
const [state , dispatch] = useReducer(Reducer,initialState)
return (
<Context.Provider value={[state , dispatch]} >
{children}
</Context.Provider>
)
}
export const Context = createContext(initialState)
export default Store
Everything works beautifully and once I log in, I'm redirected to homepage as I'm supposed to and the correct state is being logged.
But once I refresh on the home page everything is reverted back to the initial state.
I need to retain the state as it will be used to apply the 'IfLoggedIn' logic.
Thanks in advance.
Cookie time (local storage)
Your login request will give your site a cookie that should be stored. when loading the page before anything check if the cookie/session is valid and set the state accoridingly.
Your react state cannot be staved (directly) between hard refreshes
I'm trying to log user in with firebase authentication and I ran into this problem that when I use onAuthStateChanged method in action creator to get is user logged in before or not. When users is logged in before so I just call login success and navigate them to main screen, if not I just dispatch another action and show login screen, but I still see login screen for like half a second if login success action dispatched, I'm new to react native and redux, can anyone help me solve this issue, thank you guys so much!!
here is my index.js
import { checkLogin } from './actions';
class LoginScreen extends Component {
componentDidMount() {
this.props.checkLogin();
}
componentWillReceiveProps(nextProp) {
if (nextProp.loggedIn) {
this.props.navigation.navigate('main');
}
}
//... some code
render() {
if (this.props.isChecking) {
return <Spinner />
}
return this.renderContent();
}
}
const mapStateToProps = ({ auth }) => {
const { email, password, error, loading, loggedIn, isChecking } = auth;
return { email, password, error, loading, loggedIn, isChecking };
};
export default connect(mapStateToProps, {
emailChanged, passwordChanged, emailLogin, checkLogin
})(LoginScreen);
and here is my actions.js
import firebase from 'firebase';
export const checkLogin = () => {
return(dispatch) => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
dispatch({ type: EMAIL_LOGIN_SUCCESS, payload: user });
} else {
dispatch({ type: CHECK_LOGIN_FAILED });
}
})
}
}
// some code
and reducer.js
const INITIAL_STATE = {
// ...
isChecking: true,
}
export default (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
// ...
case CHECK_LOGIN_FAILED:
return { ...state, isChecking: false };
case EMAIL_LOGIN_SUCCESS:
return { ...state, user: action.payload, error: '', loading: false, email: '', password: '', loggedIn: true, isChecking: false };
default:
return state;
}
}
I think this happens because the previous state of isChecking is remembered by your reducer from the previous rendering of your component. I bet if you reset the content and settings of your simulator that you would not first see the login page. However, to fix this I would recommend dispatching an action before firebase.auth().onAuthStateChanged to tell your reducer that you are in an "isChecking" state.
I don't think it matters for the purpose of this question what my exact setup is, but I just noticed this in my React and React Native apps, and suddenly realized they are not actually checking any kind of validity of the JWT that is stored.
Here is the code:
const tokenOnLoad = localStorage.getItem('token')
if (tokenOnLoad) store.dispatch({ type: AUTH_USER })
It's probably not really an issue, because the token is attached to the headers and the server will ignore any request without a valid token, but is there a way I can upgrade this to be better (ie: more secure && less chance of loading UI that detonates due to malformed token or if someone hacked in their own 'token') ?
Here is the token getting attached to every request:
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) req.options.headers = {}
const token = localStorage.getItem('token')
req.options.headers.authorization = token || null
next()
}
}])
Should I add some logic to at least check the length of the token or decode it and check if it has a user id in it? Or, is that a waste of CPU and time when the server does it?
I'm just looking to see if there are any low-cost ways to further validate the token and harden the app.
I do also use a requireAuth() higher-order component that kicks users out if they are not logged in. I feel like there could be some bad UX if the app somehow did localStorage.setItem('token',
'lkjashkjdf').
Your solution is not optimal as you stated you don't really check the validity of the user's token.
Let me detail how you can handle it:
1. Check token at start time
Wait for the redux-persist to finish loading and injecting in the Provider component
Set the Login component as the parent of all the other components
Check if the token is still valid
3.1. Yes: Display the children
3.2. No: Display the login form
2. When the user is currently using the application
You should use the power of middlewares and check the token validity in every dispatch the user makes.
If the token is expired, dispatch an action to invalidate the token. Otherwise, continue as if nothing happened.
Take a look at the middleware token.js below.
I wrote a whole sample of code for your to use and adapt it if needed.
The solution I propose below is router agnostic.
You can use it if you use react-router but also with any other router.
App entry point: app.js
See that the Login component is on top of the routers
import React from 'react';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import createRoutes from './routes'; // Contains the routes
import { initStore, persistReduxStore } from './store';
import { appExample } from './container/reducers';
import Login from './views/login';
const store = initStore(appExample);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { rehydrated: false };
}
componentWillMount() {
persistReduxStore(store)(() => this.setState({ rehydrated: true }));
}
render() {
const history = syncHistoryWithStore(browserHistory, store);
return (
<Provider store={store}>
<Login>
{createRoutes(history)}
</Login>
</Provider>
);
}
}
store.js
The key to remember here is to use redux-persist and keep the login reducer in the local storage (or whatever storage).
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import localForage from 'localforage';
import { routerReducer } from 'react-router-redux';
import reducers from './container/reducers';
import middlewares from './middlewares';
const reducer = combineReducers({
...reducers,
routing: routerReducer,
});
export const initStore = (state) => {
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
{},
composeEnhancers(
applyMiddleware(...middlewares),
autoRehydrate(),
),
);
persistStore(store, {
storage: localForage,
whitelist: ['login'],
});
return store;
};
export const persistReduxStore = store => (callback) => {
return persistStore(store, {
storage: localForage,
whitelist: ['login'],
}, callback);
};
Middleware: token.js
This is a middleware to add in order to check wether the token is still valid.
If the token is no longer valid, a dispatch is trigger to invalidate it.
import jwtDecode from 'jwt-decode';
import isAfter from 'date-fns/is_after';
import * as actions from '../container/actions';
export default function checkToken({ dispatch, getState }) {
return next => (action) => {
const login = getState().login;
if (!login.isInvalidated) {
const exp = new Date(jwtDecode(login.token).exp * 1000);
if (isAfter(new Date(), exp)) {
setTimeout(() => dispatch(actions.invalidateToken()), 0);
}
}
return next(action);
};
}
Login Component
The most important thing here is the test of if (!login.isInvalidated).
If the login data is not invalidated, it means that the user is connected and the token is still valid. (Otherwise it would have been invalidated with the middleware token.js)
import React from 'react';
import { connect } from 'react-redux';
import * as actions from '../../container/actions';
const Login = (props) => {
const {
dispatch,
login,
children,
} = props;
if (!login.isInvalidated) {
return <div>children</div>;
}
return (
<form onSubmit={(event) => {
dispatch(actions.submitLogin(login.values));
event.preventDefault();
}}>
<input
value={login.values.email}
onChange={event => dispatch({ type: 'setLoginValues', values: { email: event.target.value } })}
/>
<input
value={login.values.password}
onChange={event => dispatch({ type: 'setLoginValues', values: { password: event.target.value } })}
/>
<button>Login</button>
</form>
);
};
const mapStateToProps = (reducers) => {
return {
login: reducers.login,
};
};
export default connect(mapStateToProps)(Login);
Login actions
export function submitLogin(values) {
return (dispatch, getState) => {
dispatch({ type: 'readLogin' });
return fetch({}) // !!! Call your API with the login & password !!!
.then((result) => {
dispatch(setToken(result));
setUserToken(result.token);
})
.catch(error => dispatch(addLoginError(error)));
};
}
export function setToken(result) {
return {
type: 'setToken',
...result,
};
}
export function addLoginError(error) {
return {
type: 'addLoginError',
error,
};
}
export function setLoginValues(values) {
return {
type: 'setLoginValues',
values,
};
}
export function setLoginErrors(errors) {
return {
type: 'setLoginErrors',
errors,
};
}
export function invalidateToken() {
return {
type: 'invalidateToken',
};
}
Login reducers
import { combineReducers } from 'redux';
import assign from 'lodash/assign';
import jwtDecode from 'jwt-decode';
export default combineReducers({
isInvalidated,
isFetching,
token,
tokenExpires,
userId,
values,
errors,
});
function isInvalidated(state = true, action) {
switch (action.type) {
case 'readLogin':
case 'invalidateToken':
return true;
case 'setToken':
return false;
default:
return state;
}
}
function isFetching(state = false, action) {
switch (action.type) {
case 'readLogin':
return true;
case 'setToken':
return false;
default:
return state;
}
}
export function values(state = {}, action) {
switch (action.type) {
case 'resetLoginValues':
case 'invalidateToken':
return {};
case 'setLoginValues':
return assign({}, state, action.values);
default:
return state;
}
}
export function token(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken':
return action.token;
default:
return state;
}
}
export function userId(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken': {
const { user_id } = jwtDecode(action.token);
return user_id;
}
default:
return state;
}
}
export function tokenExpires(state = null, action) {
switch (action.type) {
case 'invalidateToken':
return null;
case 'setToken':
return action.expire;
default:
return state;
}
}
export function errors(state = [], action) {
switch (action.type) {
case 'addLoginError':
return [
...state,
action.error,
];
case 'setToken':
return state.length > 0 ? [] : state;
default:
return state;
}
}
Feel free to ask me any question or if you need me to explain more on the philosophy.
I'm creating my first React-Redux App. I'm using yo generator-redux and following this repo and official documenation. I have rendered de SignIn Presentational Component and it works fine, show errors if inputs are blanks. The problem is at dispatching. I use Thunk Middleware but the repo doesn't.
I have used console.log() to explore how deeper is working my code and I found that the Component Actions are being called, the AJAX request (with axios) is working fine, but the .then() (I think) is not working but doesn't throw errors.
This is my code:
Action
actions/UsersActions.js
import axios from 'axios';
//sign in user
export const SIGNIN_USER = 'SIGNIN_USER';
export const SIGNIN_USER_SUCCESS = 'SIGNIN_USER_SUCCESS';
export const SIGNIN_USER_FAILURE = 'SIGNIN_USER_FAILURE';
//Get current user(me) from token in localStorage
export const ME_FROM_TOKEN = 'ME_FROM_TOKEN';
export const ME_FROM_TOKEN_SUCCESS = 'ME_FROM_TOKEN_SUCCESS';
export const ME_FROM_TOKEN_FAILURE = 'ME_FROM_TOKEN_FAILURE';
export const RESET_TOKEN = 'RESET_TOKEN';
//log out user
export const LOGOUT_USER = 'LOGOUT_USER';
axios.defaults.baseURL = location.href.indexOf('10.1.1.33') > 0 ? 'http://10.1.1.33:8080/api/v1' : 'http://10.1.1.33:8080/api/v1';
export function signInUser(formValues) {
const request = axios.post('/login', formValues);
console.log(request);
// It works fine and receives the resposen when is invoked from Container
return {
type: SIGNIN_USER,
payload: request
};
}
export function signInUserSuccess(user) {
return {
type: SIGNIN_USER_SUCCESS,
payload: user
}
}
export function signInUserFailure(error) {
return {
type: SIGNIN_USER_FAILURE,
payload: error
}
}
export function meFromToken(tokenFromStorage) {
//check if the token is still valid, if so, get me from the server
const request = axios.get('/me/from/token?token=${tokenFromStorage}');
return {
type: ME_FROM_TOKEN,
payload: request
};
}
export function meFromTokenSuccess(currentUser) {
return {
type: ME_FROM_TOKEN_SUCCESS,
payload: currentUser
};
}
export function meFromTokenFailure(error) {
return {
type: ME_FROM_TOKEN_FAILURE,
payload: error
};
}
export function resetToken() {//used for logout
return {
type: RESET_TOKEN
};
}
export function logOutUser() {
return {
type: LOGOUT_USER
};
}
Component
components/SignInForm.js
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
class SignInForm extends Component {
static contextTypes = {
router: PropTypes.object
};
componentWillUnmount() {
// Invoked immediately before a component is unmounted from the DOM.
// Perform any necessary cleanup in this method, such as invalidating timers or
// cleaning up any DOM elements that were created in componentDidMount.
// Important! If your component is navigating based on some global state(from say componentWillReceiveProps)
// always reset that global state back to null when you REMOUNT
this.props.resetMe();
}
componentWillReceiveProps(nextProps) {
// Invoked when a component is receiving new props. This method is not called for the initial render.
if(nextProps.user && nextProps.user.status === 'authenticated' && nextProps.user.user && !nextProps.user.error) {
this.context.router.push('/');
}
//error
//Throw error if it was not already thrown (check this.props.user.error to see if alert was already shown)
//If u dont check this.props.user.error, u may throw error multiple times due to redux-form's validation errors
if(nextProps.user && nextProps.user.status === 'signin' && !nextProps.user.user && nextProps.user.error && !this.props.user.error) {
alert(nextProps.user.error.message);
}
}
render() {
const { asyncValidating, fields: { email, password }, handleSubmit, submitting, user } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.props.signInUser.bind(this))}>
<div>
<label>Email</label>
<input type="text" placeholder="email#4geeks.com.ve" {...email} />
<div>{email.touched ? email.error : ''}</div>
<div>{ asyncValidating === 'email' ? 'validating...' : ''}</div>
</div>
<div>
<label>Password</label>
<input type="password" {...password} />
<div>{password.touched ? password.error : ''}</div>
<div>{ asyncValidating === 'password' ? 'validating...' : ''}</div>
</div>
<button type="submit" disabled={submitting}>Submit</button>
</form>
</div>
);
}
}
export default SignInForm;
Container
containers/SignInFormContainer.js
import { reduxForm } from 'redux-form';
import SignInForm from '../components/SignInForm';
import { signInUser, signInUserSuccess, signInUserFailure } from '../actions/UsersActions';
// Client side validation
function validate(values) {
var errors = {};
var hasErrors = false;
if(!values.email || values.email.trim() == '') {
errors.email = "Enter a registered email.";
hasErrors = true;
}
if(!values.password || values.password.trim() == '') {
errors.password = "Enter password.";
hasErrors = true;
}
return hasErrors && errors;
}
// For any field errors upon submission (i.e. not instant check)
const validateAndSignInUser = (values, dispatch) => {
return new Promise ((resolve, reject) => {
console.log('this is showed');
dispatch(signInUser(values))
.then((response) => {
console.log('this console.log is not showed');
let data = response.payload.data;
// if any one of these exist, then there is a field error
if(response.payload.status != 200) {
// let other components know of error by updating the redux` state
dispatch(signInUserFailure(response.payload));
reject(data); // this is for redux-form itself
} else {
// store JWT Token to browser session storage
// If you use localStorage instead of sessionStorage, then this w/ persisted across tabs and new windows.
// sessionStorage = persisted only in current tab
sessionStorage.setItem('dhfUserToken', response.payload.data.token);
// let other components know that we got user and things are fine by updating the redux` state
dispatch(signInUserSuccess(response.payload));
resolve(); // this is for redux-form itself
}
});
});
}
const mapDispatchToProps = (dispatch) => {
return {
signInUser: validateAndSignInUser
}
}
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
// connect: first argument is mapStateToProps, 2nd is mapDispatchToProps
// reduxForm: 1st is form config, 2nd is mapStateToProps, 3rd is mapDispatchToProps
export default reduxForm({
form: 'SignInForm',
fields: ['email', 'password'],
null,
null,
validate
}, mapStateToProps, mapDispatchToProps)(SignInForm);
Presentational/Page/View
presentational/SignIn.js
import React, { Component } from 'react';
import HeaderContainer from '../containers/HeaderContainer';
import SignInFormContainer from '../containers/SignInFormContainer';
class SignIn extends Component {
render() {
return (
<div>
<HeaderContainer />
<SignInFormContainer />
</div>
);
}
}
export default SignIn;
Reducers
reducres/UserReducer.js
import {
ME_FROM_TOKEN, ME_FROM_TOKEN_SUCCESS, ME_FROM_TOKEN_FAILURE, RESET_TOKEN,
SIGNIN_USER, SIGNIN_USER_SUCCESS, SIGNIN_USER_FAILURE,
LOGOUT_USER
} from '../actions/UsersActions';
const INITIAL_STATE = {user: null, status:null, error:null, loading: false};
export default function(state = INITIAL_STATE, action) {
let error;
switch(action.type) {
case ME_FROM_TOKEN:// loading currentUser("me") from jwttoken in local/session storage storage,
return { ...state, user: null, status:'storage', error:null, loading: true};
case ME_FROM_TOKEN_SUCCESS://return user, status = authenticated and make loading = false
return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
case ME_FROM_TOKEN_FAILURE:// return error and make loading = false
error = action.payload.data || {message: action.payload.message};//2nd one is network or server down errors
return { ...state, user: null, status:'storage', error:error, loading: false};
case RESET_TOKEN:// remove token from storage make loading = false
return { ...state, user: null, status:'storage', error:null, loading: false};
case SIGNIN_USER:// sign in user, set loading = true and status = signin
return { ...state, user: null, status:'signin', error:null, loading: true};
case SIGNIN_USER_SUCCESS://return authenticated user, make loading = false and status = authenticated
return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
case SIGNIN_USER_FAILURE:// return error and make loading = false
error = action.payload.data || {message: action.payload.message};//2nd one is network or server down errors
return { ...state, user: null, status:'signin', error:error, loading: false};
case LOGOUT_USER:
return {...state, user:null, status:'logout', error:null, loading: false};
default:
return state;
}
}
reducers/index.js
import { combineReducers } from 'redux';
import { UserReducer } from './UserReducer';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({
user: UserReducer,
form: formReducer // <-- redux-form
});
export default rootReducer;
Store
import {createStore, applyMiddleware, combineReducers, compose} from 'redux';
import thunkMiddleware from 'redux-thunk';
import {devTools, persistState} from 'redux-devtools';
import rootReducer from '../reducers/index';
let createStoreWithMiddleware;
// Configure the dev tools when in DEV mode
if (__DEV__) {
createStoreWithMiddleware = compose(
applyMiddleware(thunkMiddleware),
devTools(),
persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(createStore);
} else {
createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
}
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import configureStore from './store/configureStore';
import {renderDevTools} from './utils/devTools';
const store = configureStore();
ReactDOM.render(
<div>
{/* <Home /> is your app entry point */}
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>
{/* only renders when running in DEV mode */
renderDevTools(store)
}
</div>
, document.getElementById('main'));
I hope you can help me! I don't know if something is wrong because I'm using Thunk and the example don't, or if something is missging.
Thank you guys!
It looks like you are using redux-thunk where as I am using redux-promise middlewares. They are totally different. You should change redux-thunk to redux-promise if you want to use the repo
I solved my issue. The difference is that I need to process de Promise received from signInUser throug the attribute that has it.
I had to receive the response in response and then access to the Promise in response.payload. In addition, I had to use .then() and .catch() to handle it.
// For any field errors upon submission (i.e. not instant check)
const validateAndSignInUser = (values, dispatch) => {
return new Promise ((resolve, reject) => {
let response = dispatch(signInUser(values));
response.payload.then((payload) => {
// if any one of these exist, then there is a field error
if(payload.status != 200) {
// let other components know of error by updating the redux` state
dispatch(signInUserFailure(payload));
reject(payload.data); // this is for redux-form itself
} else {
// store JWT Token to browser session storage
// If you use localStorage instead of sessionStorage, then this w/ persisted across tabs and new windows.
// sessionStorage = persisted only in current tab
sessionStorage.setItem('dhfUserToken', payload.data.token);
// let other components know that we got user and things are fine by updating the redux` state
dispatch(signInUserSuccess(payload));
resolve(); // this is for redux-form itself
}
}).catch((payload) => {
// let other components know of error by updating the redux` state
sessionStorage.removeItem('dhfUserToken');
dispatch(signInUserFailure(payload));
reject(payload.data); // this is for redux-form itself
});
});
}