elgerlambert / redux-localstorage

Store enhancer that syncs (a subset) of your Redux store state to localstorage.
MIT License
1.32k stars 107 forks source link

persistState namespace #3

Closed raineroviir closed 8 years ago

raineroviir commented 9 years ago

redux-devtools currently uses persistState as a module, which conflicts with this module's persistState. Any way I can use both of these addons?

Thanks

elgerlambert commented 9 years ago

Hi @raineroviir,

There are two things you can do about the namespace issue.

  1. Since persistState is exported as default you can import it using any name you like.
  2. Use redux-localstorage as a drop-in replacement for dev-tool's persistState.
import { devTools } from 'redux-devtools';
import persistState from 'redux-localstorage'; // ~v1.0.0 (beta)

const finalCreateStore = compose(
  devTools(),
  persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)),
  createStore
);

Works exactly the same as:

import { devTools, persistState } from 'redux-devtools';

const finalCreateStore = compose(
  devTools(),
  persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)),
  createStore
);

There is however a different conflict/issue when you're using dev-tools and that's the fact that dev-tools "lifts" your store state. In other words, your store doesn't have the shape you would expect it to, based on your reducers. If you're persisting your complete store state (in dev and in production) there isn't any issue, but it falls apart if you only want to persist a subset of your store state (in production).

This is something I still have to look into further, to see if an elegant solution can be found (or at least a recommended way of doing things..). How are you enabling dev-tools while developing and disabling/removing it when in production? - that may very well provide the means to configure persistState differently to meet your needs, both in production and while developing using dev-tools.

raineroviir commented 9 years ago

Thank you very much for the thorough explanation. I am not enabling/disabling dev-tools in production, but I will cross that bridge soon

snackycracky commented 9 years ago
import persistState from 'redux-localstorage'
....
if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) {
        let devtools = require('redux-devtools');
        finalCreateStore = compose(
            applyMiddleware(middleware),
            devtools.devTools(),
            persistState(/*paths , config*/),
            devtools.persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)),
            createStore
        );
    } else {
        finalCreateStore = compose(
            applyMiddleware(middleware),
            persistState(/*paths, config*/),
            createStore
        );
    }
ruprict commented 9 years ago

Hey @snackycracky , this doesn't really work for me. In development, the state that I want to filter is deeply nested in a { computedStates: [{state: { stuff I want to get to }]}, so the keys I provide to filter don't reconcile. This is @elgerlambert was alluding to above. I think you'd almost have to have a redux-devtools-filter adapter to return the last computedStates[last].state, which I guess wouldn't be that bad since it'd only be a dev dependency.

fully prepared to be wrong on this and doing something stupid

ruprict commented 9 years ago

Maybe something like this?

elgerlambert commented 9 years ago

Hi @ruprict, thanks for the gist. The conclusion I had come to is that it doesn't actually make any sense to filter the state you want to persist when you're using dev-tools. If you do, it would break the ability to replay/reload a previous dev-session since you didn't persist what it needs.

If alternatively, you persisted both everything that dev-tools needs, as well as a filtered section of state that you ultimately want in production, then the state persisted by dev-tools would override whatever filtered state you persisted.

Thinking about it while I write, the use case that remains and is probably what you're after; if you start a session with e.g. http://localhost:3000/?debug_session=123 you want to persist everything that dev-tools needs, but if you just went to http://localhost:3000/, you'd want to see the behaviour you expect in production. Is that correct?

ruprict commented 9 years ago

Hey @elgerlambert, cheers for replying.

So, if the dev-tools are persisting everything under the key redux-dev-session-&debug_session=123, and redux-localstorage is filtering out what I want under a differen key (like, redux-localstorage), then this seems to work.

I get both keys, the state looks like I’d think. I am persisting both states:

var storage;
if (process.env.NODE_ENV === 'development') {
  storage = compose(
    devToolsFilter('auth'),
    adapter(localStorage)
  );
} else{
  storage = compose(
    filter('auth'),
    adapter(localStorage)
  );
}
...
var middleware = clientMiddleware(client), finalCreateStore;
  if (process.env.NODE_ENV === 'development') {
    let devTools = require('redux-devtools');
    finalCreateStore =
      compose(
        applyMiddleware(middleware),
        devTools.devTools(),
        persistState(storage, 'esalerugs'),
        typeof window !== 'undefined' ?
          devTools.persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)) :
          identity,
        createStore
      )
  } else
    finalCreateStore = applyMiddleware(middleware)(createPersistentStore);

I am not sure if this breaks something else, but it's "working" for me. What I want is for the dev to act like prod from the localStorage perspective, regardless of the dev tools being enabled.

I apologize if I am being obtuse. I am still swimming a bit in the deep end with redux/react/etc.

elgerlambert commented 9 years ago

Thanks @ruprict, yeah you've essentially created the last scenario that I was describing in my previous comment. Rather then creating a special devToolsFilter that replicates everything that filter does you can create the following (significantly simpeler!) storage enhancer:

function unliftState(liftedState) {
  const { computedStates, currentStateIndex } = liftedState
  const { state } = computedStates[currentStateIndex]
  return state
}

export default function unliftDevToolsState(storage) {
  return ({
    ...storage,
    put: (key, state, callback) => {
      storage.put(key, unliftState(state), callback)
    }
  })
}

With this storage enhancer you would change your snippet above to the following:

if (process.env.NODE_ENV === 'development') {
  storage = compose(
    unliftDevToolsState,
    filter('auth'),
    adapter(localStorage)
  );
} else{
  storage = compose(
    filter('auth'),
    adapter(localStorage)
  );
}

Make sure unliftDevToolsState comes before filter.

The code above is untested, but should do the trick, try it out and let me know :). I still plan on looking into this further and will keep an eye on dev-tools to see how it evolves, but I guess the unliftDevToolsState storage enhancer offers a solution for now. I will leave this issue open to continue the discussion around dev-tools and welcome any suggestions!

Btw, does the order of persistState and devTools.persistState matter (inside your finalCreateStore compose)? Does it break if you switch them around?

ruprict commented 9 years ago

@elgerlambert works great! Thanks!

The order doesn't seem to matter, nothing breaks when I switch them.

elgerlambert commented 9 years ago

Looks like the unliftDevToolsState storage enhancer isn't actually needed. If you place redux-localstorage's persistState above devTools everything appears to be working as it should:

import {devTools, persistState as persistDevToolsState} from 'redux-devtools'
import persistState from 'redux-localstorage'
import adapter from 'redux-localstorage/lib/adapters/localStorage'
import filter from 'redux-localstorage-filter'

const storage = compose(
  filter('key')
)(adapter(window.localStorage));

const createPersistentStoreWithDevTools = compose(
  persistState(storage, 'my-storage-key'),
  devTools()
  persistDevToolsState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(createStore);

It also solves a different issue whereby devTools swallows the redux-localstorage/INIT action. ref: https://github.com/elgerlambert/redux-localstorage/issues/5#issuecomment-137128656

Note: the above gist is based on redux-localstorage@1.0.0-rc and redux@2.0.0