rt2zz / redux-persist

persist and rehydrate a redux store
MIT License
12.95k stars 865 forks source link

Manual rehydrate #952

Closed bohdan145 closed 5 years ago

bohdan145 commented 5 years ago

How can i stop automatic re-hydration and launch it manually only if there is no internet connection ?

aguynamedben commented 5 years ago

I'm not sure how to to do that, but I'm curious about your use case... why do you want to stall rehydration until there is internet? Are you using a custom storage that requires internet? My app just uses localStorage, so I don't care if there is internet during rehydration.

Also, note the v4->v5 updates for autoRehydrate: https://github.com/rt2zz/redux-persist/blob/208c6b112ead87b3701dfacaae2cdbe78377775a/docs/MigrationGuide-v5.md#migration-from-v4-to-v5 Maybe something in there will help you figure out what to do here.

bohdan145 commented 5 years ago

Because in my application(chatting app) i store messages locally, but what if messages was changed when i was offline ? (like deleted or changed text). How do i know that i need to delete my local message or change it to be the same as server version?

cvanem commented 5 years ago

@bohdan145 It might not be the best way, but I believe you can just intercept the 'persist/REHYDRATE' action for your desired store(s). In your store's reducer you can either return the hydrated payload or your original state. Inside your desired reducer you would add something like this:

case 'persist/REHYDRATE':
  const payload = (action as any).payload;
  const shouldHydrate = false; //or true
  const newState = shouldHydrate ? payload.myStore : state;      
  return {
    ...newState,
  };

That would at least allow you to prevent hydration based on your internet connection. I'm sure there is a way to re-trigger the hydration logic once the internet is restored although I am not sure how. Maybe you can dispatch the REHYDRATE action or re-initialize the persistor to accomplish this.

leoek commented 5 years ago

Hi I am interested in this as well. I need to stall the rehydration until my storage is initialized, which can only be done after the user entered a password which will be used for file level decryption in the implementation of the storage.

leoek commented 5 years ago

@aguynamedben Do you have any ideas whether this is possible or not, or who might now?

cvanem commented 5 years ago

@leoek I think you can accomplish what you need by ignoring the initial hydration and then manually re-hydrating using getStoredState.

Once you have the state you can dispatch the necessary actions to update your app state as needed. If you use my example above you could dispatch 'persist/REHYDRATE' with shouldHydrate set to true and include the data from getStoredState in the dispatched action.

If that doesn't work for whatever reason, another idea would be to delay the persistor initialization and adding the persistedReducer to your store until your storage is initialized. That seems like it should work in theory, but might get messy.

leoek commented 5 years ago

@cvanem Thank you for the suggestion. This seems like a viable option. In theory I only need to encrypt parts of the state i am persisting and I should be able to use multiple persistors. One configured like this:

const persistConfig = {
  key: "root",
  storage: FSStorage(),
  whitelist: ["some", "key"],
  transforms: []
};

and one for each encrypted reducer which is initialized later, configured like this:

const encryptedPersistConfig = {
  key: "encryptPart",
  storage: FSStorage(<credentials which are not available at startup>),
  transforms: []
};

Are there any cave hats with the usage of multiple persistors, you know of?

mauriciopasquier commented 5 years ago

To me it seems "obvious" that a manual rehydration would reset the store just the same as restarting the app, white/blacklisting taken into account. Wouldn't it be a common use cast, "just discard all the state not whitelisted", for example?

leoek commented 5 years ago

@mauriciopasquier rehydration does not reset the state. I believe the default reducer for rehydration does a shallow merge. If you need to merge within a deeper level you can use your own reducer.

(Manual means delayed until a manual callback is triggered. You can check the implementation in #986 This is only a very little change. We are using it in production for quite a while now, without any issues.)

mauriciopasquier commented 5 years ago

@leoek thanks for your reply, I guess I'll implement "restarting" manually then, but I wanted to KISS and DRY with redux-persist white/blacklisting configuration.

leoek commented 5 years ago

@mauriciopasquier Your requirement seems unrelated to this issue. I think it is best if you open a new issue and pull request for that.

bitflower commented 4 years ago

@cvanem Can you elaborate a bit on what getStoredState is?

leoek commented 4 years ago

@bitflower getStoredState is the function which retrieves the state (through the provided storage), deserializes it and than applies the provided transformations. I believe you can get a pretty good understanding from the source: https://github.com/rt2zz/redux-persist/blob/master/src/getStoredState.js

bitflower commented 4 years ago

Thanks @leoek I swear I had previously tried to import it but didn't find it. Got it now ;-)

@cvanem says "I think you can accomplish what you need by ignoring the initial hydration and then manually re-hydrating using getStoredState." - I wonder how this can be achieved on a global level. Not on a per-reducer basis as also described in this thread.

leoek commented 4 years ago

This was about doing the rehydration manually at a custom point in time (instead of doing it right after the application startup). However to achieve this you don't need to use getStoredState manually, as it was integrated into redux-persist with #986

bitflower commented 4 years ago

@leoek I've played around with manualPersist earlier and it seemed to rehydrate right one the beginning. Does this option effect only persisting or also rehydration?

bitflower commented 4 years ago

btw I've decided to add the reducer and persistor only after the user is logged in. I need seperate storages per user. This way I hope to be able to customize the storage after login and then "add" the persistor.

leoek commented 4 years ago

Setting manualPersist to true delays the rehydration (and persistence) until persistor.persist() is called.

This way I hope to be able to customize the storage after login and then "add" the persistor.

This is exactly what it was intended for.

bitflower commented 4 years ago

Aha, so procedure would look something like:

1: Setup store incl. persistence, manualPersist: true 2: User logs in 3: Configure persistence storage according to requirements for example incl. user info 4: Call persistor.persist() to "read" the persisted state the first time and continuously persist from then on

I wonder how to achieve 3: when the persist config was already defined in 1:

Thanks again for some more breadcrumbs ;-)

bitflower commented 4 years ago

Nvmd, found it out. I can set the config again in persistReducer to make it user agnostic.

I might PR a fix for the persistStore type which doesn't recognize the manualPersist option for me. I needed to cast to make it work:

persistor = persistStore(
    store,
    ({
      manualPersist: true
    } as unknown) as PersistorOptions
  );

Do you use typescript @leoek ?

Thanks again for your input and have a nice Sunday :-)

albanx commented 1 year ago

I have a similar issue. I need to hydrate the store only once the user has logged in, so that I can create a persistent store only by using the user id in react native (in case the user uses different account to login in the app)