Skip to content

Commit fc0d8dd

Browse files
authored
Merge pull request #6913 from marmelab/fix-usegetmany-state
Update useGetMany loading/loaded state
2 parents dfcd326 + 5f0ccfe commit fc0d8dd

File tree

2 files changed

+159
-5
lines changed

2 files changed

+159
-5
lines changed

packages/ra-core/src/dataProvider/useGetMany.spec.tsx

+148
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { renderWithRedux } from 'ra-test';
55
import useGetMany from './useGetMany';
66
import { DataProviderContext } from '../dataProvider';
77
import { waitFor } from '@testing-library/react';
8+
import { useState } from 'react';
89

910
const UseGetMany = ({
1011
resource,
@@ -18,6 +19,26 @@ const UseGetMany = ({
1819
return <div>hello</div>;
1920
};
2021

22+
let updateState;
23+
24+
const UseCustomGetMany = ({
25+
resource,
26+
ids,
27+
options = {},
28+
callback = null,
29+
...rest
30+
}) => {
31+
const [stateIds, setStateIds] = useState(ids);
32+
const hookValue = useGetMany(resource, stateIds, options);
33+
if (callback) callback(hookValue);
34+
35+
updateState = newIds => {
36+
setStateIds(newIds);
37+
};
38+
39+
return <div>hello</div>;
40+
};
41+
2142
describe('useGetMany', () => {
2243
it('should call the dataProvider with a GET_MANY on mount', async () => {
2344
const dataProvider = {
@@ -200,6 +221,133 @@ describe('useGetMany', () => {
200221
});
201222
});
202223

224+
it('should update loading state when ids change', async () => {
225+
const dataProvider = {
226+
getMany: jest.fn((resource, params) => {
227+
if (params.ids.length === 1) {
228+
return Promise.resolve({
229+
data: [{ id: 1, title: 'foo' }],
230+
});
231+
} else {
232+
return Promise.resolve({
233+
data: [
234+
{ id: 1, title: 'foo' },
235+
{ id: 2, title: 'bar' },
236+
],
237+
});
238+
}
239+
}),
240+
};
241+
242+
const hookValue = jest.fn();
243+
renderWithRedux(
244+
<DataProviderContext.Provider value={dataProvider}>
245+
<UseCustomGetMany
246+
resource="posts"
247+
ids={[1]}
248+
callback={hookValue}
249+
/>
250+
</DataProviderContext.Provider>,
251+
{
252+
admin: {
253+
resources: {
254+
posts: {
255+
data: {},
256+
},
257+
},
258+
},
259+
}
260+
);
261+
262+
await waitFor(() => {
263+
expect(dataProvider.getMany).toBeCalledTimes(1);
264+
});
265+
266+
expect(hookValue.mock.calls[0][0]).toEqual({
267+
data: [undefined],
268+
error: null,
269+
loaded: false,
270+
loading: true,
271+
refetch: expect.any(Function),
272+
});
273+
expect(hookValue.mock.calls[1][0]).toEqual({
274+
data: [undefined],
275+
error: null,
276+
loaded: false,
277+
loading: true,
278+
refetch: expect.any(Function),
279+
});
280+
expect(hookValue.mock.calls[2][0]).toEqual({
281+
data: [{ id: 1, title: 'foo' }],
282+
error: null,
283+
loaded: false,
284+
loading: true,
285+
refetch: expect.any(Function),
286+
});
287+
expect(hookValue.mock.calls[3][0]).toEqual({
288+
data: [{ id: 1, title: 'foo' }],
289+
error: null,
290+
loaded: true,
291+
loading: false,
292+
refetch: expect.any(Function),
293+
});
294+
295+
// Updating ids...
296+
updateState([1, 2]);
297+
298+
await waitFor(() => {
299+
expect(dataProvider.getMany).toBeCalledTimes(2);
300+
});
301+
302+
expect(hookValue.mock.calls[4][0]).toEqual({
303+
data: [{ id: 1, title: 'foo' }],
304+
error: null,
305+
loaded: true,
306+
loading: false,
307+
refetch: expect.any(Function),
308+
});
309+
expect(hookValue.mock.calls[5][0]).toEqual({
310+
data: [{ id: 1, title: 'foo' }],
311+
error: null,
312+
loaded: true,
313+
loading: true,
314+
refetch: expect.any(Function),
315+
});
316+
expect(hookValue.mock.calls[6][0]).toEqual({
317+
data: [{ id: 1, title: 'foo' }],
318+
error: null,
319+
loaded: true,
320+
loading: true,
321+
refetch: expect.any(Function),
322+
});
323+
expect(hookValue.mock.calls[7][0]).toEqual({
324+
data: [
325+
{ id: 1, title: 'foo' },
326+
{
327+
id: 2,
328+
title: 'bar',
329+
},
330+
],
331+
error: null,
332+
loaded: true,
333+
loading: true,
334+
refetch: expect.any(Function),
335+
});
336+
expect(hookValue.mock.calls[8][0]).toEqual({
337+
data: [
338+
{ id: 1, title: 'foo' },
339+
{
340+
id: 2,
341+
title: 'bar',
342+
},
343+
],
344+
error: null,
345+
loaded: true,
346+
loading: false,
347+
refetch: expect.any(Function),
348+
});
349+
});
350+
203351
it('should retrieve results from redux state on mount', () => {
204352
const hookValue = jest.fn();
205353
renderWithRedux(

packages/ra-core/src/dataProvider/useGetMany.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useMemo } from 'react';
1+
import { useCallback, useMemo, useEffect } from 'react';
22
import ReactDOM from 'react-dom';
33
import { useSelector } from 'react-redux';
44
import { createSelector } from 'reselect';
@@ -11,7 +11,6 @@ import { CRUD_GET_MANY } from '../actions/dataActions/crudGetMany';
1111
import { Identifier, Record, ReduxState, DataProviderProxy } from '../types';
1212
import { useSafeSetState } from '../util/hooks';
1313
import useDataProvider from './useDataProvider';
14-
import { useEffect } from 'react';
1514
import { useVersion } from '../controller';
1615
import { Refetch } from './useQueryWithStore';
1716

@@ -119,10 +118,17 @@ const useGetMany = (
119118
refetch,
120119
});
121120
if (!isEqual(state.data, data)) {
122-
setState({
121+
const newState = {
123122
...state,
124-
data,
125-
});
123+
data: data?.includes(undefined) ? state.data : data,
124+
loading:
125+
state.data?.length !== 0 &&
126+
(state.loading || data?.includes(undefined)),
127+
};
128+
129+
if (!isEqual(state, newState)) {
130+
setState(newState);
131+
}
126132
}
127133
dataProvider = useDataProvider(); // not the best way to pass the dataProvider to a function outside the hook, but I couldn't find a better one
128134
useEffect(

0 commit comments

Comments
 (0)