Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux: What is the correct place to save cookie after login request?

Tags:

I have the following situation: The user enters his credentials and clicks a Login button. An API call is done in the action creator via redux-thunk. When the API call was successful, another action is dispatched containing the response from the server. After the (successful) login I want to store the users session id in a cookie (via react-cookie).

Action creator

export function initiateLoginRequest(username, password) {   return function(dispatch) {     dispatch(loginRequestStarting())      return fetch('http://path.to/api/v1/login',       {         method: 'POST',         headers: {           'Accept': 'application/json',           'Content-Type': 'application/json'         },         body: JSON.stringify({           username: username,           password: password         })       })       .then(checkStatus)       .then(parseJSON)       .then(function(data) {         dispatch(loginRequestSuccess(data))       })       .catch(function(error) {         dispatch(loginRequestError(error))       })   } }  export function loginRequestSuccess(user) {   return {     type: ActionTypes.LOGIN_REQUEST_SUCCESS,     user   } } 

Reducer

export default function user(state = initialState, action) {   switch (action.type) {     case ActionTypes.LOGIN_REQUEST_SUCCESS:       cookie.save('sessionId', action.user.sid, { path: '/' })       return merge({}, state, {         sessionId: action.user.sid,         id: action.user.id,         firstName: action.user.first_name,         lastName: action.user.last_name,         isAuthenticated: true       })     default:       return state   } } 

Right now the reducer responsible for LOGIN_REQUEST_SUCCESS saves the cookie. I know the reducer has to be a pure function.

Is saving a cookie in the reducer violating this principle? Would it be better to save the cookie inside the action creator?

like image 983
Steve Avatar asked Jul 15 '16 13:07

Steve


People also ask

What should I save in Redux?

But...that means in most of off applications the only data what I should store in Redux store is the logged in user data. That is the only entity which attributes influences all the screens (role, login name, user settings). Usual applications do not need to remember entered data between screens, cache, history....

Is Redux a cookie?

The point of redux is immutable data flow, cookies are more like a global variable. You could use the cookie store or local storage API to store data (see react-redux-persist ) but you wouldn't rely on it performance wise.

What is the only way to trigger a state change in Redux?

dispatch(action)​ Dispatches an action. This is the only way to trigger a state change. The store's reducing function will be called with the current getState() result and the given action synchronously.


2 Answers

Have a look at redux-persist.

You can persist/save your reducers (or parts of them) in LocalStorage.

Concept

  • Initiate login.
  • Receive cookie from server.
  • Dispatch login success.
  • Reducer stores cookie in memory.
  • Persist middleware stores reducer state in LocalStorage.

Example

Install

npm install --save-dev redux-persist 

Example Usage

Create a component that wraps the persistence/rehydration logic.

AppProvider.js

import React, { Component, PropTypes } from 'react'; import { Provider } from 'react-redux'; import { persistStore } from 'redux-persist';  class AppProvider extends Component {     static propTypes = {         store: PropTypes.object.isRequired,         children: PropTypes.node     }      constructor(props) {         super(props);          this.state = { rehydrated: false };     }      componentWillMount() {         const opts = {             whitelist: ['user'] // <-- Your auth/user reducer storing the cookie         };          persistStore(this.props.store, opts, () => {             this.setState({ rehydrated: true });         });     }      render() {         if (!this.state.rehydrated) {             return null;         }          return (             <Provider store={this.props.store}>                 {this.props.children}             </Provider>         );     } }  AppProvider.propTypes = {     store: PropTypes.object.isRequired,     children: PropTypes.node }  export default AppProvider; 

Then, in your index.js or file in which you set up the store, wrap the rendered components in your new AppProvider.

index.js

... import AppProvider from 'containers/AppProvider.jsx'; ...  render((     <AppProvider store={store}>         ...     </AppProvider> ), document.getElementById('App')); 

This will serialize your user reducer state to LocalStorage on each update of the store/state. You can open your dev tools (Chrome) and look at Resources => Local Storage.

like image 180
Mario Tacke Avatar answered Oct 21 '22 00:10

Mario Tacke


I'm not sure if this is the "right" way, but that's how my team is persisting the logged user in the Redux app we built:

We have a very default architecture, an API ready to receive requests in one side, and a React/Redux/Single Page app that consumes this API endpoints in the other side.

When the user credentials are valid, the API's endpoint responsible for the login respond to the app with the user object, including an access token. The access token is latter used in every request made by the app to validate the user against the API.

When the app receives this user information from the API two things happen: 1) an action is dispatched to the users reducer, something like ADD_USER, to include this user in the users store and 2) the user's access token is persisted in the localStorage.

After this, any component can connect to the users reducer and use the persisted access token to know who is the logged user, and of course if you have no access token in your localStorage it means the user is not logged in.

In the top of our components hierarchy, we have one component responsible to connect to the users reducer, get the current user based on the access token persisted in the localStorage, and pass this current user in the React's context. So we avoid every component that depends on the current user to have to connect to the users reducer and read from the localStorage, we assume that this components will always receive the current user from the app's context.

There are some challenges like token expiration that adds more complexity to the solution, but basically this is how we are doing it and it's working pretty well.

like image 38
Nícolas Iensen Avatar answered Oct 21 '22 01:10

Nícolas Iensen