Git Product home page Git Product logo

redux-state-sync's Introduction

Redux-State-Sync 3

A lightweight middleware to sync your redux state across browser tabs. It will listen to the Broadcast Channel and dispatch exactly the same actions dispatched in other tabs to keep the redux state in sync.

Why Redux-State-Sync?

It syncs your redux store across tabs with very minimal configuration.

Thanks to BroadcastChannel, we now have a more efficient way to communicate between tabs instead of using any type of local storage. However, Not all the browsers support BroadcastChannel API for now. So I used pubkey's BroadcastChannel to find the best way to communicate between tabs for redux-state-sync. pubkey's BroadcastChannel will make sure that the communication between tabs always works.

How to install

Install with npm.

npm install --save redux-state-sync

Install with yarn

yarn add redux-state-sync

TypeScript support

Install with npm.

npm install --save-dev @types/redux-state-sync

Install with yarn

yarn add --dev @types/redux-state-sync

Types are defined here

Before you use

Please take note that BroadcastChannel can only send data that is supported by the structured clone algorithm (Strings, Objects, Arrays, Blobs, ArrayBuffer, Map), so you need to make sure that the actions that you wanna send to other tabs doesn't include any functions in the payload.

If you are using redux-persist, you may need to blacklist some of the actions that is triggered by redux-persist. e.g. persist/PERSIST, persist/REHYDRATE, etc.

How to use

Create the state sync middleware with config:

import { createStore, applyMiddleware } from 'redux';
import { createStateSyncMiddleware, initMessageListener } from 'redux-state-sync';

const config = {
    // TOGGLE_TODO will not be triggered in other tabs
    blacklist: ['TOGGLE_TODO'],
};
const middlewares = [createStateSyncMiddleware(config)];
const store = createStore(rootReducer, {}, applyMiddleware(...middlewares));
// this is used to pass store.dispatch to the message listener
initMessageListener(store);
initMessageListener is a new function to fix the bug that if the other tab not triggering any action on first load, it cannot receive any messages.

Init new tabs with existing state:

  1. Use initStateWithPrevTab to get existing state from other tabs
import { createStore, applyMiddleware } from 'redux';
import { createStateSyncMiddleware, initStateWithPrevTab } from 'redux-state-sync';

const config = {
    // TOGGLE_TODO will not be triggered in other tabs
    blacklist: ['TOGGLE_TODO'],
};
const middlewares = [createStateSyncMiddleware(config)];
const store = createStore(rootReducer, {}, applyMiddleware(...middlewares));
// init state with other tabs
initStateWithPrevTab(store);
// initMessageListener(store);
Note: if you are already using initStateWithPrevTab, you don't need to initMessageListener anymore.
  1. Wrap your root reducer with withReduxStateSync
import { withReduxStateSync } from 'redux-state-sync';
const rootReducer = combineReducers({
    todos,
    visibilityFilter,
});

export default withReduxStateSync(rootReducer);
Note: ignore this if you are using redux-persist, because you will always inite your app with the state in the storage. However, if you don't want to persist the state in the storage and still want to init new tabs with opening tabs' state, you can follow the example above.

Config

channel

Unique name for Broadcast Channel

type: String

default: "redux_state_sync"

const config = {
    channel: 'my_broadcast_channel',
};
const middlewares = [createStateSyncMiddleware(config)];

predicate

A function to let you filter the actions as you wanted.

Note: Since version 3.0 the function receives the action itself and not only the action type.

type: Function

default: null

const config = {
    // All actions will be triggered in other tabs except 'TOGGLE_TODO'
    predicate: action => action.type !== 'TOGGLE_TODO',
};
const middlewares = [createStateSyncMiddleware(config)];

blacklist

A list of action types that you don't want to be triggered in other tabs.

type: ArrayOf(<String>)

default: []

const config = {
    // All actions will be triggered in other tabs except 'TOGGLE_TODO'
    blacklist: ['TOGGLE_TODO'],
};
const middlewares = [createStateSyncMiddleware(config)];

whitelist

Only actions in this list will be triggered in other tabs.

type: ArrayOf(<String>)

default: []

const config = {
    // Only 'TOGGLE_TODO' will be triggered in other tabs
    whitelist: ['TOGGLE_TODO'],
};
const middlewares = [createStateSyncMiddleware(config)];
Warning: You should only use one of the option to filter your actions. if you have all 3 options predicate, blacklist, and whitelist, only one will be effective and the priority is predicate > blacklist > whitelist.

broadcastChannelOption

Redux-state-sync is using BroadcastChannel to comunicate between tabs. broadcastChannelOption is the option passed to broadcastChannel when we creating the channel.

type: Object

default: null

const config = {
    // Only 'TOGGLE_TODO' will be triggered in other tabs
    whitelist: ['TOGGLE_TODO'],
    // enforce a type, oneOf['native', 'idb', 'localstorage', 'node']
    broadcastChannelOption: { type: 'localstorage' },
};
const middlewares = [createStateSyncMiddleware(config)];

Working with immutable.js

Please check the example_immutable folder.

prepareState

Prepare the initial state for sending to other tabs.

type: Function

default: state => state

const config = {
    // Map immutable object to js
    prepareState: state => state.toJS(),
};
const middlewares = [createStateSyncMiddleware(config)];

receiveState

Reconcile the incoming state from other tabs with the existing state.

type: Function

default: (prevState, nextState) => nextState

const config = {
    // Overwrite existing state with incoming state
    receiveState: (prevState, nextState) => nextState,
};
const middlewares = [createStateSyncMiddleware(config)];
import { combineReducers } from 'redux-immutable';
import { withReduxStateSync } from 'redux-state-sync';
const rootReducer = combineReducers({
    todos,
    visibilityFilter,
});

// Overwrite existing state with incoming state
export default withReduxStateSync(appReducer, (prevState, nextState) => nextState);

redux-state-sync's People

Contributors

aohua avatar cdtinney avatar dependabot[bot] avatar devmrin avatar ekilah avatar godested avatar jaulz avatar npaulo avatar olebedev avatar rowrowrowrow avatar vanuan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

redux-state-sync's Issues

Error when receiving initial state, action coming as undefined when using withReduxStateSync

Hi, Im trying to use this lib to maintain just a little bit of my application in sync, i was looking for somehing like this and o got here.
My aplication uses saga, and connected-router as middlewares, im not sure why im getting this error on the start of my application, i look in the examples, tryed not using initStateWithPrevTab, using all the methos to filter the action that i need (the action is not used or dispatched by a saga) and i cant make this work.

this is my config

const config = {
    // initiateWithState: true,
    channel: window.location.host,
    whitelist: [
        GRAVA_UNIDADE
    ],
}

this is my rootReducer

const rootReducer = history =>
  combineReducers({
    router: connectRouter(history),
    banner: bannerReducer,
    // ... All other reducers
    Intl
  });

export default withReduxStateSync(rootReducer);

this is my store creation

import rootSaga from "./rootSaga";
import rootReducer from "./rootReducer";
export const history = createBrowserHistory();
const sagaMiddleware = createSagaMiddleware();

const store = createStore(
  rootReducer(history),
  compose(
    applyMiddleware(routerMiddleware(history), sagaMiddleware, createStateSyncMiddleware(syncStateConfig)),
    //For working redux dev tools in chrome (https://github.com/zalmoxisus/redux-devtools-extension)
    window.__REDUX_DEVTOOLS_EXTENSION__
      ? window.__REDUX_DEVTOOLS_EXTENSION__({ trace: true })
      : function (f) {
        return f;
      }
  )
);

initStateWithPrevTab(store);
sagaMiddleware.run(rootSaga);

export default store;

Im not sure what I am doing wrong, if it is something with the compose (i already tryed without it), or the middlewares that im using.
If anyone can help me, I would be very grateful. Since already thank you very much.

EDIT
The problem is happening when I use withReduxStateSync on my reducers

export const createReduxStateSync = ({ prepareState }) => appReducer =>
  ((state, action) => {
    let initState = state;
    if (action.type === RECEIVE_INIT_STATE) {
      initState = prepareState(action.payload);
    }
    return appReducer(initState, action);
  });

Im still not sure why this is happening, i dont know if is something missing on my reducers, (all of them are with a standarf structure: initial state, immutable data, pure and passive). I thought that was something with using a connected router, some version, but im still with no progress on the solution

Broadcast channels not systematically closed ? Leak ?

I am running into a very strange bug, which I am having trouble reproducing consistently.
For background I am using the initStateWithPrevTab' helper to reinitialize state with previous tabs upon page load. My overall code looks like this (I have only one reducer, dataFetching`, and corresponding actions which only act on that reducer, which are whitelisted by redux-state-sync)

const persistConfig: PersistConfig<RootState> = {
  key: "root",
  storage,
  timeout: 0,
  stateReconciler: autoMergeLevel1,
  blacklist: ["dataFetching"],
};

const persistedReducer = persistReducer(
  persistConfig,
  withReduxStateSync(rootReducer) as any
);


const dataFetchingSync = createStateSyncMiddleware({
  whitelist: DATA_FETCHING_ACTION_TYPES,
});

export const store = createStore(
  persistedReducer,
  applyMiddleware(logger, dataFetchingSync)
);

initStateWithPrevTab(store);

Occasionally, I will run into issues when reloading a page where the redux state is immediately reset to its previous value, including the non-persisted reducer, which seemed strange.
Upon investigation, what happens is the following

  • Upon page load, a &_GET_INIT_STATE action is dispatched
  • Immediately after, a &_RECEIVE_INIT_STATE is received, with the same window uuid, and the previous value for the state, before reload
  • the state is thus overriden by that value

This means that there is no way, when that happens, to refresh the state in memory. Closing all tabs does not resolve it. Closing Chrome, however, does - and no more _RECEIVE_INIT_STATE action is received

I cannot reproduce this consistently and it happens randomly.
I cannot see a simple explanation for this, but I don't understand how, after reload, the state can still be available somewhere, and dispatched through the BroadcastChannel. I can't find a trace of that state anywhere in application storage so it must be in memory.
I am wondering if, by any chance, the BroadcastChannel created is not being closed upon reload, thus leading to a leak where, after reloading, it is still present with its original listener, and dispatches a state which has been kept in memory.
The BroadCastChannel API docs explicitly say that the channel should be closed when no longer used in order to avoid memory leaks. This does not seem to be the case in the source code. Should there perhaps be a listener for window.unload to close the channel ?

Use with ConfigureStore()

Hey guys I cannot get this to work with configureStore() which is Redux's recommended way of working with fresh applications. Can someone provide an example of getting createStateSyncMiddleware() into the configureStore() middleware array?

If I enable it, my ID's and Entities are nulled out, but if I disable it, they are back to working again.

Sync is slow / laggy on Indexed DB

Hi,

For some reason sync over IDB seems to be very slow. When using native / localStorage this issue disappears. I tested this on Chrome Version 84.0.4147.89 (Official Build) (64-bit) and Safari 13.1.1 (15609.2.9.1.2).

I thought maybe it's a problem in a way how the package uses IDB. However, if it's an issue in IDB itself, it would be a nice option to be able to choose automatically between native and localstorage (ban IDB).

Unable to initialize state for new tab from current state on original tab

I'll start off by saying redux-state-sync does a great job keeping the state sync across browser tabs. However I'm noticing that when I create a new tab I have to alter the state on the original tab for the state on the new tab(s) to properly match the original tab's state.

i.e. The state on the new tab does not actually match the original tab's state until the original tab's state is altered. After that initial alteration to the original tab's state, all the states match and stay sync'ed.

Does this plugin include a method for performing an initial copy of the original tab's state to the state of the new tab? I took a look through the code but didn't see any methods that might help.

Cheers
-W

immutable but only for parts of the state

Hello. I know I can use toJS() and fromJS() for the entire state, but on redux persist you can use "transformers" that will for ex convert internal parts of the state to from immutable, is there anyway to do that? thanks

Test cases fails with message "input is invalid type"

Test cases written in React testing library is failing with the following error
at Keccak.Object.<anonymous>.Keccak.update (node_modules/js-sha3/src/sha3.js:204:15) at node_modules/js-sha3/src/sha3.js:63:46 at getPaths (node_modules/broadcast-channel/src/methods/node.js:59:29) at Object.create (node_modules/broadcast-channel/src/methods/node.js:367:19) at _prepareChannel (node_modules/broadcast-channel/dist/lib/broadcast-channel.js:210:37) at new BroadcastChannel (node_modules/broadcast-channel/dist/lib/broadcast-channel.js:62:3) at createStateSyncMiddleware (node_modules/redux-state-sync/dist/syncState.js:131:19) at Object.<anonymous> (src/redux/store.ts:8:19) at Object.<anonymous> (src/__tests__/test-util.jsx:6:1) at Object.<anonymous> (src/components/resusablecomponents/popup/Popup.test.tsx:5:1)

image

Export critical variables

Unless you want to duplicate functionality and/or values when integrating with this package, some variables need to be exported so they can be reused. Namely the wuid and action types.

DOMException: Failed to execute 'postMessage' on 'BroadcastChannel'

I get this on my console after installing this package. The package seems to be working though. Any ideas?

Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'BroadcastChannel': function register(key) {
    _pStore.dispatch({
      type: _constants__WEBPACK_IMPORTED_MODULE_1__["REGISTER"]...<omitted>... } could not be cloned.
    at Object.postMessage (http://localhost:1962/main.js?486b1c6adc86eae78860:46721:19)
    at http://localhost:1962/main.js?486b1c6adc86eae78860:46027:36
postMessage @ native.js:25
(anonymous) @ index.js:163
Promise.then (async)
_post @ index.js:162
postMessage @ index.js:91
(anonymous) @ syncState.js:150
persist @ persistStore.js:114
persistStore @ persistStore.js:123
(anonymous) @ index.tsx:37
setInterval (async)
SocialAuthButtons.login @ index.tsx:33
SocialAuthButtons._this.showFacebook @ index.tsx:57
callback @ ext.js:8890
doTap @ ext.js:62143
(anonymous) @ ext.js:13989
fire @ ext.js:13858
doFireEvent @ ext.js:14283
doFireEvent @ ext.js:24961
prototype.doFireEvent @ ext.js:30073
fireEventArgs @ ext.js:14224
fireAction @ ext.js:14235
onTap @ ext.js:62130
onClick @ ext.js:62125
fire @ ext.js:13858
fire @ ext.js:19527
publish @ ext.js:19492
publishDelegatedDomEvent @ ext.js:19508
doDelegatedEvent @ ext.js:19546
onDelegatedEvent @ ext.js:19535

Typescript support

Hello there,

I have been using your middleware (which I think is amazing!) lately in a Typescript project and I found some minor issues. When I tried to import something from it, I got the following warning:

[ts]

Could not find a declaration file for module 'redux-state-sync' [...] implicitly has an 'any' type.
Try 'npm install @types/redux-state-sync' [...]

Then, I tried the following as suggested:

npm install @types/redux-state-sync

but I got this other error:

npm ERR! code E404
npm ERR! 404 Not Found: @types/redux-state-sync@latest

At this point, I realized there is no type declarations file (not in your project, nor in DefinitelyTyped). So I decided to create one and I would like to share it with you:

// Type definitions for redux-state-sync 2.0
// Project: https://github.com/AOHUA/redux-state-sync#readme
// Definitions by: AntonioMendez <https://github.com/AntonioMendez>
// TypeScript Version: 2.9.2

import { Store, Reducer, Middleware, Action, AnyAction } from 'redux'
import BroadcastChannel from 'broadcast-channel'

interface IStampedAction {
    $uuid: string
    $wuid: string
}

type StampedAction = AnyAction & IStampedAction
type MiddlewareConfig = {
    channel?: string,
    predicate?: Function | null
    blacklist?: Array<string>
    whitelist?: Array<string>
    broadcastChannelOption?: object | null
}
type MessageListenerConfig = {
    channel: BroadcastChannel
    dispatch: (action: AnyAction | StampedAction) => void
    allowed: (type?: string) => boolean
}

// S: state object type
export function initStateWithPrevTab<S> (
    store: Store<S>
): Store<S>

// S: state object type
export function withReduxStateSync<S> (
    appReducer: Reducer<S>
): (state: any, action: AnyAction) => Reducer<S>

export function createStateSyncMiddleware (config?: MiddlewareConfig): Middleware

// export for test
export function generateUuidForAction (action: AnyAction): StampedAction
// export for test
export function isActionAllowed (config: MiddlewareConfig): (type?: any) => boolean
// export for test
export function createMessageListener (config: MessageListenerConfig): void

I have been using this file in my own project and it works smoothly (no errors, no warnings), but I am not 100% sure the definitions are correct. Of course, feel free to make any change you wish and I hope this helps.

Thank you!.

freezed action error

Redux saga advises that using freezed objects for actions in middleware :

io-427945dd.js:178 Error: You can't put (a.k.a. dispatch from saga) frozen actions.
We have to define a special non-enumerable property on those actions for scheduling purposes.
Otherwise you wouldn't be able to communicate properly between sagas & other subscribers (action ordering would become far less predictable).
If you are using redux and you care about this behaviour (frozen actions),
then you might want to switch to freezing actions in a middleware rather than in action creator.
Example implementation:

const freezeActions = store => next => action => next(Object.freeze(action))

but if we freeze our action object , sync state will gives this error :

TypeError: Cannot add property $uuid, object is not extensible
    at generateUuidForAction (syncState.js:59)
    at eval (syncState.js:139)

Sagas

Is there any recommended way to use it with redux-saga? Currently, if actions are replicated to another tab the sagas will also fire and load/update data again. It would be great to sync only the state itself and not the actions. Or do I misunderstand anything here? Thanks anyway for the great library!

Old version of broadcast-channel dependency causing issues

Using additional broadcast-channel to sync other data beside redux causing issues with the version of the redux-state-sync when the version of broadcast-channel is different (latest).
Dependency added to redux-state-sync library is 3.1.0
Screen Shot 2021-12-15 at 17 32 50
0

Allow to omit certain fields from actions on broadcast

I have a promise function field inside my async actions, I would like those to be ignored when the action is cloned for broadcasting to avoid getting the "failed to clone" error and action not being broadcasted because of it

Periodic errors on iOS

I get bug reports from time to time:

TypeError
undefined is not an object (evaluating 't.id')
{snip} e(o)}})).then(function(t){return t.map(function(t){return t.id>e.lastCursorId&&(e.lastCursorId=t.id),t}).filter(function(t){return function( {snip}

https://github.com/pubkey/broadcast-channel/blob/master/src/methods/indexed-db.js#L227

Devices: iPhone, iPad.
Browser: Safari.
iOS versions: 12.1.1, 12.1.2, 12.2, 12.3.1.

This error appears in the broadcast-channel package, so I opened the issue in their repository (I do not know which package is causing the error, so I notified both).

How does initMessageListener work?

I have a quick question about how initMessageListener is supposed to behave.

Let’s say I have a main window (Window 1) and then I open a new window/tab (Window 2). Currently, I’m seeing (via redux-logger) that &_INIT_MESSAGE_LISTENER is triggering:

  1. When Window 1 initially loads.
  2. When Window 2 initially loads.
  3. Again on Window 1 after Window 2 initially loads.

Why am I seeing 3?

Is it necessary for all other windows/tabs to re-init their message listener when new windows or tabs are created? If not, should I be putting &_INIT_MESSAGE_LISTENER in the backlist array?

To rephrase the question, what should I expect to break if I put &_INIT_MESSAGE_LISTENER in the backlist array?

This sentence is in the README of the project:

initMessageListener is a new function to fix the bug that if the other tab not triggering any action on first load, it cannot receive any messages.

Does ^ this mean that the new tab cannot receive messages from pre-existing tabs, or all other pre-existing tabs cannot receive messages from the new tab? Or both?

Actions are duplicated in same window when we use IE

Problem

IE has a special behavior with storage event, when we call localStorage.setItem(...) the current window receives the storage event and dispatch again the same action.
More information in this link ie-localstorage-event-misfired

My solution idea:

  • Create a unique id for each window when the listener is created;
  • Add that window unique id to each action;
  • Inside the listener verify if window unique id is equals to the received action, if is equal discard that action;

[Issue] Prevent writing `LAST_ACTION` to localStorage unless predicate is true.

On the codebase that I am working on, we are dealing with very large state objects that exceed the amount allowed by localStorage. We had to override actionStorageMiddleware with our own version that prevented localStorage.setItem('LAST_ACTION', .. from happening unless action.type was allowed. So my question is, does every action have to be stored even if you're only allowing a few of them? Let me know if this tweak falls within the purpose of redux-state-sync and we can continue this conversation.

Parent Tab Stops Working

After opening a new tab the parent tab will randomly stop detecting state changes that are made on the parent tab, but it will detect state changes made on the child tab.

Don't have time to create bare-bones app to replicate. We're using useEffect(...) hooks.

TypeError: inputState.withMutations is not a function

Hi,

Thanks for this amazing library.
But I have been getting this issue and unable to solve it.

TypeError: inputState.withMutations is not a function
src/combineReducers.js:22
  19 |   }
  20 | }
  21 | 
> 22 | return inputState
  23 | ^ .withMutations((temporaryState) => {
  24 |     reducerKeys.forEach((reducerName) => {
  25 |       const reducer = reducers[reducerName];
    

I followed the docs and ended with the following configs:

// store.ts
import createSagaMiddleware from 'redux-saga';
import { createStore, compose, applyMiddleware } from 'redux';
import { createStateSyncMiddleware, initStateWithPrevTab } from 'redux-state-sync';

const sagaMiddleware = createSagaMiddleware();
const store = createStore(
    rootReducer,
    composeEnhancers(applyMiddleware(
        sagaMiddleware, 
        createStateSyncMiddleware({}) // <-- Middleware added
    ))
);

initStateWithPrevTab(store); // <-- Initialised
sagaMiddleware.run(rootSaga);

export default store;
// reducer.ts
import { combineReducers } from 'redux-immutable';
import { withReduxStateSync } from 'redux-state-sync';

const rootReducer = combineReducers({
    authentication,
    configuration,
    user,
});

export default withReduxStateSync(rootReducer); // <-- wrapped

Any help or suggestions towards solution would be greatly appreciated. Thanks!

I am using the following versions of libraries:

immutable: 4.0.0-rc.12
react: 16.12.0
react-dom: 16.12.0
react-redux: 7.1.3
redux-immutable: 4.0.0
redux-saga: 1.1.3
redux-state-sync: 3.0.0

Update dock

How to integrate with Immutable.js redux store.

Locally syncing the results of Async actions.. how does this work here?

Not sure how this works as I have not looked into details yet. The documentation says it will "...dispatch exactly the same actions dispatched in other tabs.."

Does this mean if one of my tab dispatches an async action (say fetching user data from backend).. the same action will be dispatched in all other tabs (requiring multiple api fetch calls to backend from all open tabs to receive the same user data)?? Would it not be more efficient to dispatch actions from only the source tab and on state change in that tab, sync the resulting state in other tabs ??

Error after adding the package @types/redux-state-sync

I use the lib redux-state-sync without problem - everything works fine. But when i add the packages with types @types/redux-state-sync i get an error:

index.js:41662 Uncaught TypeError: _broadcastChannel.BroadcastChannel is not a constructor

What could be the reason for this error?

Documentation questions...

This package looks pretty slick. A couple questions about how to use it.

  1. Does redux-state-sync use localStorage? If so, does this mean it will persist between browser sessions? (That could be helpful.)

  2. If it uses localStorage, is it subject to the same ~5mbyte limit? What happens if the app runs over that limit?

  3. I believe the way to test the example code is cd example; yarn install; yarn start That opens a window that has a to-do app. Opening another tab to the same URL and entering a to-do also shows it in the original window. Correct?

  4. The 'init new tab with current state' feature includes two code snippets. I am unsure how to incorporate them into the example project to verify its operation.

Thanks for any help!

Lighthouse audit fails with `no-unload-listeners`

Is your feature request related to a problem? Please describe.
Lighthouse errors due to no-unload-listeners.

Describe the solution you'd like
Based off of https://web.dev/bfcache/#never-use-the-unload-event, pagehide event is the recommended event since it's triggered in all cases where unload is triggered.

Additional context

Never add an unload event listener! Use the pagehide event instead. Adding an unload event listener will make your site slower in Firefox, and the code won't even run most of the time in Chrome and Safari.

Trying to keep tabs in sync with each other

Hi there,

I am trying to create an example of holding a react app inside of another one.
E.g. Parent app holds general parent state info, and child app can access that state and do things with it.
I have got it working sending actions back to the parent app, and receiving the initial state from the parent app,but it doesn't seem to to sync up with the changes to the parent state app.

For example, I have an action in the parent that updates the state. It handles as expected in the parent when I call from the child, however the child does not update its state with the changes. Is this possible with this library?

I have set up my parent as follows:

const config = {
  predicate: (action) => action
};`

export default function configureStore() {
  const store = createStore(
    rootReducer,
    {},
    applyMiddleware(createStateSyncMiddleware(config))
  );
  return store;
}

and used initMessageListener(store);

for the child I did essentially the same, but used initStateWithPrevTab(store); and withReduxStateSync around the rootReducer.
Can anyone advise on whether what I am wanting to do is even possible?

Thanks in advance!

TypeScript typing wrong in returned store after wrapping reducers with withReduxStateSync()

const store = createStore(reducers, defaultState, enhancers);

store is Store<CombinedState<ApplicationState>, AnyAction> & {dispatch: unknown}

Once I wrap reducers in withReduxStateSync(), store is Store<Reducer<any, AnyAction>, AnyAction> & {dispatch: unknown}.

If I totally erase the typings it works:

const store = (createStore(
  reducers,
  defaultState,
  enhancers
) as unknown) as Store<CombinedState<ApplicationState>, AnyAction> & {
  dispatch: unknown;
};

Feature request: don't sync between different Firefox containers

Firefox has a system called multi-account containers that allow for logical separation between tabs (e.g. being logged into the same site with different accounts in different tabs). I often use this during development, to test interactions between different users.

I would only like redux state to be synced between tabs that are opened in the same container.

I think this could be implemented relatively easily using a non-http-only session-age marker cookie. Broadcast events would include the cookie, and tabs that have a different marker cookie (due to a different cookie jar because of being in an isolated container) would discard those events. There may be another way to implement it, but that's what came to mind.

Whst are your thoughts on this? I figure this is a low priority for you, so if you're OK with the feature I may take a stab at implementing it myself and opening a PR.

edit: looks like there might be a way to directly detect the container: https://discourse.mozilla.org/t/firefox-container-detection-using-cookiestoreid/95050/4

Security Hook In

Hi,

This library looks good, but do you offer a way to easily hook into when you receive a message? I envisage something like:

const handler = msg => {
     // default is empty fn returning true, we can configure
     if ( thisLibrary.callbacks.validateMessage( msg ) ) {
         ... do redux magic
    }
}

channel.addEventListener('message', handler);

The reason is because I want to pass with the message a security token. The token will be the same across tabs for my user, and by checking this message token exactly matches the current users token stored elsewhere, I can be certain the message is not from a malicious source.

redux persist "and" sync

hello. I can use redux-state-sync but using combineReducers from redux, to use the one from redux-immutable then I can't use persist.
if I set the config with state => state.toJS() I don't have where to set the Immutable.fromJS() if I don't use withReduxStateSync.

how can I use both packages? thanks

Init tab with partial state

Is there currently a way to initialize the state of a new tab with only a part of the entire state? I saw that this was a feature which was looked at in issue #9, but cannot find where in the documentation it shows how this can be done. If the feature is not yet added what would be a possible workaround, or even would it be possible to add? I would be available to help if needed.

Memory Leak on Azure

Hello,

I am using the latest version of redux-state-sync. As you can see in the graph below, the usage of RAM is constantly increasing.

Node version is 14.16.1

image

Store configuration;

import { createStore, applyMiddleware, compose } from 'redux';

import {
  createStateSyncMiddleware,
  withReduxStateSync,
  initStateWithPrevTab
} from 'redux-state-sync';

import Router from 'next/router';

import reducer from './reducer';

import middlewares from './middleware';
import { actionType } from './actions';

const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;

const stateSyncConfig = {
  channel: 'my_project_redux_state_sync',
  whitelist: [
    actionType.StateClear,
    'StateMerge',
    'StatesMerge',
    'PrependToKey',
    'AddToKey',
    'RemoveFromKey'
  ]
};

export const initStore =
  (initialState = {
    apiUrl: process.env.API_URL || 'http://localhost:5000',
    auctionHubState: 'Initializing'
  }) => {
    console.log(JSON.stringify(process.env));
    const storeMiddlewares = [createStateSyncMiddleware(stateSyncConfig)];

    if (process.browser && Router.route === '/home') {

      storeMiddlewares.push(...middlewares);
    }

    const store =
      createStore(
        withReduxStateSync(reducer),
        initialState,
        composeEnhancers(
          applyMiddleware(...storeMiddlewares)
        )
      );

    if (process.browser && Router.route !== '/operator') {

      initStateWithPrevTab(store);
    }

    return store;
  };

How to disable initial application loading if synced

Hi,
Great library!

However am stuck on something. My app does an initial load of various pieces of data from the server in a useEffect block and populates this into the redux store.
When I open new tabs, for performance reasons I want this to instead come in synced from the other already open tabs (using this library), in which case the server side load does not need to be made.

The problem I am facing however is the redux state sync happens asynchronously, so if I simply check some loaded property in the store when the components mount, it will be false at this point because the redux state sync happens afterwards. Is there a way to do this somehow?

Thanks

Feature Request: modify actions

Hi, thanks for the great package!

Would you consider adding a function to the configuration for modifying the actions before they are sent?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.