Skip to content

Commit 1f6f24a

Browse files
committed
Fix store initialization issue
1 parent c645452 commit 1f6f24a

File tree

4 files changed

+54
-38
lines changed

4 files changed

+54
-38
lines changed

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,52 @@
11
import * as React from 'react';
2-
import { useEffect } from 'react';
2+
import { useEffect, useMemo, useRef } 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+
1013
useEffect(() => {
1114
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;
1225
return () => {
1326
Store.teardown();
27+
initialized.current = false;
1428
};
1529
}, [Store]);
1630

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+
1746
return (
18-
<StoreContext.Provider value={Store}>{children}</StoreContext.Provider>
47+
<StoreContext.Provider value={wrapper}>
48+
{children}
49+
</StoreContext.Provider>
1950
);
2051
};
2152

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const memoryStore = (
2222
initialStorage: Record<string, any> = {}
2323
): Store => {
2424
// Use a flat Map to store key-value pairs directly without treating dots as nested paths
25-
let storage = new Map<string, any>(Object.entries(initialStorage));
25+
let storage = new Map<string, any>(Object.entries(initialStorage ?? {}));
2626
const subscriptions: { [key: string]: Subscription } = {};
2727

2828
const publish = (key: string, value: any) => {

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,7 @@ describe('useStore', () => {
8282
return <button onClick={() => setValue('world')}>update</button>;
8383
};
8484
render(
85-
<StoreContextProvider
86-
value={memoryStore({ foo: { bar: 'hello' } })}
87-
>
85+
<StoreContextProvider value={memoryStore({ 'foo.bar': 'hello' })}>
8886
<StoreReader name="foo.bar" />
8987
<UpdateStore />
9088
</StoreContextProvider>
@@ -100,9 +98,7 @@ describe('useStore', () => {
10098
return <button onClick={() => setValue('world')}>update</button>;
10199
};
102100
render(
103-
<StoreContextProvider
104-
value={memoryStore({ foo: { bar: 'hello' } })}
105-
>
101+
<StoreContextProvider value={memoryStore({ 'foo.bar': 'hello' })}>
106102
<StoreReader name="foo.bar" />
107103
<UpdateStore />
108104
</StoreContextProvider>

packages/ra-ui-materialui/src/theme/useTheme.spec.tsx

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
2-
import { CoreAdminContext, useStore, memoryStore } from 'ra-core';
2+
import { CoreAdminContext, useStore, memoryStore, StoreSetter } from 'ra-core';
33
import expect from 'expect';
4-
import { render, screen, renderHook } from '@testing-library/react';
4+
import { render, screen, renderHook, waitFor } from '@testing-library/react';
55
import { useTheme } from './useTheme';
66
import { AdminContext } from '../AdminContext';
77
import { ThemeTestWrapper } from '../layout/ThemeTestWrapper';
@@ -99,32 +99,21 @@ describe('useTheme', () => {
9999
expect(screen.queryByText('dark')).not.toBeNull();
100100
});
101101

102-
it('should return theme from settings when available', () => {
103-
const { result: storeResult } = renderHook(() => useStore('theme'), {
104-
wrapper: ({ children }: any) => (
105-
<AdminContext
106-
authProvider={authProvider}
107-
darkTheme={defaultDarkTheme}
108-
>
109-
{children}
110-
</AdminContext>
111-
),
112-
});
113-
const [_, setTheme] = storeResult.current;
114-
setTheme('dark');
115-
116-
const { result: themeResult } = renderHook(() => useTheme(), {
117-
wrapper: ({ children }: any) => (
118-
<AdminContext
119-
authProvider={authProvider}
120-
darkTheme={defaultDarkTheme}
121-
>
122-
{children}
123-
</AdminContext>
124-
),
125-
});
126-
const [theme, __] = themeResult.current;
127-
128-
expect(theme).toEqual('dark');
102+
it('should return theme from settings when available', async () => {
103+
const ThemeViewer = () => {
104+
const [theme] = useTheme();
105+
return <>{theme}</>;
106+
};
107+
render(
108+
<AdminContext
109+
authProvider={authProvider}
110+
darkTheme={defaultDarkTheme}
111+
>
112+
<StoreSetter name="theme" value="dark">
113+
<ThemeViewer />
114+
</StoreSetter>
115+
</AdminContext>
116+
);
117+
await screen.findByText('dark');
129118
});
130119
});

0 commit comments

Comments
 (0)