Description
openedon Sep 18, 2024
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?