Skip to content

Commit 99cd041

Browse files
authored
fix: Collections work with nested args (#3281)
1 parent 79b9eca commit 99cd041

File tree

7 files changed

+229
-6
lines changed

7 files changed

+229
-6
lines changed

.changeset/dirty-lamps-raise.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@data-client/endpoint': patch
3+
'@data-client/graphql': patch
4+
'@data-client/rest': patch
5+
---
6+
7+
Collections work with nested args
8+
9+
This fixes [integration with qs library](https://dataclient.io/rest/api/RestEndpoint#using-qs-library) and more complex search parameters.

packages/endpoint/src/schemas/Collection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export default class CollectionSchema<
136136
const obj =
137137
this.argsKey ? this.argsKey(...args) : this.nestKey(parent, key);
138138
for (const key in obj) {
139-
if (typeof obj[key] !== 'string' && obj[key] !== undefined)
139+
if (['number', 'boolean'].includes(typeof obj[key]))
140140
obj[key] = `${obj[key]}`;
141141
}
142142
return consistentSerialize(obj);

packages/endpoint/src/schemas/__tests__/Collection.test.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// eslint-env jest
2-
import { initialState, State } from '@data-client/core';
2+
import { initialState } from '@data-client/core';
33
import { normalize, denormalize, MemoCache } from '@data-client/normalizr';
4-
import { IDEntity } from '__tests__/new';
4+
import { ArticleResource, IDEntity } from '__tests__/new';
55
import { Record } from 'immutable';
66

77
import SimpleMemoCache from './denormalize';
@@ -12,7 +12,7 @@ import PolymorphicSchema from '../Polymorphic';
1212
let dateSpy: jest.SpyInstance;
1313
beforeAll(() => {
1414
dateSpy = jest
15-
// eslint-disable-next-line no-undef
15+
1616
.spyOn(global.Date, 'now')
1717
.mockImplementation(() => new Date('2019-05-14T11:01:58.135Z').valueOf());
1818
});
@@ -670,4 +670,23 @@ describe(`${schema.Collection.name} denormalization`, () => {
670670
);
671671
expect(queryKey).toBeUndefined();
672672
});
673+
674+
it('pk should serialize differently with nested args', () => {
675+
const filtersA = {
676+
search: {
677+
type: 'Coupon',
678+
},
679+
};
680+
const filtersB = {
681+
search: {
682+
type: 'Cashback',
683+
},
684+
};
685+
686+
expect(
687+
ArticleResource.getList.schema.pk([], undefined, '', [filtersA]),
688+
).not.toEqual(
689+
ArticleResource.getList.schema.pk([], undefined, '', [filtersB]),
690+
);
691+
});
673692
});

packages/react/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@
189189
"@anansi/browserslist-config": "^1.4.2",
190190
"@react-navigation/native": "^7.0.0",
191191
"@types/node": "^22.0.0",
192+
"@types/qs": "^6",
192193
"@types/react": "^18.0.30",
194+
"qs": "^6.13.1",
193195
"react-native": "^0.76.0"
194196
}
195197
}

packages/react/src/__tests__/__snapshots__/integration-collections.tsx.snap

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,32 @@ exports[`CacheProvider RestEndpoint/current should update on get for a paginated
200200
}
201201
`;
202202

203+
exports[`CacheProvider RestEndpoint/current should update on get for nested args change 1`] = `
204+
[
205+
Offer {
206+
"id": "5",
207+
"text": "hi",
208+
},
209+
Offer {
210+
"id": "2",
211+
"text": "next",
212+
},
213+
]
214+
`;
215+
216+
exports[`CacheProvider RestEndpoint/current should update on get for nested args change 2`] = `
217+
[
218+
Offer {
219+
"id": "10",
220+
"text": "second",
221+
},
222+
Offer {
223+
"id": "11",
224+
"text": "page",
225+
},
226+
]
227+
`;
228+
203229
exports[`CacheProvider RestEndpoint/next endpoint.assign should add to schema.Values Collections 1`] = `
204230
Article {
205231
"author": null,
@@ -400,6 +426,32 @@ exports[`CacheProvider RestEndpoint/next should update on get for a paginated re
400426
}
401427
`;
402428

429+
exports[`CacheProvider RestEndpoint/next should update on get for nested args change 1`] = `
430+
[
431+
Offer {
432+
"id": "5",
433+
"text": "hi",
434+
},
435+
Offer {
436+
"id": "2",
437+
"text": "next",
438+
},
439+
]
440+
`;
441+
442+
exports[`CacheProvider RestEndpoint/next should update on get for nested args change 2`] = `
443+
[
444+
Offer {
445+
"id": "10",
446+
"text": "second",
447+
},
448+
Offer {
449+
"id": "11",
450+
"text": "page",
451+
},
452+
]
453+
`;
454+
403455
exports[`CacheProvider pagination should ignore undefined values 1`] = `
404456
[
405457
Article {
@@ -759,6 +811,32 @@ exports[`ExternalDataProvider RestEndpoint/current should update on get for a pa
759811
}
760812
`;
761813

814+
exports[`ExternalDataProvider RestEndpoint/current should update on get for nested args change 1`] = `
815+
[
816+
Offer {
817+
"id": "5",
818+
"text": "hi",
819+
},
820+
Offer {
821+
"id": "2",
822+
"text": "next",
823+
},
824+
]
825+
`;
826+
827+
exports[`ExternalDataProvider RestEndpoint/current should update on get for nested args change 2`] = `
828+
[
829+
Offer {
830+
"id": "10",
831+
"text": "second",
832+
},
833+
Offer {
834+
"id": "11",
835+
"text": "page",
836+
},
837+
]
838+
`;
839+
762840
exports[`ExternalDataProvider RestEndpoint/next endpoint.assign should add to schema.Values Collections 1`] = `
763841
Article {
764842
"author": null,
@@ -959,6 +1037,32 @@ exports[`ExternalDataProvider RestEndpoint/next should update on get for a pagin
9591037
}
9601038
`;
9611039

1040+
exports[`ExternalDataProvider RestEndpoint/next should update on get for nested args change 1`] = `
1041+
[
1042+
Offer {
1043+
"id": "5",
1044+
"text": "hi",
1045+
},
1046+
Offer {
1047+
"id": "2",
1048+
"text": "next",
1049+
},
1050+
]
1051+
`;
1052+
1053+
exports[`ExternalDataProvider RestEndpoint/next should update on get for nested args change 2`] = `
1054+
[
1055+
Offer {
1056+
"id": "10",
1057+
"text": "second",
1058+
},
1059+
Offer {
1060+
"id": "11",
1061+
"text": "page",
1062+
},
1063+
]
1064+
`;
1065+
9621066
exports[`ExternalDataProvider pagination should ignore undefined values 1`] = `
9631067
[
9641068
Article {

packages/react/src/__tests__/integration-collections.tsx

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { CacheProvider } from '@data-client/react';
22
import { DataProvider as ExternalDataProvider } from '@data-client/react/redux';
3-
import { schema, RestEndpoint, PolymorphicInterface } from '@data-client/rest';
3+
import {
4+
schema,
5+
RestEndpoint,
6+
PolymorphicInterface,
7+
RestGenerics,
8+
Entity,
9+
} from '@data-client/rest';
410
import { resource } from '@data-client/rest';
511
import {
612
IDEntity,
@@ -10,6 +16,7 @@ import {
1016
SecondUnion,
1117
} from '__tests__/new';
1218
import nock from 'nock';
19+
import qs from 'qs';
1320

1421
import { makeRenderDataClient, act } from '../../../test';
1522
import { useSuspense } from '../hooks';
@@ -19,6 +26,12 @@ import {
1926
valuesFixture,
2027
} from '../test-fixtures';
2128

29+
class QSEndpoint<O extends RestGenerics = any> extends RestEndpoint<O> {
30+
searchToString(searchParams: any) {
31+
return qs.stringify(searchParams);
32+
}
33+
}
34+
2235
class Todo extends IDEntity {
2336
userId = 0;
2437
title = '';
@@ -607,6 +620,64 @@ describe.each([
607620
]);
608621
});
609622

623+
it('should update on get for nested args change', async () => {
624+
const filtersA = {
625+
search: {
626+
type: 'Coupon',
627+
},
628+
};
629+
const filtersB = {
630+
search: {
631+
type: 'Cashback',
632+
},
633+
};
634+
class Offer extends Entity {
635+
id = '';
636+
text = '';
637+
}
638+
const OfferResource = resource({
639+
Endpoint: QSEndpoint,
640+
schema: Offer,
641+
searchParams: filtersA,
642+
path: '/offers/:id',
643+
paginationField: 'page',
644+
});
645+
646+
const { result, rerender } = renderDataClient(
647+
({ filters }) => {
648+
return useSuspense(OfferResource.getList, filters);
649+
},
650+
{
651+
initialProps: { filters: filtersA },
652+
initialFixtures: [
653+
{
654+
endpoint: OfferResource.getList,
655+
args: [filtersA],
656+
response: [
657+
{ id: '5', text: 'hi' },
658+
{ id: '2', text: 'next' },
659+
],
660+
},
661+
{
662+
endpoint: OfferResource.getList,
663+
args: [filtersB],
664+
response: [
665+
{ id: '10', text: 'second' },
666+
{ id: '11', text: 'page' },
667+
],
668+
},
669+
],
670+
},
671+
);
672+
expect(result.current).toMatchSnapshot();
673+
console.log(result.current);
674+
const firstResult = result.current;
675+
rerender({ filters: filtersB });
676+
console.log(result.current);
677+
expect(result.current).not.toEqual(firstResult);
678+
expect(result.current).toMatchSnapshot();
679+
});
680+
610681
it('should update on get for a paginated resource with searchParams', async () => {
611682
mynock.get(`/article`).reply(200, paginatedFirstPage);
612683
mynock.get(`/article?userId=2`).reply(200, paginatedFirstPage);

yarn.lock

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3189,7 +3189,9 @@ __metadata:
31893189
"@data-client/use-enhanced-reducer": "npm:^0.1.10"
31903190
"@react-navigation/native": "npm:^7.0.0"
31913191
"@types/node": "npm:^22.0.0"
3192+
"@types/qs": "npm:^6"
31923193
"@types/react": "npm:^18.0.30"
3194+
qs: "npm:^6.13.1"
31933195
react-native: "npm:^0.76.0"
31943196
peerDependencies:
31953197
"@react-navigation/native": ^6.0.0 || ^7.0.0
@@ -7491,6 +7493,13 @@ __metadata:
74917493
languageName: node
74927494
linkType: hard
74937495

7496+
"@types/qs@npm:^6":
7497+
version: 6.9.17
7498+
resolution: "@types/qs@npm:6.9.17"
7499+
checksum: 10c0/a183fa0b3464267f8f421e2d66d960815080e8aab12b9aadab60479ba84183b1cdba8f4eff3c06f76675a8e42fe6a3b1313ea76c74f2885c3e25d32499c17d1b
7500+
languageName: node
7501+
linkType: hard
7502+
74947503
"@types/range-parser@npm:*":
74957504
version: 1.2.4
74967505
resolution: "@types/range-parser@npm:1.2.4"
@@ -25016,7 +25025,7 @@ __metadata:
2501625025
languageName: node
2501725026
linkType: hard
2501825027

25019-
"qs@npm:6.13.0, qs@npm:^6.12.3":
25028+
"qs@npm:6.13.0":
2502025029
version: 6.13.0
2502125030
resolution: "qs@npm:6.13.0"
2502225031
dependencies:
@@ -25025,6 +25034,15 @@ __metadata:
2502525034
languageName: node
2502625035
linkType: hard
2502725036

25037+
"qs@npm:^6.12.3, qs@npm:^6.13.1":
25038+
version: 6.13.1
25039+
resolution: "qs@npm:6.13.1"
25040+
dependencies:
25041+
side-channel: "npm:^1.0.6"
25042+
checksum: 10c0/5ef527c0d62ffca5501322f0832d800ddc78eeb00da3b906f1b260ca0492721f8cdc13ee4b8fd8ac314a6ec37b948798c7b603ccc167e954088df392092f160c
25043+
languageName: node
25044+
linkType: hard
25045+
2502825046
"qs@npm:~6.5.2":
2502925047
version: 6.5.3
2503025048
resolution: "qs@npm:6.5.3"

0 commit comments

Comments
 (0)