Skip to content

Instantly share code, notes, and snippets.

@lindskogen
Created July 10, 2025 12:15
Show Gist options
  • Save lindskogen/ba21f1707d0c0cf90055ae4e24b8eeba to your computer and use it in GitHub Desktop.
Save lindskogen/ba21f1707d0c0cf90055ae4e24b8eeba to your computer and use it in GitHub Desktop.
offlineRTK
import { isAnyOf, isFulfilled, createAction, createReducer, Middleware, UnknownAction } from '@reduxjs/toolkit';
interface PendingAction {
meta: {
requestId: string;
requestStatus: 'pending';
startedTimeStamp: number;
};
type: string;
payload: unknown;
}
interface OfflineQueueState {
queue: string[];
meta: Record<string, PendingAction>;
}
const initialState: OfflineQueueState = {
queue: [],
meta: {}
};
export const pushOfflineQueueAction = createAction<PendingAction>('offline/queue/push');
export const offlineQueueReducer = createReducer(initialState, builder => {
builder
.addCase(pushOfflineQueueAction, (state, action) => {
const innerAction = action.payload;
state.queue.push(innerAction.meta.requestId);
state.meta[innerAction.meta.requestId] = innerAction;
return state;
})
.addMatcher(isFulfilled, (state, action) => {
if (action.meta.requestId in state.meta) {
delete state.meta[action.meta.requestId];
}
});
});
const isPendingApiAction = isAnyOf(api.endpoints.updateUser.matchPending);
export const offlineQueueMiddleware: Middleware =
({ getState, dispatch }) =>
next =>
action => {
if (isPendingApiAction(action)) {
const isOffline = selectIsOffline(getState());
if (isOffline) {
// we're offline, abort and add to queue!
dispatch(pushOfflineQueueAction(action));
// Attempt 1:
// Don't call next()
// still runs the thunk, just skips dispatching the pending action
// Attempt 2:
// Dispatch action with same structure as when calling action.abort()
// still runs the thunk, no change.
// Attempt 3:
// Get the thunk with `getRunningMutationThunk`:
//
// const thunk = dispatch(
// api.util.getRunningMutationThunk(action.meta.arg.endpointName as any, action.meta.requestId)
// );
// thunk.abort();
//
// It doesn't find it? I guess it's not started yet?
// Attempt 4:
// Throw error
// the thunk gets cancelled!
// throw new Error('Offline error');
} else {
console.log('is online, let the action through!', action);
return next(action);
}
} else {
return next(action);
}
};
export const addOfflineQueueListener = (startListening: AppStartListening) => {
startListening({
actionCreator: online,
effect: async (_onlineAction, { getState, dispatch }) => {
const { queue, meta } = getState().offlineQueue;
for (const requestId of queue) {
// but this does not actually re-start the thunk - it's just the pending action
await dispatch(meta[requestId]);
}
}
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment