When to use local state and when to use global state

| In Articles | 5th June 2020

This article will target React users but what I am presenting here and the advice I will give, should be taken into consideration for any other programming language or library meant to be used for the same purpose, whether it is REACT, Angular or VUE.

Global State and Local State for JS one page applications, explained

If you have interest in one of this smart JS libraries mentioned above and in particular REACT, which I prefer among other, and used daily for a few years, you will learn things related to how data is kept/saved in session and used in frontend after is returned from an API. You will learn about limitations/constraints/risks (security risks) and how much code and logic will involve using different methods.

Many times, for your code to be cleaner and more easy to read and understood by you and others, more logic and planning time should be allocated. Only go in one direction after you considered all other direction and done some software testing to have an idea where it can fail.

Things done in a hurry will never give good results and even if you will have a fast solution for your problem, this solution can be a disaster later on. Projects that are not well planned are not scalable, not easy to read and understand, and they will end having a lot more code.

Let's talk about Local State and Global State.

As we said above, the State of an application can be global or local (at the component level). Even if they are techniques to have a global React State without a State management library is highly recommended to use Redux or MobX ar any other library that can help with that. They all have pros and cons but this is not the purpose of this article.

Any of this 2 types of states can be stored on localStorage, a file in the user computer that can be accessed even after the browser is closed. This method is considered less secure for sensitive information but very useful if you need to store large amount of data or you want to keep data about the user (lets say a visitor that is not registered on your website but is adding products intro your application shopping cart and you still want to show the cart with the products even after a long period of time when they return. In this period of time the visitor can do many computer shut downs and the only way to keep the data will be thru localStorage).

Even if the local state of a component can be stored on localStorage to, is no point to do that. A state defined at the component level is meant to be used only on that place for a short period of time.

The global state can be used in 2 ways: stored in localStorage or into the browser session.

The difference between this 2 methods is that the state stored into localStorage will survive after the browser is closed and can be accessed by the same platform on a different browser (this is the reason why is very important to remove the state or part of it that have sensitive data when user is closing the application or logout), when the state kept into the browser session will be removed when the browser will be close, when the tab with the application will be closed and even when the page will get a refresh. On one page application, navigation between pages is not done with refresh, but with simple manipulation of the URL and loading the component needed.

How to set global state

The location of the reducers dealing with the global state in my project is app/src/js/store/reducers.js

import { combineReducers } from 'redux'

import user from '../modules/user/store/reducer';
import customer from '../modules/customer/store/reducer';
export default combineReducers({ user, customer })

and on app/src/js/modules/user/store/reducer.js like example, we will have

import {
  USER_REQUEST,
  USER_SUCCESS,
  USER_FAILURE
} from './actions';

const initialState = {
  user: {
    data: null,
    fetching: false,
    complete: false,
    error: null
  }
};

const reducer = (state = initialState, { type, payload = null }) => {
  switch(type) {
  
    case USER_REQUEST:
      return userRequest(state)

    case USER_SUCCESS:
      return userSuccess(state, payload)

    case USER_FAILURE:
      return userFailure(state)

    default:
      return state;
  }
};

function userRequest(state) {
  state = Object.assign({}, state, {
    user: {
      fetching: true,
      complete: false,
      error: null,
      data: null
    }
  })

  return state;
}

function userSuccess(state, payload) {
  state = Object.assign({}, state, {
    user: {
      fetching: false,
      complete: true,
      error: null,
      data: payload
    }
  })

  return state;
}

function userFailure(state, error) {
  logout();
}

function logout(state) {
  return {
    ...state, 
    user: {
      fetching: false,
      complete: false,
      error: null,
      data: null
    }
  } 
}

export default reducer;

the actions file from app/src/js/modules/user/store/actions.js

import {
  USER_REQUEST,
  USER_SUCCESS,
  USER_FAILURE
} from './actions';

export function userRequest() {
  return {
    type: USER_REQUEST
  }
}

export function userSuccess(payload) {
  return {
    type: USER_SUCCESS,
    payload
  }
}

export function userFailure(payload) {
  return {
    type: USER_FAILURE,
    payload
  }
}

The choose between global state and local state

Global state, if you will decide in using Redux, will use actions and reducers. Actions will be called on the component where the data is returning lets say from an API response and help with its transportation (having data as a payload). The payload is transported to the reducer and the reducer is setting the global state. The transported data can be shaped (changed) before is attached to the action or into the reducer before is attached to the global state

While in a project we can have different modules, different parts of the global state can be set in each module, into the state folder/reducers.js file and after that all this reducers files to be imported into one global state.

Local state is the state used at the component level. Lets suppose you get some data from an API, setState with this data and use it on the render method after that. Nothing can be more simple than that.

import React, { Component } from 'react'
import axios from 'axios';

class Main extends Component {

	constructor(props) {
		super(props);		
		this.state = {
			customer: null,
			error: null
		};
	}
	
	componentDidMount() {
		//data connected to this component from the store
		if (this.props.user) {
			//get data
			return axios.get('customer')
			  .then(response => {			  
				this.setState({customer: response.data.customer});
			})
			.catch(error => {
				this.setState({error: response.data.error});
			});
		}
	}
	
	//...some other methods	

}

Things can be a little bit more complex if the local state need to be passed as props to a child component where values will be changed, lets say, into a form, and returned to the parent component which will do the final thing with them. Data is sent from the parent component to the child as a prop and the child component will use a method attached as prop to return the data thru this method to the parent. This data will not survive to a page refresh and when using local state you need to be sure that if user is refreshing the page or if something else is triggering a refresh, the initialValues are retrieved again on componentDidMount() method.

The global state survives for a longer period of time because a global state will come from a higher component level or localStorage and will connect as props with a component with the Connect method of Redux.

Like a child component is getting initialValues from a parent component, the same a parent component will get the initialValues from the global state, using props, and the need of getting data on each page refresh is not needed within componentDidMount.

Though, even if data is kept into the global state is still has to be refreshed from time to time to be accurate when more users have the possibility to use the same information. For this, some functions called services will be used to update the global state at a set time and inform the user about the changes if the user is on the page of which data was just refreshed. A data refresh process is not needed if the data is stored into the local state because new data is called from the database each time when somebody is accessing a component.

When data should be kept into gloabal state and when into the local state?

Is important to store at a global level only information that is needed over the entire application, or on more components. This is because you want to avoid repeatedly calling the same data from the API in different places of the app.

Some of the data objects that best fit into the global state will be the ones related to logged in user (like name/email), user permissions and roles, and any other data, like for a customer, is a user is related to a customer like example.

The customer in this case can also have values that will dictate what pages the users can see based on licence, subscription or number of users, etc (things can get very complex)

On the other hand, the local state should be used on specific components related to this data. As example we can give a details page of an invoice. Data can be loaded in componentDidUpdate and used to view the invoice or edit it. Once the page is closed this information is not needed anymore, so it can be lost/removed.