Skip to content

Commit

Permalink
feat(react-query): update to major v5
Browse files Browse the repository at this point in the history
  • Loading branch information
DakEnviy committed Aug 11, 2024
1 parent 4a074fd commit 0f7fbdf
Show file tree
Hide file tree
Showing 19 changed files with 328 additions and 270 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release-alpha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
version:
type: string
required: false
description: If your build failed and the version is already exists you can set version of package manually, e.g. `3.0.0-alpha.0``. Use the prefix `alpha` otherwise you will get error.
description: If your build failed and the version is already exists you can set version of package manually, e.g. `3.0.0-alpha.0`. Use the prefix `alpha` otherwise you will get error.

jobs:
release:
Expand Down
327 changes: 147 additions & 180 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@gravity-ui/prettier-config": "^1.1.0",
"@gravity-ui/tsconfig": "^1.0.0",
"@swc/jest": "^0.2.36",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query": "^5.51.23",
"@types/jest": "^29.5.12",
"@types/react": "^18.3.3",
"eslint": "^8.57.0",
Expand All @@ -63,7 +63,7 @@
"react": "^18.3.1"
},
"peerDependencies": {
"@tanstack/react-query": "^4.0.0",
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
"@tanstack/react-query": "^5.0.0",
"react": "^18.0.0"
}
}
8 changes: 4 additions & 4 deletions src/core/types/DataSource.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {idle} from '../constants';

export type DataSourceKey = readonly unknown[];
export type DataSourceKey = ReadonlyArray<unknown>;
export type DataSourceTag = string;

declare const errorHintSymbol: unique symbol;
Expand All @@ -9,11 +9,11 @@ declare const stateHintSymbol: unique symbol;
export interface DataSource<
TContext,
TParams,
TRequest,
TRequest extends object,
TResponse,
TData,
TError,
TOptions,
TOptions extends object,
TState,
TFetchContext,
> {
Expand All @@ -31,7 +31,7 @@ export interface DataSource<

[errorHintSymbol]?: TError;

options?: TOptions;
options?: Partial<TOptions>;
[stateHintSymbol]?: TState;
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/utils/composeFullKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {composeKey} from './composeKey';
export const composeFullKey = <TDataSource extends AnyDataSource>(
dataSource: TDataSource,
params: DataSourceParams<TDataSource>,
): readonly string[] => {
): ReadonlyArray<string> => {
const tags = dataSource.tags?.(params) ?? [];

return [dataSource.name, ...tags, composeKey(dataSource, params)];
Expand Down
4 changes: 2 additions & 2 deletions src/core/utils/composeKey.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// TODO(DakEnviy): Do not use react-query in core
import {hashQueryKey} from '@tanstack/react-query';
import {hashKey} from '@tanstack/react-query';

import {idle} from '../constants';
import type {AnyDataSource, DataSourceParams} from '../types/DataSource';
Expand All @@ -8,4 +8,4 @@ export const composeKey = <TDataSource extends AnyDataSource>(
dataSource: TDataSource,
params: DataSourceParams<TDataSource>,
): string =>
params === idle ? `${dataSource.name}:idle` : `${dataSource.name}(${hashQueryKey(params)})`;
params === idle ? `${dataSource.name}:idle` : `${dataSource.name}(${hashKey(params)})`;
19 changes: 14 additions & 5 deletions src/react-query/hooks/useQueryData.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import type {DataSourceOptions, DataSourceParams, DataSourceState} from '../../core';
import {useInfiniteQueryData} from '../impl/infinite/hooks';
import type {AnyInfiniteQueryDataSource} from '../impl/infinite/types';
import {usePlainQueryData} from '../impl/plain/hooks';
import type {AnyQueryDataSource} from '../types';
import {notReachable} from '../utils/notReachable';

import {useQueryContext} from './useQueryContext';

export const useQueryData = <TDataSource extends AnyQueryDataSource>(
dataSource: TDataSource,
params: DataSourceParams<TDataSource>,
options?: DataSourceOptions<TDataSource>,
options?: Partial<DataSourceOptions<TDataSource>>,
): DataSourceState<TDataSource> => {
const context = useQueryContext();

const type = dataSource.type;
let state: DataSourceState<AnyQueryDataSource> | undefined;

// Do not change data source type in the same hook call
if (dataSource.type === 'plain') {
if (type === 'plain') {
// eslint-disable-next-line react-hooks/rules-of-hooks
state = usePlainQueryData(context, dataSource, params, options);
} else if (dataSource.type === 'infinite') {
} else if (type === 'infinite') {
// eslint-disable-next-line react-hooks/rules-of-hooks
state = useInfiniteQueryData(context, dataSource, params, options);
state = useInfiniteQueryData(
context,
dataSource,
params,
// TS can't calculate types in this place
options as Partial<DataSourceOptions<AnyInfiniteQueryDataSource>> | undefined,
);
} else {
throw new Error('Data Source type must be plain or infinite');
return notReachable(type, `Data Source type must be plain or infinite, got: ${type}`);
}

return state as DataSourceState<TDataSource>;
Expand Down
8 changes: 7 additions & 1 deletion src/react-query/impl/infinite/factory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type {InfiniteQueryDataSource} from './types';

export const makeInfiniteQueryDataSource = <TParams, TRequest, TResponse, TData, TError>(
export const makeInfiniteQueryDataSource = <
TParams,
TRequest extends object,
TResponse,
TData,
TError,
>(
config: Omit<InfiniteQueryDataSource<TParams, TRequest, TResponse, TData, TError>, 'type'>,
): InfiniteQueryDataSource<TParams, TRequest, TResponse, TData, TError> => ({
...config,
Expand Down
15 changes: 9 additions & 6 deletions src/react-query/impl/infinite/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {useMemo} from 'react';

import {useInfiniteQuery} from '@tanstack/react-query';

import type {DataSourceContext, DataSourceOptions, DataSourceParams} from '../../../core';
import type {
DataSourceContext,
DataSourceOptions,
DataSourceParams,
DataSourceState,
} from '../../../core';

import type {AnyInfiniteQueryDataSource} from './types';
import {composeOptions, transformResult} from './utils';
Expand All @@ -11,10 +14,10 @@ export const useInfiniteQueryData = <TDataSource extends AnyInfiniteQueryDataSou
context: DataSourceContext<TDataSource>,
dataSource: TDataSource,
params: DataSourceParams<TDataSource>,
options?: DataSourceOptions<TDataSource>,
) => {
options?: Partial<DataSourceOptions<TDataSource>>,
): DataSourceState<TDataSource> => {
const composedOptions = composeOptions(context, dataSource, params, options);
const result = useInfiniteQuery(composedOptions);

return useMemo(() => transformResult(result), [result]);
return transformResult(result);
};
58 changes: 47 additions & 11 deletions src/react-query/impl/infinite/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
InfiniteData,
InfiniteQueryObserverOptions,
InfiniteQueryObserverResult,
QueryFunctionContext,
Expand All @@ -8,29 +9,64 @@ import type {Overwrite} from 'utility-types';
import type {ActualData, DataLoaderStatus, DataSource, DataSourceKey} from '../../../core';
import type {QueryDataSourceContext} from '../../types';

export type InfiniteQueryDataSource<TParams, TRequest, TResponse, TData, TError> = DataSource<
export type InfiniteQueryDataSource<
TParams,
TRequest extends object,
TResponse,
TData,
TError,
> = DataSource<
QueryDataSourceContext,
TParams,
TRequest,
TResponse,
TData,
TError,
InfiniteQueryObserverOptions<TResponse, TError, ActualData<TData, TResponse>, TResponse>,
ResultWrapper<InfiniteQueryObserverResult<ActualData<TData, TResponse>, TError>>,
QueryFunctionContext<DataSourceKey, Partial<TRequest> | undefined>
InfiniteQueryObserverOptions<
TResponse,
TError,
InfiniteData<ActualData<TData, TResponse>, Partial<TRequest>>,
TResponse,
DataSourceKey,
Partial<TRequest>
>,
ResultWrapper<
InfiniteQueryObserverResult<
InfiniteData<ActualData<TData, TResponse>, Partial<TRequest>>,
TError
>,
TRequest,
TResponse,
TData,
TError
>,
QueryFunctionContext<DataSourceKey, Partial<TRequest>>
> & {
type: 'infinite';
next: (lastPage: TResponse, allPages: TResponse[]) => Partial<TRequest> | undefined;
prev?: (firstPage: TResponse, allPages: TResponse[]) => Partial<TRequest> | undefined;
next: (lastPage: TResponse, allPages: TResponse[]) => Partial<TRequest> | null | undefined;
prev?: (firstPage: TResponse, allPages: TResponse[]) => Partial<TRequest> | null | undefined;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AnyInfiniteQueryDataSource = InfiniteQueryDataSource<any, any, any, any, any>;

type ResultWrapper<T> =
T extends InfiniteQueryObserverResult<infer TData>
? Overwrite<T, {status: DataLoaderStatus; data: TData}> & {
originalStatus: T['status'];
originalData: T['data'];
// It is used instead of `Partial<DataSourceRequest<TDataSource>>` because TS can't calculate type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AnyPageParam = Partial<any>;

type ResultWrapper<TResult, TRequest, TResponse, TData, TError> =
TResult extends InfiniteQueryObserverResult<
InfiniteData<ActualData<TData, TResponse>, Partial<TRequest>>,
TError
>
? Overwrite<
TResult,
{status: DataLoaderStatus; data: DataWrapper<ActualData<TData, TResponse>>}
> & {
originalStatus: TResult['status'];
originalData: TResult['data'];
}
: never;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type DataWrapper<TActualData> = TActualData extends any[] ? TActualData : TActualData[];
51 changes: 27 additions & 24 deletions src/react-query/impl/infinite/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {skipToken} from '@tanstack/react-query';
import type {
InfiniteData,
InfiniteQueryObserverOptions,
InfiniteQueryObserverResult,
QueryFunctionContext,
Expand All @@ -12,63 +14,64 @@ import type {
DataSourceKey,
DataSourceOptions,
DataSourceParams,
DataSourceRequest,
DataSourceResponse,
DataSourceState,
} from '../../../core';
import {normalizeStatus} from '../../utils/normalizeStatus';

import type {AnyInfiniteQueryDataSource} from './types';
import type {AnyInfiniteQueryDataSource, AnyPageParam} from './types';

const EMPTY_ARRAY: unknown[] = [];
const EMPTY_OBJECT = {};

export const composeOptions = <TDataSource extends AnyInfiniteQueryDataSource>(
context: DataSourceContext<TDataSource>,
dataSource: TDataSource,
params: DataSourceParams<TDataSource>,
options?: DataSourceOptions<TDataSource>,
options?: Partial<DataSourceOptions<TDataSource>>,
): InfiniteQueryObserverOptions<
DataSourceResponse<TDataSource>,
DataSourceError<TDataSource>,
DataSourceData<TDataSource>,
DataSourceResponse<TDataSource>
InfiniteData<DataSourceData<TDataSource>, AnyPageParam>,
DataSourceResponse<TDataSource>,
DataSourceKey,
AnyPageParam
> => {
const {transformParams, transformResponse, next, prev} = dataSource;

const queryFn = (
fetchContext: QueryFunctionContext<DataSourceKey, AnyPageParam>,
): DataSourceResponse<TDataSource> | Promise<DataSourceResponse<TDataSource>> => {
const request = transformParams ? transformParams(params) : params;
const paginatedRequest = {...request, ...fetchContext.pageParam};

return dataSource.fetch(context, fetchContext, paginatedRequest);
};

return {
...dataSource.options,
enabled: params !== idle,
queryKey: composeFullKey(dataSource, params),
queryFn: (
fetchContext: QueryFunctionContext<
DataSourceKey,
Partial<DataSourceRequest<TDataSource>> | undefined
>,
) => {
const actualParams = transformParams ? transformParams(params) : params;
const request =
typeof actualParams === 'object'
? {...actualParams, ...fetchContext.pageParam}
: actualParams;

return dataSource.fetch(context, fetchContext, request);
},
queryFn: params === idle ? skipToken : queryFn,
select: transformResponse
? (data) => ({...data, pages: data.pages.map(transformResponse)})
: undefined,
initialPageParam: EMPTY_OBJECT,
getNextPageParam: next,
getPreviousPageParam: prev,
...dataSource.options,
...options,
};
};

export const transformResult = <TDataSource extends AnyInfiniteQueryDataSource>(
result: InfiniteQueryObserverResult<DataSourceData<TDataSource>, DataSourceError<TDataSource>>,
) => {
result: InfiniteQueryObserverResult<
InfiniteData<DataSourceData<TDataSource>, AnyPageParam>,
DataSourceError<TDataSource>
>,
): DataSourceState<TDataSource> => {
return {
...result,
status: normalizeStatus(result.status, result.fetchStatus),
data: result.data?.pages.flat() ?? EMPTY_ARRAY,
data: result.data?.pages.flat(1) ?? EMPTY_ARRAY,
originalStatus: result.status,
originalData: result.data,
} as DataSourceState<TDataSource>;
Expand Down
8 changes: 7 additions & 1 deletion src/react-query/impl/plain/factory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type {PlainQueryDataSource} from './types';

export const makePlainQueryDataSource = <TParams, TRequest, TResponse, TData, TError>(
export const makePlainQueryDataSource = <
TParams,
TRequest extends object,
TResponse,
TData,
TError,
>(
config: Omit<PlainQueryDataSource<TParams, TRequest, TResponse, TData, TError>, 'type'>,
): PlainQueryDataSource<TParams, TRequest, TResponse, TData, TError> => ({
...config,
Expand Down
15 changes: 9 additions & 6 deletions src/react-query/impl/plain/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {useMemo} from 'react';

import {useQuery} from '@tanstack/react-query';

import type {DataSourceContext, DataSourceOptions, DataSourceParams} from '../../../core';
import type {
DataSourceContext,
DataSourceOptions,
DataSourceParams,
DataSourceState,
} from '../../../core';

import type {AnyPlainQueryDataSource} from './types';
import {composeOptions, transformResult} from './utils';
Expand All @@ -11,10 +14,10 @@ export const usePlainQueryData = <TDataSource extends AnyPlainQueryDataSource>(
context: DataSourceContext<TDataSource>,
dataSource: TDataSource,
params: DataSourceParams<TDataSource>,
options?: DataSourceOptions<TDataSource>,
) => {
options?: Partial<DataSourceOptions<TDataSource>>,
): DataSourceState<TDataSource> => {
const composedOptions = composeOptions(context, dataSource, params, options);
const result = useQuery(composedOptions);

return useMemo(() => transformResult(result), [result]);
return transformResult(result);
};
Loading

0 comments on commit 0f7fbdf

Please sign in to comment.