Skip to content

Using RTK-Query with my store doesn't relay the fulfilled result back to the state #4627

Closed

Description

Hi there, I am having a hard time understanding the issue with RTK-Query.

I am using it in the context of a web extension. It was working properly when my store was simple enough and data wasn't need to be shared between different contexts of the extension, but due to requirements in , we had to place the store inside a Service Worker.

We use a Broadcast Channel to communicate store updates so all states are hydrated and remain up-to-date. However, since we've made the changes, the RTK-Queries have stopped working, I can see the query going out and response being returned, but fail to save the data, maybe some here can help?

Here's my store.ts

import {
  combineReducers,
  configureStore,
  type Middleware,
  type MiddlewareAPI,
  type UnknownAction,
} from '@reduxjs/toolkit';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useDispatch, useSelector, useStore } from 'react-redux';
import { localStorage } from 'redux-persist-webextension-storage';
import { createSelector } from 'reselect';

import {
  FLUSH,
  PAUSE,
  PERSIST,
  persistReducer,
  persistStore,
  PURGE,
  REGISTER,
  REHYDRATE,
  RESYNC,
} from '@plasmohq/redux-persist';

import { generateId } from '~shared/wallet/utils/id';
import { apiMiddlewares, apiReducers } from '~store/api/combinedApi';
import { StoreKeys } from '~store/store.interface';

import { createActivitySlice } from './slices/activity/activity.slice';


const persistConfig = {
  key: 'root',
  version: 1,
  storage: localStorage,
};

const combinedReducers = combineReducers({
  ...apiReducers,
  ..moreReducers,
});

// IMPORTANT: without it, the RootState type is wrong.
// Until persistReducer is fixed, we need to use this mock store to get the types
const mockStore = configureStore({
  reducer: combinedReducers,
});

export type AppStore = typeof mockStore;
export type AppDispatch = typeof mockStore.dispatch;
export type RootState = ReturnType<typeof mockStore.getState>;

export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppStore = useStore.withTypes<AppStore>();
export const createAppSelector = createSelector.withTypes<RootState>();

// @ts-expect-error Types of property storage are incompatible. Because localStorage don't have getAllKeys method.
const persistedReducer = persistReducer(persistConfig, combinedReducers);
const storeId = generateId();
// channel to be used to communicate about store updates on different stores
const broadcastChannel = new BroadcastChannel('redux-mv3');
const createSyncMiddleware: Middleware =
  (_store: MiddlewareAPI) =>
  (next: (action: UnknownAction) => UnknownAction) =>
  async (action: UnknownAction) => {
    if (action.type === 'persist/REHYDRATE') {
      const getState = async (): Promise<RootState | undefined> => {
        // broadcast a message to get the state which may be more up to date than the persisted state
        broadcastChannel.postMessage({
          type: 'getInitState',
        });
        return new Promise<any>((resolve) => {
          // listener to receieve the state and resolve the promise
          const handleStateResponse = (event: MessageEvent): void => {
            if (event.data.type === 'currentState') {
              broadcastChannel.removeEventListener(
                'message',
                handleStateResponse,
              );
              resolve(event.data.state); // Resolve with the received state
            }
          };

          // Listen for the currentState response
          broadcastChannel.addEventListener('message', handleStateResponse);
          // if there is no response in 100ms then return undefined and use persisted state
          setTimeout(() => {
            resolve(undefined);
          }, 100);
        });
      };
      const state = await getState();
      if (state) action.payload = state;
      return next(action);
    } else if (action.type.includes('persist')) {
      return next(action);
    } else {
      // if the action has a storeId it means it was already broadcasted to other stores
      if (action?.storeId) return next(action);
      // Only broadcast actions that aren't already broadcasted
      const newAction = { ...action, storeId };
      broadcastChannel.postMessage(newAction);

      // Proceed with the next middleware or reducer
      return next(action);
    }
  };

export const store = configureStore<RootState>({
  reducer: persistedReducer,
  // @ts-expect-error persistedReducer loses information about number of necessary middleware for API
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [
          FLUSH,
          REHYDRATE,
          PAUSE,
          PERSIST,
          PURGE,
          REGISTER,
          RESYNC,
        ],
      },
      thunk: true,
    }).concat([...apiMiddlewares, createSyncMiddleware]),
});

broadcastChannel.onmessage = (event) => {
  if (event.data?.storeId && event.data.storeId !== storeId) {
    // here you have received a message contains a dispatch from another store
    // Dispatch the received action to the current store
    store.dispatch(event.data);
  } else if (event.data.type === 'getInitState') {
    // here you have received a message asking for the current state for another store
    const currentState = store.getState();
    broadcastChannel.postMessage({
      type: 'currentState',
      state: currentState,
      storeId,
    });
  }
};

export const persistor = persistStore(store);

The idea is to keep multiple stores hydrated via Broadcast Channels

One thing I have noticed is that in the reduxStore, when I check the API's slice, it has as middlewareRegistered: conflict. Any idea where I could be going wrong?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions