Skip to content

Commit c06612f

Browse files
fix: preserve redux cache when updating url state
1 parent 38b6856 commit c06612f

2 files changed

Lines changed: 77 additions & 1 deletion

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {configureStore, createNextState} from '@reduxjs/toolkit';
2+
import {createMemoryHistory} from 'history';
3+
import {listenForHistoryChange} from 'redux-location-state';
4+
5+
import getLocationMiddleware from './state-url-mapping';
6+
7+
jest.mock('./reducers/heatmap', () => ({
8+
initialState: {sort: false, heatmap: false, currentMetric: 'cpu'},
9+
}));
10+
jest.mock('./reducers/tenant/tenant', () => ({initialState: {metricsTab: 'overview'}}));
11+
12+
const identity = 'clusterName=test&backend=https%3A%2F%2Fbackend.test&schema=%2FRoot%2Ftable';
13+
14+
function setup() {
15+
const initial = createNextState(
16+
{
17+
api: {queries: {nodes: {data: [{NodeId: 1, PDisks: [{PDiskId: 1}]}]}}},
18+
tenant: {queryTab: 'query', metricsTab: 'overview', unrelated: {value: 42}},
19+
heatmap: {sort: false, heatmap: false, currentMetric: 'cpu'},
20+
},
21+
() => {},
22+
);
23+
const history = createMemoryHistory({initialEntries: [`/cluster/nodes?${identity}`]});
24+
const reducer = (state = initial, action: {type: string}) =>
25+
action.type === 'test/select-plan'
26+
? {...state, tenant: {...state.tenant, queryTab: 'plan'}}
27+
: state;
28+
const {locationMiddleware, reducersWithLocation} = getLocationMiddleware(history, reducer);
29+
const store = configureStore({
30+
reducer: reducersWithLocation,
31+
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(locationMiddleware),
32+
});
33+
listenForHistoryChange(store, history);
34+
return {initial, history, store};
35+
}
36+
37+
test('navigation without mapped URL changes preserves the entire cached state', () => {
38+
const {initial, history, store} = setup();
39+
const state = store.getState();
40+
41+
history.push(`/cluster/network?${identity}`);
42+
43+
expect(store.getState()).toBe(state);
44+
expect(store.getState().api).toBe(initial.api);
45+
});
46+
47+
test('URL changes and browser history update mapped fields without copying API data', () => {
48+
const {initial, history, store} = setup();
49+
history.push(`/database?${identity}&queryTab=results&sort=true&metricsTab=storage`);
50+
51+
expect(store.getState().tenant.queryTab).toBe('results');
52+
expect(store.getState().tenant.metricsTab).toBe('storage');
53+
expect(store.getState().heatmap.sort).toBe(true);
54+
expect(store.getState().tenant.unrelated).toBe(initial.tenant.unrelated);
55+
expect(store.getState().api).toBe(initial.api);
56+
57+
store.dispatch({type: 'test/select-plan'});
58+
const query = new URLSearchParams(history.location.search);
59+
expect(query.get('queryTab')).toBe('plan');
60+
expect(query.get('clusterName')).toBe('test');
61+
expect(query.get('backend')).toBe('https://backend.test');
62+
expect(query.get('schema')).toBe('/Root/table');
63+
64+
history.push(`/database?${identity}&queryTab=query`);
65+
history.goBack();
66+
expect(store.getState().tenant.queryTab).toBe('plan');
67+
history.goForward();
68+
expect(store.getState().tenant.queryTab).toBe('query');
69+
expect(store.getState().api).toBe(initial.api);
70+
expect(initial.tenant.queryTab).toBe('query');
71+
expect(initial.heatmap.sort).toBe(false);
72+
});

‎src/store/state-url-mapping.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {Action, Reducer, UnknownAction} from '@reduxjs/toolkit';
2+
import {createNextState} from '@reduxjs/toolkit';
23
import type {History, Location} from 'history';
34
import each from 'lodash/each';
45
import keys from 'lodash/keys';
@@ -75,7 +76,10 @@ export const paramSetup = {
7576
} as const;
7677

7778
function mergeLocationToState<S>(state: S, location: Pick<LocationWithQuery, 'query'>): S {
78-
return merge({}, state, location.query);
79+
// Preserve cached API data when updating only URL-backed state.
80+
return createNextState(state, (draft) => {
81+
merge(draft, location.query);
82+
});
7983
}
8084

8185
function restoreUnknownParams(location: Location, prevLocation: Location) {

0 commit comments

Comments
 (0)