Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(react-query): update to major v5 #13

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
}
4 changes: 2 additions & 2 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 Down Expand Up @@ -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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need todo?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, just a reminder to find another function for hashing in the future

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
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> => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move partial type to DataSourceOptions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that having origin options can be useful

const composedOptions = composeOptions(context, dataSource, params, options);
const result = useInfiniteQuery(composedOptions);

return useMemo(() => transformResult(result), [result]);
return transformResult(result);
};
50 changes: 40 additions & 10 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 @@ -15,22 +16,51 @@ export type InfiniteQueryDataSource<TParams, TRequest, TResponse, TData, TError>
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: Array<FlatArray<Array<ActualData<TData, TResponse>>, 1>>;
}
> & {
originalStatus: TResult['status'];
originalData: TResult['data'];
}
: never;
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
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);
};
18 changes: 12 additions & 6 deletions src/react-query/impl/plain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,22 @@ export type PlainQueryDataSource<TParams, TRequest, TResponse, TData, TError> =
TResponse,
TData,
TError,
QueryObserverOptions<TResponse, TError, ActualData<TData, TResponse>, TResponse>,
ResultWrapper<QueryObserverResult<ActualData<TData, TResponse>, TError>>,
QueryFunctionContext<DataSourceKey, unknown>
QueryObserverOptions<TResponse, TError, ActualData<TData, TResponse>, TResponse, DataSourceKey>,
ResultWrapper<
QueryObserverResult<ActualData<TData, TResponse>, TError>,
TResponse,
TData,
TError
>,
QueryFunctionContext<DataSourceKey>
> & {
type: 'plain';
};

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

type ResultWrapper<T> = T extends QueryObserverResult
? Overwrite<T, {status: DataLoaderStatus}> & {originalStatus: T['status']}
: never;
type ResultWrapper<TResult, TResponse, TData, TError> =
TResult extends QueryObserverResult<ActualData<TData, TResponse>, TError>
? Overwrite<TResult, {status: DataLoaderStatus}> & {originalStatus: TResult['status']}
: never;
Loading
Loading