The ability to reply to discussions is limited to PRO members. Want to join in the discussion? Click here to subscribe now.

Redux: Persisting the State to the Local Storage

Redux: Persisting the State to the Local Storage

7:36
We will learn how to use store.subscribe() to efficiently persist some of the app’s state to localStorage and restore it after a refresh.
Watch this lesson now
Avatar
egghead.io

We will learn how to use store.subscribe() to efficiently persist some of the app’s state to localStorage and restore it after a refresh.

Avatar
strajk

Is it important to distinguish between null and undefined on initializing reducers?

Specifically, can I replace:

if (serializedState === null) {
  return undefined
}
return JSON.parse(serializedState)

with just

return JSON.parse(serializedState)

as

JSON.parse(null) // => null
Avatar
strajk

Lesson learned (the hard way) with node-uuid

It seems like a small library, but when used with Browserify on client, it will bundle also Crypto polyfill and this:

const uuid = require("node-uuid")
console.log(uuid.v4())

compiled with browserify test.js -o test.bundle.js will output 562KB monster

Avatar
Zhentian

https://github.com/reactjs/redux/blob/master/test/createStore.spec.js#L546

looks like second args for createStore() can accept undefined, [], {} & fn but not null.

In reply to strajk
Avatar
Dean

I'd like to know what tradeoff/benefit is to to use store.subscribe() over putting the localStorage capability in a middleware?

Avatar
Dan Abramov

Yes, the distinction between null and undefined important. The ES6 feature we use in Redux (as noted in the previous lesson) is that default argument syntax only kicks in if state is undefined. If you pass null, the combined reducer won’t use the default {} state, and will crash trying to access previous values of the state of the nested reducers.

In reply to strajk
Avatar
Dan Abramov

It seems like a small library, but when used with Browserify on client, it will bundle also Crypto polyfill

Good catch, I didn’t realize this. While I believe this is configurable, this fork might be a better choice (uuid on npm).

In reply to strajk
Avatar
Dan Abramov

I'd like to know what tradeoff/benefit is to to use store.subscribe() over putting the localStorage capability in a middleware?

No real difference IMO.

In reply to Dean
Avatar
Anshuman

Regarding the use of uuid - isn't it recommended to have values for the key prop based on the identity of the item in question?

Thus - if one doesn't have server-assigned ids for each item - making a hash of a given item's data potentially a better choice for the key. Here it doesn't make sense because one could trivially generate two todos with identical content and properties, but perhaps in a table of user records a hash is a better choice?

I am struggling with finding a general answer to this question as I start to convert a large project to an SPA with state persisted/cached via local storage.

References:
https://github.com/facebook/react/issues/1342#issuecomment-39230939
https://facebook.github.io/react/docs/reconciliation.html#trade-offs

In reply to Dan Abramov
Avatar
Eddie Flores

What font are you using on your console?

Avatar
Piyabhum Sornpaisarn

I am using combine reducer here and I also use reduxdev tools. I can see that my persistedData have the previous sate on it but when I tried to put into store it has error. Can you please explain ? my store look like this

export const store = createStore(rootReducer, [persistedState], composeEnhancers(
applyMiddleware(thunk))
)

Avatar
Evan Gillogley

This seems very hacky and hard coded and not really a part of redux. Is there any way to use a middleware per reducer (meta reducer) and wrap it like so? - combineReducer({ todos: localstorageMeta('todos', todosReducer) }) then everything is stored without any configuration - the method above is only useful for async stores like indexDB - the meta reducer - here's a quick implementation -

{ RESET_STATE } from './reset';
export function localstorageMeta (key: string, reducer: any): any {
  return function(state: any, action: any): any {
    let nextState = reducer(state, action);

    let storageState = JSON.parse(localStorage.getItem(key));
    if (action.type === RESET_STATE || action.type.includes('DELETE')) {
      localStorage.removeItem(key);
    } else if (!state && storageState || action.type === '@@redux/INIT') {
      nextState = storageState;
    } else if (nextState && nextState !== storageState) {
      localStorage.setItem(key, JSON.stringify(nextState));
    }
    return nextState;
  };
};

// same with cookies
const Cookie = require('js-cookie');
import { RESET_STATE } from './reset';
export function cookieMeta (
  key: string,
  reducer: any,
  expiry: Date | number = 365,
  path: string = '/',
  domain: string = window.location.hostname): Function {
  return function(state: any, action: any): any {
    let nextState = reducer(state, action);
    let cookieState = Cookie.getJSON(key);

    if (action.type === RESET_STATE || action.type.includes('DELETE')) {
      Cookie.remove(key);
    } else if (!nextState && cookieState || action.type === '@@redux/INIT') {
      nextState = cookieState;
    } else if (nextState && nextState !== cookieState) {
        Cookie.set(key, nextState, { expires: expiry, path: path, domain: domain, secure: process.env.local });
    }
    return nextState;
  };
};
Avatar
Omri Mor

I think the font is Operator Mono

In reply to Eddie Flores
HEY, QUICK QUESTION!
Joel's Head
Why are we asking?