Skip to content

Commit e8b1cc4

Browse files
committed
Move the items initialization post setup in the store implementation
1 parent d717d87 commit e8b1cc4

File tree

6 files changed

+109
-102
lines changed

6 files changed

+109
-102
lines changed

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

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,21 @@
11
import * as React from 'react';
2-
import { useEffect, useMemo, useRef } from 'react';
2+
import { useEffect } from 'react';
33
import { StoreContext } from './StoreContext';
44
import { Store } from './types';
55

66
export const StoreContextProvider = ({
77
value: Store,
88
children,
99
}: StoreContextProviderProps) => {
10-
const initialized = useRef(false);
11-
const itemsToSetAfterInitialization = useRef<Record<string, any>>([]);
12-
1310
useEffect(() => {
1411
Store.setup();
15-
// Because children might call setItem before the store is initialized,
16-
// we store those calls parameters and apply them once the store is ready
17-
if (itemsToSetAfterInitialization.current.length > 0) {
18-
const items = Object.values(itemsToSetAfterInitialization.current);
19-
for (const [key, value] of items) {
20-
Store.setItem(key, value);
21-
}
22-
itemsToSetAfterInitialization.current = {};
23-
}
24-
initialized.current = true;
2512
return () => {
2613
Store.teardown();
27-
initialized.current = false;
2814
};
2915
}, [Store]);
3016

31-
const wrapper = useMemo<Store>(() => {
32-
return {
33-
...Store,
34-
setItem: (key, value) => {
35-
// Because children might call setItem before the store is initialized,
36-
// we store those calls parameters and apply them once the store is ready
37-
if (!initialized.current) {
38-
itemsToSetAfterInitialization.current.push([key, value]);
39-
return;
40-
}
41-
Store.setItem(key, value);
42-
},
43-
};
44-
}, [Store, initialized]);
45-
4617
return (
47-
<StoreContext.Provider value={wrapper}>
48-
{children}
49-
</StoreContext.Provider>
18+
<StoreContext.Provider value={Store}>{children}</StoreContext.Provider>
5019
);
5120
};
5221

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import { StoreContextProvider } from './StoreContextProvider';
88
describe('memoryStore', () => {
99
it('should allow to store and retrieve a value', () => {
1010
const store = memoryStore();
11+
store.setup();
1112
store.setItem('foo', 'bar');
1213
expect(store.getItem('foo')).toEqual('bar');
1314
});
1415
describe('removeItem', () => {
1516
it('should remove an item', () => {
1617
const store = memoryStore();
18+
store.setup();
1719
store.setItem('foo', 'bar');
1820
store.setItem('hello', 'world');
1921
store.removeItem('foo');
@@ -24,6 +26,7 @@ describe('memoryStore', () => {
2426
describe('removeItems', () => {
2527
it('should remove all items with the given prefix', () => {
2628
const store = memoryStore();
29+
store.setup();
2730
store.setItem('foo', 'bar');
2831
store.setItem('foo2', 'bar2');
2932
store.setItem('foo3', 'bar3');
@@ -38,6 +41,7 @@ describe('memoryStore', () => {
3841
describe('reset', () => {
3942
it('should reset the store', () => {
4043
const store = memoryStore();
44+
store.setup();
4145
store.setItem('foo', 'bar');
4246
store.reset();
4347
expect(store.getItem('foo')).toEqual(undefined);
@@ -47,6 +51,7 @@ describe('memoryStore', () => {
4751
describe('nested-looking keys', () => {
4852
it('should store and retrieve values in keys that appear nested without overriding content', () => {
4953
const store = memoryStore();
54+
store.setup();
5055
store.setItem('foo', 'parent value');
5156
store.setItem('foo.bar', 'nested value');
5257

@@ -65,6 +70,7 @@ describe('memoryStore', () => {
6570
};
6671

6772
const store = memoryStore(initialStorage);
73+
store.setup();
6874

6975
expect(store.getItem('user')).toEqual({ name: 'John' });
7076
expect(store.getItem('user.settings')).toEqual({ theme: 'dark' });

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ export const memoryStore = (
2424
// Use a flat Map to store key-value pairs directly without treating dots as nested paths
2525
let storage = new Map<string, any>(Object.entries(initialStorage ?? {}));
2626
const subscriptions: { [key: string]: Subscription } = {};
27+
let initialized = false;
28+
let itemsToSetAfterInitialization: Record<string, unknown> = {};
2729

2830
const publish = (key: string, value: any) => {
2931
Object.keys(subscriptions).forEach(id => {
@@ -37,6 +39,19 @@ export const memoryStore = (
3739
return {
3840
setup: () => {
3941
storage = new Map<string, any>(Object.entries(initialStorage));
42+
43+
// Because children might call setItem before the store is initialized,
44+
// we store those calls parameters and apply them once the store is ready
45+
if (Object.keys(itemsToSetAfterInitialization).length > 0) {
46+
const items = Object.entries(itemsToSetAfterInitialization);
47+
for (const [key, value] of items) {
48+
storage.set(key, value);
49+
publish(key, value);
50+
}
51+
itemsToSetAfterInitialization = {};
52+
}
53+
54+
initialized = true;
4055
},
4156
teardown: () => {
4257
storage.clear();
@@ -47,6 +62,12 @@ export const memoryStore = (
4762
: (defaultValue as T);
4863
},
4964
setItem<T = any>(key: string, value: T): void {
65+
// Because children might call setItem before the store is initialized,
66+
// we store those calls parameters and apply them once the store is ready
67+
if (!initialized) {
68+
itemsToSetAfterInitialization[key] = value;
69+
return;
70+
}
5071
storage.set(key, value);
5172
publish(key, value);
5273
},

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

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import * as React from 'react';
2-
import {
3-
render,
4-
screen,
5-
fireEvent,
6-
waitFor,
7-
renderHook,
8-
} from '@testing-library/react';
2+
import { render, screen, fireEvent, renderHook } from '@testing-library/react';
93

104
import { useStore } from './useStore';
115
import { StoreContextProvider } from './StoreContextProvider';
@@ -68,17 +62,9 @@ describe('useStore', () => {
6862
expect(unsubscribe).toHaveBeenCalled();
6963
});
7064

71-
it('should allow to set values', async () => {
72-
const { result } = renderHook(() => useStore('foo.bar'));
73-
await waitFor(() => {
74-
result.current[1]('hello');
75-
expect(result.current[0]).toBe('hello');
76-
});
77-
});
78-
7965
it('should update all components using the same store key on update', () => {
8066
const UpdateStore = () => {
81-
const [, setValue] = useStore('foo.bar');
67+
const [, setValue] = useStore<string>('foo.bar');
8268
return <button onClick={() => setValue('world')}>update</button>;
8369
};
8470
render(
@@ -92,9 +78,9 @@ describe('useStore', () => {
9278
screen.getByText('world');
9379
});
9480

95-
it('should not update components using other store key on update', () => {
81+
it('should not update components using other store key on update', async () => {
9682
const UpdateStore = () => {
97-
const [, setValue] = useStore('other.key');
83+
const [, setValue] = useStore<string>('other.key');
9884
return <button onClick={() => setValue('world')}>update</button>;
9985
};
10086
render(
@@ -109,22 +95,28 @@ describe('useStore', () => {
10995
});
11096

11197
it('should accept an updater function as parameter', async () => {
112-
const { result } = renderHook(() => useStore('foo.bar'));
113-
result.current[1]('hello');
114-
let innerValue;
115-
result.current[1](value => {
116-
innerValue = value;
117-
return 'world';
118-
});
119-
await waitFor(() => {
120-
expect(innerValue).toBe('hello');
121-
});
122-
expect(result.current[0]).toBe('world');
98+
const UpdateStore = () => {
99+
const [, setValue] = useStore<string>('foo.bar');
100+
return (
101+
<button onClick={() => setValue(current => `${current} world`)}>
102+
update
103+
</button>
104+
);
105+
};
106+
render(
107+
<StoreContextProvider value={memoryStore({ 'foo.bar': 'hello' })}>
108+
<StoreReader name="foo.bar" />
109+
<UpdateStore />
110+
</StoreContextProvider>
111+
);
112+
screen.getByText('hello');
113+
fireEvent.click(screen.getByText('update'));
114+
screen.getByText('hello world');
123115
});
124116

125117
it('should clear its value when the key changes', () => {
126118
const StoreConsumer = ({ storeKey }: { storeKey: string }) => {
127-
const [value, setValue] = useStore(storeKey);
119+
const [value, setValue] = useStore<string>(storeKey);
128120
return (
129121
<>
130122
<p>{value}</p>

packages/ra-ui-materialui/src/form/SimpleFormConfigurable.stories.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
I18nContextProvider,
55
TestMemoryRouter,
66
ResourceContextProvider,
7+
StoreContextProvider,
8+
memoryStore,
79
} from 'ra-core';
810
import { ThemeProvider, createTheme, Box, Paper } from '@mui/material';
911
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
@@ -29,13 +31,15 @@ const Wrapper = ({ children }) => (
2931
<ThemeProvider theme={createTheme(defaultTheme)}>
3032
<PreferencesEditorContextProvider>
3133
<TestMemoryRouter>
32-
<ResourceContextProvider value="posts">
33-
<Inspector />
34-
<Box display="flex" justifyContent="flex-end">
35-
<InspectorButton />
36-
</Box>
37-
<Paper sx={{ width: 600, m: 2 }}>{children}</Paper>
38-
</ResourceContextProvider>
34+
<StoreContextProvider value={memoryStore()}>
35+
<ResourceContextProvider value="posts">
36+
<Inspector />
37+
<Box display="flex" justifyContent="flex-end">
38+
<InspectorButton />
39+
</Box>
40+
<Paper sx={{ width: 600, m: 2 }}>{children}</Paper>
41+
</ResourceContextProvider>
42+
</StoreContextProvider>
3943
</TestMemoryRouter>
4044
</PreferencesEditorContextProvider>
4145
</ThemeProvider>

packages/ra-ui-materialui/src/list/datagrid/SelectColumnsButton.stories.tsx

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import * as React from 'react';
2-
import { PreferencesEditorContextProvider, TestMemoryRouter } from 'ra-core';
2+
import {
3+
memoryStore,
4+
PreferencesEditorContextProvider,
5+
StoreContextProvider,
6+
TestMemoryRouter,
7+
} from 'ra-core';
38
import { Box } from '@mui/material';
49
import { createTheme, ThemeProvider } from '@mui/material/styles';
510

@@ -44,22 +49,27 @@ export const Basic = () => (
4449
<PreferencesEditorContextProvider>
4550
<QueryClientProvider client={new QueryClient()}>
4651
<TestMemoryRouter>
47-
<Box p={2}>
48-
<Box textAlign="right">
49-
<SelectColumnsButton resource="books" />
52+
<StoreContextProvider value={memoryStore()}>
53+
<Box p={2}>
54+
<Box textAlign="right">
55+
<SelectColumnsButton resource="books" />
56+
</Box>
57+
<DatagridConfigurable
58+
resource="books"
59+
data={data}
60+
sort={{ field: 'title', order: 'ASC' }}
61+
bulkActionButtons={false}
62+
>
63+
<TextField source="id" />
64+
<TextField
65+
source="title"
66+
label="Original title"
67+
/>
68+
<TextField source="author" />
69+
<TextField source="year" />
70+
</DatagridConfigurable>
5071
</Box>
51-
<DatagridConfigurable
52-
resource="books"
53-
data={data}
54-
sort={{ field: 'title', order: 'ASC' }}
55-
bulkActionButtons={false}
56-
>
57-
<TextField source="id" />
58-
<TextField source="title" label="Original title" />
59-
<TextField source="author" />
60-
<TextField source="year" />
61-
</DatagridConfigurable>
62-
</Box>
72+
</StoreContextProvider>
6373
</TestMemoryRouter>
6474
</QueryClientProvider>
6575
</PreferencesEditorContextProvider>
@@ -71,23 +81,28 @@ export const WithPreferenceKey = () => (
7181
<PreferencesEditorContextProvider>
7282
<QueryClientProvider client={new QueryClient()}>
7383
<TestMemoryRouter>
74-
<Box p={2}>
75-
<Box textAlign="right">
76-
<SelectColumnsButton preferenceKey="just-a-key.to_test_with" />
84+
<StoreContextProvider value={memoryStore()}>
85+
<Box p={2}>
86+
<Box textAlign="right">
87+
<SelectColumnsButton preferenceKey="just-a-key.to_test_with" />
88+
</Box>
89+
<DatagridConfigurable
90+
resource="books"
91+
preferenceKey="just-a-key.to_test_with"
92+
data={data}
93+
sort={{ field: 'title', order: 'ASC' }}
94+
bulkActionButtons={false}
95+
>
96+
<TextField source="id" />
97+
<TextField
98+
source="title"
99+
label="Original title"
100+
/>
101+
<TextField source="author" />
102+
<TextField source="year" />
103+
</DatagridConfigurable>
77104
</Box>
78-
<DatagridConfigurable
79-
resource="books"
80-
preferenceKey="just-a-key.to_test_with"
81-
data={data}
82-
sort={{ field: 'title', order: 'ASC' }}
83-
bulkActionButtons={false}
84-
>
85-
<TextField source="id" />
86-
<TextField source="title" label="Original title" />
87-
<TextField source="author" />
88-
<TextField source="year" />
89-
</DatagridConfigurable>
90-
</Box>
105+
</StoreContextProvider>
91106
</TestMemoryRouter>
92107
</QueryClientProvider>
93108
</PreferencesEditorContextProvider>

0 commit comments

Comments
 (0)