38

I'm really new to React and Redux, I've been following Stephen Grider's Advanced React and Redux course and I'm doing the client side of the authentication. I already have a token saved on my local storage and everything seemed to work fine until I refreshed the page. When I sign in/sign up the navigation changes to display the log out button but then if I manually refresh the page the navigation changes back to display the sign in/sign up buttons.

I'm really new to this and have no idea what should I include as code snippets. I'll leave the reducer and the actions/index.js. Also this is a lik for my git repository.

actions/index.js

import axios from 'axios';
import { browserHistory } from 'react-router';
import { push } from 'react-router-redux';
import { AUTH_USER, UNAUTH_USER, AUTH_ERROR } from './types';

const API_URL = 'http://localhost:3000';

export function signinUser({ username, password }) {
  return function(dispatch) {
    // Submit username/password to the server
    axios
      .post(`${API_URL}/signin`, { username, password })
      .then(response => {
        // If request is good...
        // - Update state o indicate user is authenticated
        dispatch({ type: AUTH_USER });
        // - Save the JWT token to local storage
        localStorage.setItem('token', response.data.token);
        // - Redirect to the route '/feature'
        browserHistory.push('/feature');
      })
      .catch(() => {
        // If request is bad...
        // -Show an error to the user
        dispatch(authError('Bad login info'));
      });
  };
}

export function signupUser({ username, email, password }) {
  return function(dispatch) {
    axios
      .post(`${API_URL}/signup`, { username, email, password })
      .then(response => {
        dispatch({ type: AUTH_USER });
        localStorage.setItem('token', response.data.token);
        browserHistory.push('/feature');
      })
      .catch(response => {
        // TODO
        console.log(response);
        dispatch(authError('There was an error'));
      });
  };
}

export function authError(error) {
  return {
    type: AUTH_ERROR,
    payload: error
  };
}

export function signoutUser() {
  localStorage.removeItem('token');
  return { type: UNAUTH_USER };
}

reducer/auth_reducer.js

import { AUTH_USER, UNAUTH_USER, AUTH_ERROR } from '../actions/types';
export default function(state = {}, action) {
  switch (action.type) {
    case AUTH_USER:
      return { ...state, error: '', authenticated: true };
    case UNAUTH_USER:
      return { ...state, authenticated: false };
    case AUTH_ERROR:
      return { ...state, error: action.payload };
  }

  return state;
}

Thanks in advance, if you need any extra code snippet just please let me know.

OmarAguinaga
  • 707
  • 1
  • 8
  • 17
  • Are you trying to do `localStorage.getItem('token')` and logging in the user as soon as the app mounts? Because it's not going to happen by itself. –  Oct 10 '17 at 18:14
  • 2
    To be clear: all `state` is lost when you refresh the page; anything you want saved has to be manually saved and restored. –  Oct 10 '17 at 18:21
  • Possible duplicate of [How can I persist redux state tree on refresh?](https://stackoverflow.com/questions/37195590/how-can-i-persist-redux-state-tree-on-refresh) – Michael Freidgeim Aug 22 '19 at 23:11

7 Answers7

17

do not reinvent the wheel

To store redux state even after page refresh you can use

https://www.npmjs.com/package/redux-persist

It is easy to implement and robust.

Ashad Nasim
  • 2,511
  • 21
  • 37
13

In your reducer file reducer/auth_reducer.js you can define the initial state of the reducer.

const initialState = { 
user: localStorage.getItem('user'), foo:'bar',
};

export default function(state = initialState, action) {
    ...

in your initialState you can load stuff from localstorage or from a cookie (cookie is preferred for auth stuff).

initialState can also be setup in your createStore. It's up to you. Where you need the initial state. I use async for my routes so I can't use createStore to hold all my initial state since some routes maybe never be loaded.

const initialState = {
  user: localStorage.getItem('user'),
};

const store = createStore(mainReducer, initialState);

There is a library you can use called redux-persist. This will give you more control of what state you want to keep. (https://github.com/rt2zz/redux-persist)

Richard Torcato
  • 2,504
  • 25
  • 26
9

To retain Redux state through page refreshes, you need to persist the app state by storing it in localStorage and retrieve it on page load. Try to dispatch an action in the componentDidMount of your App component, which retrieves the data from the localStorage

Dane
  • 9,242
  • 5
  • 33
  • 56
8

You need to persist app state in localStorage. Here is a tutorial made by Dan Abramov, creator of redux.

Piotr Pliszko
  • 676
  • 5
  • 9
  • 5
    @Sergiu Dan Abramov was the creator of Redux and a co-author on Create React App - not React. Jordan Walke created React at Facebook. It's on the wikipedia for React... – milesaron Jan 09 '20 at 17:00
3

Do something like that: i used this method for my project

function saveToLocalStorage(store) {
    try {
        const serializedStore = JSON.stringify(store);
        window.localStorage.setItem('store', serializedStore);
    } catch(e) {
        console.log(e);
    }
}

function loadFromLocalStorage() {
    try {
        const serializedStore = window.localStorage.getItem('store');
        if(serializedStore === null) return undefined;
        return JSON.parse(serializedStore);
    } catch(e) {
        console.log(e);
        return undefined;
    }
}

const persistedState = loadFromLocalStorage();

const store = createStore(reducer, persistedState);

store.subscribe(() => saveToLocalStorage(store.getState()));
Guru
  • 922
  • 9
  • 12
0

we can setup store to listen the sessionStore or localStorage values, so that value will get preserve,

for example

import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import { createBrowserHistory as createHistory } from 'history';
// import createHistory from 'history/createBrowserHistory';
import rootReducer from '@reducers';
import ApiClient from '@helpers/ApiClient';
import createMiddleware from '@reducers/middleware/clientMiddleware';

export const history = createHistory();

const client = new ApiClient();
const initialState = { users: JSON.parse(window.sessionStorage.getItem('redux') || '{}') };
const enhancers = [];
const middleware = [
  createMiddleware(client),
  thunk,
  routerMiddleware(history)
];

if (process.env.NODE_ENV === 'development') {
  const devToolsExtension = window.devToolsExtension;

  if (typeof devToolsExtension === 'function') {
    enhancers.push(devToolsExtension());
  }
}

const composedEnhancers = compose(
  applyMiddleware(...middleware),
  ...enhancers
);

const store = createStore(
  rootReducer,
  initialState,
  composedEnhancers
);

const storeDataToSessionStorage = () => {
  window.sessionStorage.setItem('redux', JSON.stringify(store.getState().users));
};

store.subscribe(storeDataToSessionStorage);

export default store;

so that user reducer always get initial values from the session storage. (you can push to localStorage also based on your requirement)

Sunil Kumar
  • 420
  • 4
  • 13
0

Please check these:-

Just you need to add one condition

import { AUTH_USER, UNAUTH_USER, AUTH_ERROR } from '../actions/types';
export default function(state = {}, action) {
  switch (action.type) {
    case AUTH_USER:
      return { ...state, error: '', authenticated: localStorage.getItem('token') ? true : false }; // just add this condition here
    case UNAUTH_USER:
      return { ...state, authenticated: localStorage.getItem('token') ? true : false }; // just add this condition here also
    case AUTH_ERROR:
      return { ...state, error: action.payload };
  }

  return state;
}

saddam
  • 176
  • 2
  • 8