Skip to content

Commit 5d2436f

Browse files
authored
Better Typescript typings for resources (#43)
1 parent 728a5c1 commit 5d2436f

File tree

11 files changed

+107
-53
lines changed

11 files changed

+107
-53
lines changed

examples/basic-routing-with-resources/about.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const aboutResource = createResource({
99
const response = await fetch(
1010
'https://dog.ceo/api/breed/schnauzer/images/random'
1111
);
12-
const result = await response.json();
12+
const result:{ message: string} = await response.json();
1313

1414
return result;
1515
},

examples/basic-routing-with-resources/home.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const homeResource = createResource({
77
maxAge: 0,
88
getData: async () => {
99
const response = await fetch('https://dog.ceo/api/breeds/image/random');
10-
const result = await response.json();
10+
const result:{ message: string} = await response.json();
1111

1212
return result;
1313
},

src/__tests__/unit/controllers/hooks/resource-store/test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const mockSlice = {
2323
data: null,
2424
error: null,
2525
loading: false,
26+
key: 'i-am-a-key',
2627
promise: Promise.resolve(),
2728
expiresAt: 0,
2829
};

src/__tests__/unit/controllers/router-store/test.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -474,12 +474,15 @@ describe('SPA Router store', () => {
474474

475475
const ComponentB = () => (
476476
<ResourceSubscriber resource={resourceB}>
477-
{({ data, loading }) => {
478-
if (loading) {
479-
return <div>{loading}</div>;
477+
{props => {
478+
if (props.loading) {
479+
return <div>{props.loading}</div>;
480+
}
481+
if (props.error) {
482+
return <div>{props.error}</div>;
480483
}
481484

482-
return <div>{data}</div>;
485+
return <div>{props.data.toString()}</div>;
483486
}}
484487
</ResourceSubscriber>
485488
);

src/common/types.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,39 +66,66 @@ export type RouteResourceLoading = boolean;
6666

6767
export type RouteResourceTimestamp = number | null;
6868

69-
export type RouteResourceError = Record<string, any> | Error | null;
69+
export type RouteResourceError = Record<string, any> | Error;
7070

71-
export type RouteResourceData = Record<string, any> | null;
71+
export type RouteResourceDataPayload = Record<string, any>;
7272

73-
export type RouteResourcePromise = Promise<any> | null;
73+
export type RouteResourcePromise<T> = Promise<T> | null;
7474

75-
export type RouteResourceUpdater = (
75+
export type RouteResourceUpdater<RouteResourceData = unknown> = (
7676
data: RouteResourceData
7777
) => RouteResourceData;
7878

79-
export type RouteResourceResponse = {
80-
loading: RouteResourceLoading;
79+
export type RouteResourceResponseBase<RouteResourceData> = {
80+
loading?: RouteResourceLoading;
81+
error?: RouteResourceError | null;
82+
data?: RouteResourceData;
83+
key?: string;
84+
promise?: RouteResourcePromise<RouteResourceData>;
85+
expiresAt: RouteResourceTimestamp;
86+
};
87+
88+
export type RouteResourceResponseLoading<RouteResourceData> = {
89+
loading: true;
90+
};
91+
92+
export type RouteResourceResponseError<RouteResourceData> = {
93+
loading: false;
8194
error: RouteResourceError;
95+
};
96+
97+
export type RouteResourceResponseLoaded<RouteResourceData> = {
98+
loading: false;
99+
error: null;
82100
data: RouteResourceData;
83-
promise: RouteResourcePromise;
84-
expiresAt: RouteResourceTimestamp;
85101
};
86102

103+
export type RouteResourceResponse<
104+
RouteResourceData = unknown
105+
> = RouteResourceResponseBase<RouteResourceData> &
106+
(
107+
| RouteResourceResponseLoading<RouteResourceData>
108+
| RouteResourceResponseError<RouteResourceData>
109+
| RouteResourceResponseLoaded<RouteResourceData>
110+
);
111+
87112
export type RouteResourceGettersArgs = [RouterContext, ResourceStoreContext];
88113

89-
export type RouteResource = {
114+
export type RouteResource<RouteResourceData = unknown> = {
90115
type: string;
91116
getKey: (...args: RouteResourceGettersArgs) => string;
92117
maxAge: number;
93-
getData: (...args: RouteResourceGettersArgs) => RouteResourcePromise;
118+
getData: (
119+
...args: RouteResourceGettersArgs
120+
) => RouteResourcePromise<RouteResourceData>;
94121
};
95122

96123
export type RouteResources = RouteResource[];
97124

98125
export type ResourceStoreContext = Record<string, any>;
99126

100127
export type RouteResourceDataForType = {
101-
[index: string]: RouteResourceResponse;
128+
[index: string]: RouteResourceResponseBase<unknown>;
102129
};
103130

104131
export type ResourceStoreData = {

src/controllers/hooks/resource-store/index.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,21 @@ import { useRouterStoreStatic, RouterStore } from '../../router-store';
1111
import { EntireRouterState, AllRouterActions } from '../../router-store/types';
1212
import { createHook } from 'react-sweet-state';
1313

14-
type UseResourceHookResponse<
15-
D = RouteResourceResponse['data']
16-
> = RouteResourceResponse & {
17-
data: D;
18-
update: (getNewData: RouteResourceUpdater) => void;
14+
type UseResourceHookResponse<RouteResourceData> = RouteResourceResponse<
15+
RouteResourceData
16+
> & {
17+
update: (getNewData: RouteResourceUpdater<RouteResourceData>) => void;
1918
refresh: () => void;
2019
};
2120

2221
type UseResourceOptions = {
2322
routerContext?: RouterContext;
2423
};
2524

26-
export const useResource = (
27-
resource: RouteResource,
25+
export const useResource = <RouteResourceData extends unknown>(
26+
resource: RouteResource<RouteResourceData>,
2827
options?: UseResourceOptions
29-
): UseResourceHookResponse => {
28+
): UseResourceHookResponse<RouteResourceData> => {
3029
const [, actions] = useResourceActions();
3130
const [, { getContext: getRouterContext }] = useRouterStoreStatic();
3231

@@ -51,11 +50,19 @@ export const useResource = (
5150
);
5251

5352
const key = useKey(options?.routerContext!)[0];
54-
const [slice] = useResourceStore({ type: resource.type, key });
53+
const [slice] = useResourceStore({
54+
type: resource.type,
55+
key,
56+
}) as RouteResourceResponse<RouteResourceData>[];
5557

5658
const update = useCallback(
57-
(updater: RouteResourceUpdater) => {
58-
actions.updateResourceState(resource.type, key, resource.maxAge, updater);
59+
(updater: RouteResourceUpdater<RouteResourceData>) => {
60+
actions.updateResourceState(
61+
resource.type,
62+
key,
63+
resource.maxAge,
64+
updater as RouteResourceUpdater<unknown>
65+
);
5966
},
6067
[resource, key, actions]
6168
);
@@ -72,5 +79,5 @@ export const useResource = (
7279
actions.getResourceFromRemote(resource, routerContext);
7380
}, [resource, routerContext, actions]);
7481

75-
return { ...slice, update, refresh };
82+
return { ...slice, update, key, refresh };
7683
};

src/controllers/resource-store/index.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export const actions: Actions = {
105105
getResourceFromRemote: (resource, routerStoreContext) => async ({
106106
getState,
107107
dispatch,
108-
}): Promise<RouteResourceResponse> => {
108+
}): Promise<RouteResourceResponse<unknown>> => {
109109
const { type, getKey, getData, maxAge } = resource;
110110
const { setResourceState } = actions;
111111
const { data: resourceStoreData, context } = getState();
@@ -218,7 +218,7 @@ export const actions: Actions = {
218218
}
219219
const hydratedData = transformData(
220220
getNextStateValue<ResourceStoreData>(data, resourceData),
221-
({ error, ...rest }: RouteResourceResponse) => ({
221+
({ error, ...rest }) => ({
222222
...rest,
223223
error: !error ? null : deserializeError(error),
224224
})
@@ -240,12 +240,13 @@ export const actions: Actions = {
240240
getContext: () => ({ getState }) => getState().context,
241241

242242
/**
243-
* Returns safe, portable and reydratable data.
243+
* Returns safe, portable and rehydratable data.
244244
*
245245
*/
246246
getSafeData: () => ({ getState }) =>
247-
transformData(getState().data, ({ data, error }) => ({
247+
transformData(getState().data, ({ data, key, error }) => ({
248248
data,
249+
key,
249250
promise: null,
250251
expiresAt: null,
251252
error: !error
@@ -284,7 +285,7 @@ export const ResourceActions = createSubscriber<State, Actions, void>(
284285
export const ResourceSubscriber = createSubscriber<
285286
State,
286287
Actions,
287-
RouteResourceResponse,
288+
RouteResourceResponse<unknown>,
288289
{ resourceType: string; resourceKey: string }
289290
>(ResourceStore, {
290291
displayName: 'ResourceSelectorSubscriber',
@@ -302,7 +303,7 @@ export const getResourceStore = () =>
302303
export const useResourceStore = createHook<
303304
State,
304305
Actions,
305-
RouteResourceResponse,
306+
RouteResourceResponse<unknown>,
306307
ResourceSliceIdentifier
307308
>(ResourceStore, {
308309
selector: getSliceForResource,

src/controllers/resource-store/selectors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ export const getSliceForResource = (
1212
props: ResourceSliceIdentifier
1313
): RouteResourceResponse => {
1414
const { type, key } = props;
15-
const slice = state.data[type] && state.data[type][key];
15+
const slice =
16+
state.data[type] && (state.data[type][key] as RouteResourceResponse);
1617

1718
return slice ? { ...slice } : getDefaultStateSlice();
1819
};

src/controllers/resource-store/utils/transform-data/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import {
22
ResourceStoreData,
3-
RouteResourceResponse,
3+
RouteResourceResponseBase,
44
} from '../../../../common/types';
55

66
export const transformData = (
77
data: ResourceStoreData,
8-
transformer: (slice: RouteResourceResponse) => RouteResourceResponse
8+
transformer: (
9+
slice: RouteResourceResponseBase<unknown>
10+
) => RouteResourceResponseBase<unknown>
911
) =>
1012
Object.keys(data).reduce((acc: ResourceStoreData, type: string) => {
1113
if (!acc[type]) {

src/controllers/resource-utils/index.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,47 @@
1-
import { RouteResource, RouteResourceGettersArgs } from '../../common/types';
1+
import {
2+
RouteResource,
3+
RouteResourceDataPayload,
4+
RouteResourceGettersArgs,
5+
} from '../../common/types';
26
import { DEFAULT_RESOURCE_MAX_AGE } from '../resource-store/constants';
37

48
/**
59
* Utility method to created async versions of getData functions
610
*
711
*/
8-
type GetDataLoader = () => Promise<{
9-
default: RouteResource['getData'];
12+
type GetDataLoader<T> = () => Promise<{
13+
default: RouteResource<T>['getData'];
1014
}>;
1115

1216
type BaseResource = Pick<RouteResource, 'type' | 'getKey'>;
1317

14-
interface CreateResourceSync extends BaseResource {
15-
getData: RouteResource['getData'];
18+
interface CreateResourceSync<T> extends BaseResource {
19+
getData: RouteResource<T>['getData'];
1620
maxAge?: number;
1721
}
18-
interface CreateResourceAsync extends BaseResource {
22+
interface CreateResourceAsync<T> extends BaseResource {
1923
getDataLoader: (
2024
...args: RouteResourceGettersArgs
2125
) => Promise<{
22-
default: GetDataLoader;
26+
default: GetDataLoader<T>;
2327
}>;
2428
maxAge?: number;
2529
}
2630

27-
const handleGetDataLoader = (asyncImport: GetDataLoader) => {
31+
const handleGetDataLoader = (asyncImport: GetDataLoader<unknown>) => {
2832
return async (...args: RouteResourceGettersArgs) => {
2933
const { default: getDataFn } = await asyncImport();
3034

3135
return getDataFn(...args);
3236
};
3337
};
3438

35-
export function createResource(args: CreateResourceSync): RouteResource;
36-
export function createResource(args: CreateResourceAsync): RouteResource;
39+
export function createResource<T extends unknown = RouteResourceDataPayload>(
40+
args: CreateResourceSync<T>
41+
): RouteResource<T>;
42+
export function createResource<T extends unknown = RouteResourceDataPayload>(
43+
args: CreateResourceAsync<T>
44+
): RouteResource<T>;
3745
export function createResource(args: any) {
3846
return {
3947
type: args.type,

0 commit comments

Comments
 (0)