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.
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
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
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.
Yep, correct... it has to be undefined
otherwise
https://github.com/reactjs/redux/blob/master/src/combineReducers.js#L111
state
would not use default value {}
https://github.com/reactjs/redux/blob/master/src/combineReducers.js#L128 would throw
I'd like to know what tradeoff/benefit is to to use store.subscribe() over putting the localStorage capability in a middleware?
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.
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).
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.
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
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))
)
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 -
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;
};
};
I think the font is Operator Mono
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?
While I'm not certain what are considered best practices for creating IDs, I can say from personal experience that it's risky to use an object's own properties to construct a unique ID, because property values can and do change—even when you think they won't. Dates get reformatted, names include typos, etc. Even the most so-called "fixed" data is subject to revision. That might not be an issue if you're not referencing the IDs (but are only using them to prevent duplicates, for example), but it becomes a huge issue as soon as you relate things to other things based on IDs. As soon as one of those IDs changes, you've got some painful work to do updating the broken relationships on who knows how many records.
Whereas if you use something like a uuid
, which is totally decoupled from what it's identifying, then you can change any other property at will without concern, and you'll never have reason to change the ID and break relationships. (The same holds true for the numeric ID Dan was using previously.) There's clearly no relational business going on in this lesson, so there's nothing stopping you from using a property-based ID, but it's probably a good idea to get in the habit of not doing that.
One scenario where a decoupled ID would present a challenge is if you didn't know the ID of your target and wanted to look it up based on the object's properties. But that's what search algorithms are for, and you can also do something simple like constructing a lookup at runtime that maps a property-based hash to a uuid
or whatever. Then you get the best of both worlds.
export const store = createStore(rootReducer, [persistedState], composeEnhancers( applyMiddleware(thunk)) )
Maybe the square braces around persistedState
are causing the issue? In the lesson, it's createStore(todoApp, persistedState)
(no square braces).