Skip to content

Commit e7ce4ab

Browse files
authored
Merge pull request #8031 from marmelab/trello782-add-key-localstorage
[Fix] Add localStorage application key
2 parents 73e7588 + fb28a11 commit e7ce4ab

File tree

3 files changed

+43
-16
lines changed

3 files changed

+43
-16
lines changed

docs/Store.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ To create a Store with a different version number, call the `localStorageStore()
120120
```jsx
121121
import { Admin, Resource, localStorageStore } from 'react-admin';
122122

123-
const STORE_VERSION = 2
123+
const STORE_VERSION = 2;
124124

125125
const App = () => (
126126
<Admin dataProvider={dataProvider} store={localStorageStore(STORE_VERSION)}>
@@ -131,6 +131,23 @@ const App = () => (
131131

132132
Increase the version number each time you push code that isn't compatible with the stored values.
133133

134+
## Share/separate Store data between same domain instances
135+
136+
If you are running multiple instances of react-admin on the same domain, you can distinguish their stored objects by defining different application keys. By default, the application key is empty to allow configuration sharing between instances.
137+
138+
```jsx
139+
import { Admin, Resource, localStorageStore } from 'react-admin';
140+
141+
const APP_KEY = 'blog';
142+
143+
const App = () => (
144+
<Admin dataProvider={dataProvider} store={localStorageStore(undefined, APP_KEY)}>
145+
<Resource name="posts" />
146+
</Admin>
147+
);
148+
```
149+
150+
134151
## Transient Store
135152

136153
If you don't want the store to be persisted between sessions, you can override the default `<Admin store>` component:

packages/ra-core/src/store/localStorageStore.spec.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,15 @@ describe('localStorageStore', () => {
5858
fireEvent.click(screen.getByText('update'));
5959
screen.getByText('hello');
6060
});
61+
62+
it('should keep two version of the same key on two stores differing bey their appKey', () => {
63+
const store1 = localStorageStore(undefined, 'app1');
64+
const store2 = localStorageStore(undefined, 'app2');
65+
66+
store1.setItem('foo', 'app1');
67+
store2.setItem('foo', 'app2');
68+
69+
expect(store1.getItem('foo')).toBe('app1');
70+
expect(store2.getItem('foo')).toBe('app2');
71+
});
6172
});

packages/ra-core/src/store/localStorageStore.ts

+14-15
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ type Subscription = {
66
};
77

88
const RA_STORE = 'RaStore';
9-
const prefixLength = RA_STORE.length;
109

1110
// localStorage isn't available in incognito mode. We need to detect it
1211
const testLocalStorage = () => {
@@ -39,7 +38,12 @@ let localStorageAvailable = testLocalStorage();
3938
* </Admin>
4039
* );
4140
*/
42-
export const localStorageStore = (version: string = '1'): Store => {
41+
export const localStorageStore = (
42+
version: string = '1',
43+
appKey: string = ''
44+
): Store => {
45+
const prefix = `${RA_STORE}${appKey}`;
46+
const prefixLength = prefix.length;
4347
const subscriptions: { [key: string]: Subscription } = {};
4448
const publish = (key: string, value: any) => {
4549
Object.keys(subscriptions).forEach(id => {
@@ -53,7 +57,7 @@ export const localStorageStore = (version: string = '1'): Store => {
5357
// Whenever the local storage changes in another document, look for matching subscribers.
5458
// This allows to synchronize state across tabs
5559
const onLocalStorageChange = (event: StorageEvent): void => {
56-
if (event.key?.substring(0, prefixLength) !== RA_STORE) {
60+
if (event.key?.substring(0, prefixLength) !== prefix) {
5761
return;
5862
}
5963
const key = event.key.substring(prefixLength + 1);
@@ -77,13 +81,11 @@ export const localStorageStore = (version: string = '1'): Store => {
7781
return {
7882
setup: () => {
7983
if (localStorageAvailable) {
80-
const storedVersion = getStorage().getItem(
81-
`${RA_STORE}.version`
82-
);
84+
const storedVersion = getStorage().getItem(`${prefix}.version`);
8385
if (storedVersion && storedVersion !== version) {
8486
getStorage().clear();
8587
}
86-
getStorage().setItem(`${RA_STORE}.version`, version);
88+
getStorage().setItem(`${prefix}.version`, version);
8789
window.addEventListener('storage', onLocalStorageChange);
8890
}
8991
},
@@ -93,7 +95,7 @@ export const localStorageStore = (version: string = '1'): Store => {
9395
}
9496
},
9597
getItem<T = any>(key: string, defaultValue?: T): T {
96-
const valueFromStorage = getStorage().getItem(`${RA_STORE}.${key}`);
98+
const valueFromStorage = getStorage().getItem(`${prefix}.${key}`);
9799

98100
// eslint-disable-next-line eqeqeq
99101
return valueFromStorage == null
@@ -102,23 +104,20 @@ export const localStorageStore = (version: string = '1'): Store => {
102104
},
103105
setItem<T = any>(key: string, value: T): void {
104106
if (value === undefined) {
105-
getStorage().removeItem(`${RA_STORE}.${key}`);
107+
getStorage().removeItem(`${prefix}.${key}`);
106108
} else {
107-
getStorage().setItem(
108-
`${RA_STORE}.${key}`,
109-
JSON.stringify(value)
110-
);
109+
getStorage().setItem(`${prefix}.${key}`, JSON.stringify(value));
111110
}
112111
publish(key, value);
113112
},
114113
removeItem(key: string): void {
115-
getStorage().removeItem(`${RA_STORE}.${key}`);
114+
getStorage().removeItem(`${prefix}.${key}`);
116115
publish(key, undefined);
117116
},
118117
reset(): void {
119118
const storage = getStorage();
120119
for (let i = 0; i < storage.length; i++) {
121-
if (storage.key(i)?.substring(0, prefixLength) === RA_STORE) {
120+
if (storage.key(i)?.substring(0, prefixLength) === prefix) {
122121
const key = storage.key(i)?.substring(prefixLength + 1);
123122
if (!key || !storage.key(i)) return;
124123
// @ts-ignore

0 commit comments

Comments
 (0)