Skip to content

Commit 7c50420

Browse files
authored
Merge pull request marmelab#11096 from marmelab/store-list-items
Add `Store.listItems` method
2 parents efa8456 + 515a4a3 commit 7c50420

File tree

7 files changed

+138
-2
lines changed

7 files changed

+138
-2
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,33 @@ describe('localStorageStore', () => {
6868
expect(store.getItem('foo')).toEqual(undefined); //deleted during setup
6969
});
7070
});
71+
describe('listItems', () => {
72+
it('should return an object containing all items with the given prefix', () => {
73+
const store = localStorageStore();
74+
store.setup();
75+
store.setItem('foo', 'bar');
76+
store.setItem('foo2', 'bar2');
77+
store.setItem('foo3', 'bar3');
78+
store.setItem('hello', 'world');
79+
expect(store.listItems('foo')).toEqual({
80+
foo: 'bar',
81+
foo2: 'bar2',
82+
foo3: 'bar3',
83+
});
84+
});
85+
it('should return an object containing all items when no prefix is provided', () => {
86+
const store = localStorageStore();
87+
store.setup();
88+
store.setItem('foo', 'bar');
89+
store.setItem('foo2', 'bar2');
90+
store.setItem('foo3', 'bar3');
91+
store.setItem('hello', 'world');
92+
expect(store.listItems()).toEqual({
93+
foo: 'bar',
94+
foo2: 'bar2',
95+
foo3: 'bar3',
96+
hello: 'world',
97+
});
98+
});
99+
});
71100
});

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('localStorageStore', () => {
2525

2626
it('should update all components using the same store item on update', () => {
2727
const UpdateStore = () => {
28-
const [, setValue] = useStore('foo.bar');
28+
const [, setValue] = useStore<string>('foo.bar');
2929
return <button onClick={() => setValue('world')}>update</button>;
3030
};
3131
render(
@@ -43,7 +43,7 @@ describe('localStorageStore', () => {
4343

4444
it('should not update components using other store key on update', () => {
4545
const UpdateStore = () => {
46-
const [, setValue] = useStore('other.key');
46+
const [, setValue] = useStore<string>('other.key');
4747
return <button onClick={() => setValue('world')}>update</button>;
4848
};
4949
render(

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as React from 'react';
33
import { localStorageStore } from './localStorageStore';
44
import { StoreContextProvider } from './StoreContextProvider';
55
import { useStore } from './useStore';
6+
import { useStoreContext } from './useStoreContext';
67

78
export default {
89
title: 'ra-core/store/localStorage',
@@ -54,3 +55,46 @@ export const Basic = () => {
5455
</StoreContextProvider>
5556
);
5657
};
58+
59+
const StoreList = () => {
60+
const store = useStoreContext();
61+
const [items, setItems] = React.useState({});
62+
return (
63+
<>
64+
<div style={{ display: 'flex', gap: '8px' }}>
65+
<button
66+
type="button"
67+
onClick={() => setItems(store.listItems())}
68+
>
69+
Get all items
70+
</button>
71+
<button
72+
type="button"
73+
onClick={() => setItems(store.listItems('foo.'))}
74+
>
75+
Get items with prefix
76+
</button>
77+
</div>
78+
<pre>{JSON.stringify(items, null, 2)}</pre>
79+
</>
80+
);
81+
};
82+
export const ListItems = () => {
83+
return (
84+
<StoreContextProvider value={localStorageStore()}>
85+
<h1>Values</h1>
86+
<dl>
87+
<StoreReader name="foo.bar" />
88+
<StoreReader name="foo.baz" />
89+
<StoreReader name="bar.baz" />
90+
</dl>
91+
<h1>Setter</h1>
92+
<dl>
93+
<StoreSetter name="foo.bar" />
94+
<StoreSetter name="foo.baz" />
95+
<StoreSetter name="bar.baz" />
96+
</dl>
97+
<StoreList />
98+
</StoreContextProvider>
99+
);
100+
};

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,24 @@ export const localStorageStore = (
148148
delete subscriptions[id];
149149
};
150150
},
151+
listItems: (keyPrefix?: string) => {
152+
const storage = getStorage();
153+
const fullPrefix = `${prefix}.${keyPrefix != null ? keyPrefix : ''}`;
154+
155+
return Object.entries(storage).reduce(
156+
(acc, [key, value]) => {
157+
if (
158+
// version is considered internal
159+
key !== `${prefix}.version` &&
160+
key.startsWith(fullPrefix)
161+
) {
162+
acc[key.substring(prefix.length + 1)] = tryParse(value);
163+
}
164+
return acc;
165+
},
166+
{} as Record<string, unknown>
167+
);
168+
},
151169
};
152170
};
153171

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,35 @@ describe('memoryStore', () => {
9393

9494
await screen.findByText('John');
9595
});
96+
describe('listItems', () => {
97+
it('should return an object containing all items with the given prefix', () => {
98+
const store = memoryStore({
99+
foo: 'bar',
100+
foo2: 'bar2',
101+
foo3: 'bar3',
102+
hello: 'world',
103+
});
104+
store.setup();
105+
expect(store.listItems('foo')).toEqual({
106+
foo: 'bar',
107+
foo2: 'bar2',
108+
foo3: 'bar3',
109+
});
110+
});
111+
it('should return an object containing all items when no prefix is provided', () => {
112+
const store = memoryStore({
113+
foo: 'bar',
114+
foo2: 'bar2',
115+
foo3: 'bar3',
116+
hello: 'world',
117+
});
118+
store.setup();
119+
expect(store.listItems()).toEqual({
120+
foo: 'bar',
121+
foo2: 'bar2',
122+
foo3: 'bar3',
123+
hello: 'world',
124+
});
125+
});
126+
});
96127
});

packages/ra-core/src/store/memoryStore.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,18 @@ export const memoryStore = (
107107
delete subscriptions[id];
108108
};
109109
},
110+
listItems: (keyPrefix?: string) => {
111+
return Array.from(storage.entries()).reduce(
112+
(acc, [key, value]) => {
113+
if (keyPrefix != null && !key.startsWith(keyPrefix)) {
114+
return acc;
115+
}
116+
117+
acc[key] = value;
118+
return acc;
119+
},
120+
{} as Record<string, unknown>
121+
);
122+
},
110123
};
111124
};

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ export interface Store {
77
removeItems: (keyPrefix: string) => void;
88
reset: () => void;
99
subscribe: (key: string, callback: (value: any) => void) => () => void;
10+
listItems?: (keyPrefix?: string) => Record<string, unknown>;
1011
}

0 commit comments

Comments
 (0)