rt2zz / redux-persist-crosstab

Keep redux browser tab state in sync
85 stars 28 forks source link

Support for localForage (and other non-localStorage engine) #7

Open mattmcdonald-uk opened 7 years ago

mattmcdonald-uk commented 7 years ago

This package won't work for me, and I believe it's due to using localForage instead of localStorage which means no storage events are being fired (https://github.com/localForage/localForage/issues/244).

Is there any way to have redux-persist fire an event that can be listened to instead?

TheNeikos commented 7 years ago

@rt2zz Any updates on this?

rt2zz commented 7 years ago

afaik indexeddb has no way to listen for changes (other than polling). If there is a solution, making redux-persist-crosstab support it should be trivial. More info here: https://github.com/localForage/localForage/issues/244

If you have any other ideas for how this can be done let me know I am happy to integrate.

Altiano commented 6 years ago

Ah so this is why it's not working for me. I think I have a solution. How about everytime indexeddb change, which trigger "inbound", you set value on localStorage, so that another tabs will be notified that indexeddb is changed.

localStorage acts as the bridge.

piotr-cz commented 6 years ago

It should also be possible to use BroadcastChannel to subscribe/emit information about changes to the storage (ATM supported in Chrome and Firefox) or MessageChannel

piotr-cz commented 6 years ago

This is my take on implemetation of crosstab state using BroadcastChannel, code should be placed inside configureStore function as it requires access to store and persistConfig objects.

Works wis Redux Persist v5

import { REHYDRATE } from 'redux-persist/lib/constants'

if (window.BroadcastChannel) {
  const csBc = new window.BroadcastChannel('crosstabState-channel')
  let dispatchingSelf = false

  store.subscribe(() => {
    // Prevent endless loop when rehydration was called in this tab
    if (dispatchingSelf) {
      return dispatchingSelf = false
    }

    // Post state to channel
    csBc.postMessage(store.getState())
  })

  csBc.addEventListener('message', ev => {
    dispatchingSelf = true

    // Rehydate store with received state
    store.dispatch({
      key: persistConfig.key,
      payload: ev.data,
      type: REHYDRATE,
    })
  })
}

BroadcastChannel won't broadcast to inself, but store rehydration will trigger new store subscribe callback, so there need to be a flag to distinguish that (I've tried to attach new properties to state or piggy-back on _persist value when dispatching rehydration but that didn't work)

References: