Skip to content

Commit 9aba0d1

Browse files
committed
Simplify ra-data-local-forage setup
1 parent 661f10c commit 9aba0d1

File tree

3 files changed

+131
-66
lines changed

3 files changed

+131
-66
lines changed

examples/simple/src/dataProvider.tsx

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
1-
import fakeRestProvider from 'ra-data-fakerest';
1+
import fakeRestProvider from 'ra-data-local-forage';
22
import { DataProvider, withLifecycleCallbacks, HttpError } from 'react-admin';
33
import get from 'lodash/get';
44
import data from './data';
55
import addUploadFeature from './addUploadFeature';
66
import { queryClient } from './queryClient';
77

8-
const dataProvider = withLifecycleCallbacks(fakeRestProvider(data, true, 300), [
9-
{
10-
resource: 'posts',
11-
beforeDelete: async ({ id }, dp) => {
12-
// delete related comments
13-
const { data: comments } = await dp.getList('comments', {
14-
filter: { post_id: id },
15-
pagination: { page: 1, perPage: 100 },
16-
sort: { field: 'id', order: 'DESC' },
17-
});
18-
await dp.deleteMany('comments', {
19-
ids: comments.map(comment => comment.id),
20-
});
21-
// The queryClient would be unaware of the deleted comments without this.
22-
queryClient.invalidateQueries({ queryKey: ['comments'] });
23-
return { id };
8+
const dataProvider = withLifecycleCallbacks(
9+
fakeRestProvider({ defaultData: data, loggingEnabled: true }),
10+
[
11+
{
12+
resource: 'posts',
13+
beforeDelete: async ({ id }, dp) => {
14+
// delete related comments
15+
const { data: comments } = await dp.getList('comments', {
16+
filter: { post_id: id },
17+
pagination: { page: 1, perPage: 100 },
18+
sort: { field: 'id', order: 'DESC' },
19+
});
20+
await dp.deleteMany('comments', {
21+
ids: comments.map(comment => comment.id),
22+
});
23+
// The queryClient would be unaware of the deleted comments without this.
24+
queryClient.invalidateQueries({ queryKey: ['comments'] });
25+
return { id };
26+
},
2427
},
25-
},
26-
]);
28+
]
29+
);
2730

2831
const addTagsSearchSupport = (dataProvider: DataProvider) => ({
2932
...dataProvider,

packages/ra-data-localforage/README.md

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,9 @@ import { Admin, Resource } from 'react-admin';
1919
import localForageDataProvider from 'ra-data-local-forage';
2020

2121
import { PostList } from './posts';
22+
const dataProvider = localForageDataProvider();
2223

2324
const App = () => {
24-
const [dataProvider, setDataProvider] = React.useState<DataProvider | null>(null);
25-
26-
React.useEffect(() => {
27-
async function startDataProvider() {
28-
const localForageProvider = await localForageDataProvider();
29-
setDataProvider(localForageProvider);
30-
}
31-
32-
if (dataProvider === null) {
33-
startDataProvider();
34-
}
35-
}, [dataProvider]);
36-
37-
// hide the admin until the data provider is ready
38-
if (!dataProvider) return <p>Loading...</p>;
39-
4025
return (
4126
<Admin dataProvider={dataProvider}>
4227
<Resource name="posts" list={ListGuesser}/>
@@ -52,7 +37,7 @@ export default App;
5237
By default, the data provider starts with no resource. To set default data if the IndexedDB is empty, pass a JSON object as the `defaultData` argument:
5338

5439
```js
55-
const dataProvider = await localForageDataProvider({
40+
const dataProvider = localForageDataProvider({
5641
defaultData: {
5742
posts: [
5843
{ id: 0, title: 'Hello, world!' },
@@ -75,7 +60,7 @@ Foreign keys are also supported: just name the field `{related_resource_name}_id
7560
As this data provider doesn't use the network, you can't debug it using the network tab of your browser developer tools. However, it can log all calls (input and output) in the console, provided you set the `loggingEnabled` parameter:
7661

7762
```js
78-
const dataProvider = await localForageDataProvider({
63+
const dataProvider = localForageDataProvider({
7964
loggingEnabled: true
8065
});
8166
```

packages/ra-data-localforage/src/index.ts

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ import localforage from 'localforage';
2525
* @example // initialize with no data
2626
*
2727
* import localForageDataProvider from 'ra-data-local-forage';
28-
* const dataProvider = await localForageDataProvider();
28+
* const dataProvider = localForageDataProvider();
2929
*
3030
* @example // initialize with default data (will be ignored if data has been modified by user)
3131
*
3232
* import localForageDataProvider from 'ra-data-local-forage';
33-
* const dataProvider = await localForageDataProvider({
33+
* const dataProvider = localForageDataProvider({
3434
* defaultData: {
3535
* posts: [
3636
* { id: 0, title: 'Hello, world!' },
@@ -43,15 +43,17 @@ import localforage from 'localforage';
4343
* }
4444
* });
4545
*/
46-
export default async (
47-
params?: LocalForageDataProviderParams
48-
): Promise<DataProvider> => {
46+
export default (params?: LocalForageDataProviderParams): DataProvider => {
4947
const {
5048
defaultData = {},
5149
prefixLocalForageKey = 'ra-data-local-forage-',
5250
loggingEnabled = false,
5351
} = params || {};
5452

53+
let data: Record<string, any> | undefined;
54+
let baseDataProvider: DataProvider | undefined;
55+
let initializePromise: Promise<void> | undefined;
56+
5557
const getLocalForageData = async (): Promise<any> => {
5658
const keys = await localforage.keys();
5759
const keyFiltered = keys.filter(key => {
@@ -71,28 +73,44 @@ export default async (
7173
return localForageData;
7274
};
7375

74-
const localForageData = await getLocalForageData();
75-
const data = localForageData ?? defaultData;
76+
const initialize = async () => {
77+
if (!initializePromise) {
78+
initializePromise = initializeProvider();
79+
}
80+
return initializePromise;
81+
};
82+
83+
const initializeProvider = async () => {
84+
const localForageData = await getLocalForageData();
85+
data = localForageData ?? defaultData;
86+
87+
baseDataProvider = fakeRestProvider(
88+
data,
89+
loggingEnabled
90+
) as DataProvider;
91+
};
7692

7793
// Persist in localForage
7894
const updateLocalForage = (resource: string) => {
95+
if (!data) {
96+
throw new Error('The dataProvider is not initialized.');
97+
}
7998
localforage.setItem(
8099
`${prefixLocalForageKey}${resource}`,
81100
data[resource]
82101
);
83102
};
84103

85-
const baseDataProvider = fakeRestProvider(
86-
data,
87-
loggingEnabled
88-
) as DataProvider;
89-
90104
return {
91105
// read methods are just proxies to FakeRest
92-
getList: <RecordType extends RaRecord = any>(
106+
getList: async <RecordType extends RaRecord = any>(
93107
resource: string,
94108
params: GetListParams
95109
) => {
110+
await initialize();
111+
if (!baseDataProvider) {
112+
throw new Error('The dataProvider is not initialized.');
113+
}
96114
return baseDataProvider
97115
.getList<RecordType>(resource, params)
98116
.catch(error => {
@@ -104,19 +122,35 @@ export default async (
104122
}
105123
});
106124
},
107-
getOne: <RecordType extends RaRecord = any>(
125+
getOne: async <RecordType extends RaRecord = any>(
108126
resource: string,
109127
params: GetOneParams<any>
110-
) => baseDataProvider.getOne<RecordType>(resource, params),
111-
getMany: <RecordType extends RaRecord = any>(
128+
) => {
129+
await initialize();
130+
if (!baseDataProvider) {
131+
throw new Error('The dataProvider is not initialized.');
132+
}
133+
return baseDataProvider.getOne<RecordType>(resource, params);
134+
},
135+
getMany: async <RecordType extends RaRecord = any>(
112136
resource: string,
113137
params: GetManyParams<RecordType>
114-
) => baseDataProvider.getMany<RecordType>(resource, params),
115-
getManyReference: <RecordType extends RaRecord = any>(
138+
) => {
139+
await initialize();
140+
if (!baseDataProvider) {
141+
throw new Error('The dataProvider is not initialized.');
142+
}
143+
return baseDataProvider.getMany<RecordType>(resource, params);
144+
},
145+
getManyReference: async <RecordType extends RaRecord = any>(
116146
resource: string,
117147
params: GetManyReferenceParams
118-
) =>
119-
baseDataProvider
148+
) => {
149+
await initialize();
150+
if (!baseDataProvider) {
151+
throw new Error('The dataProvider is not initialized.');
152+
}
153+
return baseDataProvider
120154
.getManyReference<RecordType>(resource, params)
121155
.catch(error => {
122156
if (error.code === 1) {
@@ -125,13 +159,22 @@ export default async (
125159
} else {
126160
throw error;
127161
}
128-
}),
162+
});
163+
},
129164

130165
// update methods need to persist changes in localForage
131-
update: <RecordType extends RaRecord = any>(
166+
update: async <RecordType extends RaRecord = any>(
132167
resource: string,
133168
params: UpdateParams<any>
134169
) => {
170+
await initialize();
171+
if (!data) {
172+
throw new Error('The dataProvider is not initialized.');
173+
}
174+
if (!baseDataProvider) {
175+
throw new Error('The dataProvider is not initialized.');
176+
}
177+
135178
const index = data[resource].findIndex(
136179
(record: { id: any }) => record.id === params.id
137180
);
@@ -142,8 +185,16 @@ export default async (
142185
updateLocalForage(resource);
143186
return baseDataProvider.update<RecordType>(resource, params);
144187
},
145-
updateMany: (resource: string, params: UpdateManyParams<any>) => {
188+
updateMany: async (resource: string, params: UpdateManyParams<any>) => {
189+
await initialize();
190+
if (!baseDataProvider) {
191+
throw new Error('The dataProvider is not initialized.');
192+
}
193+
146194
params.ids.forEach((id: Identifier) => {
195+
if (!data) {
196+
throw new Error('The dataProvider is not initialized.');
197+
}
147198
const index = data[resource].findIndex(
148199
(record: { id: Identifier }) => record.id === id
149200
);
@@ -155,14 +206,21 @@ export default async (
155206
updateLocalForage(resource);
156207
return baseDataProvider.updateMany(resource, params);
157208
},
158-
create: <RecordType extends Omit<RaRecord, 'id'> = any>(
209+
create: async <RecordType extends Omit<RaRecord, 'id'> = any>(
159210
resource: string,
160211
params: CreateParams<any>
161212
) => {
213+
await initialize();
214+
if (!baseDataProvider) {
215+
throw new Error('The dataProvider is not initialized.');
216+
}
162217
// we need to call the fakerest provider first to get the generated id
163218
return baseDataProvider
164219
.create<RecordType>(resource, params)
165220
.then(response => {
221+
if (!data) {
222+
throw new Error('The dataProvider is not initialized.');
223+
}
166224
if (!data.hasOwnProperty(resource)) {
167225
data[resource] = [];
168226
}
@@ -171,21 +229,40 @@ export default async (
171229
return response;
172230
});
173231
},
174-
delete: <RecordType extends RaRecord = any>(
232+
delete: async <RecordType extends RaRecord = any>(
175233
resource: string,
176234
params: DeleteParams<RecordType>
177235
) => {
236+
await initialize();
237+
if (!baseDataProvider) {
238+
throw new Error('The dataProvider is not initialized.');
239+
}
240+
if (!data) {
241+
throw new Error('The dataProvider is not initialized.');
242+
}
178243
const index = data[resource].findIndex(
179244
(record: { id: any }) => record.id === params.id
180245
);
181246
pullAt(data[resource], [index]);
182247
updateLocalForage(resource);
183248
return baseDataProvider.delete<RecordType>(resource, params);
184249
},
185-
deleteMany: (resource: string, params: DeleteManyParams<any>) => {
186-
const indexes = params.ids.map((id: any) =>
187-
data[resource].findIndex((record: any) => record.id === id)
188-
);
250+
deleteMany: async (resource: string, params: DeleteManyParams<any>) => {
251+
await initialize();
252+
if (!baseDataProvider) {
253+
throw new Error('The dataProvider is not initialized.');
254+
}
255+
if (!data) {
256+
throw new Error('The dataProvider is not initialized.');
257+
}
258+
const indexes = params.ids.map((id: any) => {
259+
if (!data) {
260+
throw new Error('The dataProvider is not initialized.');
261+
}
262+
return data[resource].findIndex(
263+
(record: any) => record.id === id
264+
);
265+
});
189266
pullAt(data[resource], indexes);
190267
updateLocalForage(resource);
191268
return baseDataProvider.deleteMany(resource, params);

0 commit comments

Comments
 (0)