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

Update useGetPlacesQuery #798

Draft
wants to merge 8 commits into
base: type-authenticated-query-correctly
Choose a base branch
from
Draft
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
103 changes: 65 additions & 38 deletions src/hooks/api/authenticated-query-v2.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { NextApiRequest } from 'next';
import { useRouter } from 'next/router';
import { GetServerSidePropsContext } from 'next/types';
import { Cookies } from 'react-cookie';
import {
FetchQueryOptions,
QueryClient,
QueryFunctionContext,
QueryKey,
useQuery,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import { QueryState } from 'react-query/types/core/query';

import { FetchError } from '@/utils/fetchFromApi';
import { isTokenValid } from '@/utils/isTokenValid';
Expand All @@ -19,67 +20,68 @@ import { createHeaders, useHeaders } from './useHeaders';

type QueryArguments = Record<string, string>;

type GenerateQueryKeyArguments = {
queryKey: QueryKey;
queryArguments: QueryArguments;
};

type GeneratedQueryKey = readonly [QueryKey, QueryArguments];

type AuthenticatedQueryFunctionContext<TQueryArguments = unknown> =
QueryFunctionContext<GeneratedQueryKey> & {
headers: HeadersInit;
queryArguments?: TQueryArguments;
queryArguments: TQueryArguments;
};

type ServerSideOptions = {
req: NextApiRequest;
req: GetServerSidePropsContext['req'];
queryClient: QueryClient;
};

type PrefetchAuthenticatedQueryOptions<TQueryFnData> = {
queryArguments?: QueryArguments;
} & ServerSideOptions &
FetchQueryOptions<TQueryFnData, FetchError, TQueryFnData, QueryKey>;

type UseAuthenticatedQueryOptions<
TQueryFnData,
TQueryArguments = QueryArguments,
export type UseAuthenticatedQueryOptions<
TQueryFnData = unknown,
TQueryArguments extends Object = {},
> = {
queryArguments?: TQueryArguments;
} & Omit<
UseQueryOptions<TQueryFnData, FetchError, TQueryFnData, QueryKey>,
'queryFn'
> & {
queryFn: (
context: AuthenticatedQueryFunctionContext,
queryArguments: TQueryArguments;
} & Omit<UseQueryOptions<TQueryFnData, FetchError, TQueryFnData>, 'queryFn'> & {
queryFn?: (
context: AuthenticatedQueryFunctionContext<TQueryArguments>,
) => TQueryFnData | Promise<TQueryFnData>;
};

export type UseServerSideAuthenticatedQueryOptions<
TQueryFnData = unknown,
TQueryArguments = unknown,
> = UseAuthenticatedQueryOptions<TQueryFnData, TQueryArguments> &
ServerSideOptions;

export type UseAuthenticatedQueryWrapperOptions<
TQueryData = unknown,
TQueryArguments = unknown,
> = Omit<UseAuthenticatedQueryOptions<TQueryData, TQueryArguments>, 'queryFn'>;

const isUnAuthorized = (status: number) => [401, 403].includes(status);

const generateQueryKey = ({
const generateQueryKey = <K, A>({
queryKey,
queryArguments,
}: GenerateQueryKeyArguments): GeneratedQueryKey => {
}: {
queryKey: K;
queryArguments: A;
}): [K, A] => {
if (Object.keys(queryArguments ?? {}).length > 0) {
return [queryKey, queryArguments];
}

return [queryKey, {}];
return [queryKey, {} as A];
};

type GetPreparedOptionsArguments<TQueryFnData> = {
options: UseAuthenticatedQueryOptions<TQueryFnData>;
type GetPreparedOptionsArguments<TQueryFnData, TQueryArguments> = {
options: UseAuthenticatedQueryOptions<TQueryFnData, TQueryArguments>;
isTokenPresent: boolean;
headers: Headers;
};

const getPreparedOptions = <TQueryFnData = unknown>({
const getPreparedOptions = <TQueryFnData = unknown, TQueryArguments = unknown>({
options,
isTokenPresent,
headers,
}: GetPreparedOptionsArguments<TQueryFnData>) => {
}: GetPreparedOptionsArguments<TQueryFnData, TQueryArguments>) => {
const { queryKey, queryArguments, queryFn, ...restOptions } = options;
const generatedQueryKey = generateQueryKey({
queryKey,
Expand All @@ -101,7 +103,7 @@ const prefetchAuthenticatedQuery = async <TQueryFnData = unknown>({
req,
queryClient,
...options
}: PrefetchAuthenticatedQueryOptions<TQueryFnData>) => {
}: UseServerSideAuthenticatedQueryOptions<TQueryFnData>) => {
if (typeof window !== 'undefined') {
throw new Error('Only use prefetchAuthenticatedQuery in server-side code');
}
Expand All @@ -110,7 +112,6 @@ const prefetchAuthenticatedQuery = async <TQueryFnData = unknown>({
const headers = createHeaders(cookies.get('token'));

const { queryKey, queryFn } = getPreparedOptions<TQueryFnData>({
// @ts-expect-error
options,
isTokenPresent: isTokenValid(cookies.get('token')),
headers,
Expand All @@ -123,12 +124,38 @@ const prefetchAuthenticatedQuery = async <TQueryFnData = unknown>({
);
} catch {}

return await queryClient.getQueryData<TQueryFnData>(queryKey);
return queryClient.getQueryState<TQueryFnData>(queryKey);
};

const useAuthenticatedQuery = <TQueryFnData = unknown>(
options: UseAuthenticatedQueryOptions<TQueryFnData>,
) => {
function useAuthenticatedQuery<
TQueryFnData = unknown,
TQueryArguments = unknown,
>(
options: UseServerSideAuthenticatedQueryOptions<
TQueryFnData,
TQueryArguments
>,
): Promise<QueryState<TQueryFnData>>;
function useAuthenticatedQuery<
TQueryFnData = unknown,
TQueryArguments = unknown,
>(
options: UseAuthenticatedQueryOptions<TQueryFnData, TQueryArguments>,
): UseQueryResult<TQueryFnData, FetchError>;
function useAuthenticatedQuery<
TQueryFnData = unknown,
TQueryArguments = unknown,
>(
options:
| UseServerSideAuthenticatedQueryOptions<TQueryFnData, TQueryArguments>
| UseAuthenticatedQueryOptions<TQueryFnData, TQueryArguments>,
):
| Promise<QueryState<TQueryFnData>>
| UseQueryResult<TQueryFnData, FetchError> {
if ('req' in options) {
return prefetchAuthenticatedQuery(options);
}

const headers = useHeaders();
const { cookies, removeAuthenticationCookies } = useCookiesWithOptions([
'token',
Expand Down Expand Up @@ -156,7 +183,7 @@ const useAuthenticatedQuery = <TQueryFnData = unknown>(
}

return result;
};
}

export { prefetchAuthenticatedQuery, useAuthenticatedQuery };
export type { AuthenticatedQueryFunctionContext };
3 changes: 2 additions & 1 deletion src/hooks/api/authenticated-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { isEqual } from 'lodash';
import flatten from 'lodash/flatten';
import type { NextApiRequest } from 'next';
import { useRouter } from 'next/router';
import { GetServerSidePropsContext } from 'next/types';
import { useCallback } from 'react';
import { Cookies } from 'react-cookie';
import {
Expand All @@ -21,7 +22,7 @@ import { isTokenValid } from '@/utils/isTokenValid';
import { createHeaders, useHeaders } from './useHeaders';

type ServerSideQueryOptions = {
req?: NextApiRequest;
req?: GetServerSidePropsContext['req'];
queryClient?: QueryClient;
};

Expand Down
7 changes: 6 additions & 1 deletion src/hooks/api/offers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,13 @@ const useGetOffersByCreatorQuery = (
const useGetOfferByIdQuery = ({ scope, id }, configuration = {}) => {
const query =
scope === OfferTypes.EVENTS ? useGetEventByIdQuery : useGetPlaceByIdQuery;
const args =
scope === OfferTypes.EVENTS
? [{ id, scope }, configuration]
: [{ queryArguments: { id, scope }, ...configuration }];

return query({ id, scope }, configuration);
// @ts-expect-error
return query(...args);
};

const changeOfferName = async ({ headers, id, lang, name, scope }) => {
Expand Down
84 changes: 42 additions & 42 deletions src/hooks/api/places.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ import {
useAuthenticatedMutation,
useAuthenticatedQuery,
} from './authenticated-query';
import {
AuthenticatedQueryFunctionContext,
useAuthenticatedQuery as useAuthenticatedQueryV2,
UseAuthenticatedQueryOptions,
UseAuthenticatedQueryWrapperOptions,
} from './authenticated-query-v2';
import type { Headers } from './types/Headers';
import type { User } from './user';

const getPlaceById = async ({ headers, id }) => {
const getPlaceById = async ({ headers, queryArguments: { id } }) => {
const res = await fetchFromApi({
path: `/places/${id.toString()}`,
options: {
Expand All @@ -38,29 +44,25 @@ const getPlaceById = async ({ headers, id }) => {
});
if (isErrorObject(res)) {
// eslint-disable-next-line no-console
return console.error(res);
console.error(res);
return;
}
return await res.json();
return (await res.json()) as Place;
};

type UseGetPlaceByIdArguments = ServerSideQueryOptions & {
id: string;
scope?: Values<typeof OfferTypes>;
};

const useGetPlaceByIdQuery = (
{ req, queryClient, id, scope }: UseGetPlaceByIdArguments,
configuration: UseQueryOptions = {},
) =>
useAuthenticatedQuery({
req,
queryClient,
const useGetPlaceByIdQuery = wrap(
useAuthenticatedQueryV2<Place, UseGetPlaceByIdArguments>,
({ queryArguments: { id, scope } }) => ({
queryKey: ['places'],
queryFn: getPlaceById,
queryArguments: { id },
enabled: !!id && scope === OfferTypes.PLACES,
...configuration,
});
}),
);

const getPlacesByCreator = async ({ headers, ...queryData }) => {
delete headers['Authorization'];
Expand Down Expand Up @@ -123,20 +125,22 @@ const useGetPlacesByCreatorQuery = (

type GetPlacesByQueryArguments = {
name: string;
terms: Array<Values<typeof EventTypes>>;
terms: Values<typeof EventTypes>[];
zip?: string;
addressLocality?: string;
addressCountry?: Country;
};

const getPlacesByQuery = async ({
headers,
name,
terms,
zip,
addressLocality,
addressCountry,
}: Headers & GetPlacesByQueryArguments) => {
queryArguments: {
name,
terms,
zip = null,
addressLocality = null,
addressCountry = null,
},
}) => {
const termsString = terms.reduce(
(acc, currentTerm) => `${acc}terms.id:${currentTerm}`,
'',
Expand Down Expand Up @@ -169,34 +173,30 @@ const getPlacesByQuery = async ({

if (isErrorObject(res)) {
// eslint-disable-next-line no-console
return console.error(res);
console.error(res);
return;
}

return await res.json();
};

const useGetPlacesByQuery = (
{
name,
terms,
zip,
addressLocality,
addressCountry,
}: GetPlacesByQueryArguments,
configuration = {},
) =>
useAuthenticatedQuery<Place[]>({
function wrap<T extends typeof useAuthenticatedQueryV2>(
fn: T,
outerArgs: any = {},
): T {
return <any>function (args) {
return fn({ ...args, ...outerArgs(args) });
};
}

const useGetPlacesByQuery = wrap(
useAuthenticatedQueryV2<{ member: Place[] }, GetPlacesByQueryArguments>,
({ enabled, queryArguments: { name, terms } }) => ({
queryKey: ['places'],
queryFn: getPlacesByQuery,
queryArguments: {
name,
terms,
zip,
addressCountry,
addressLocality,
},
enabled: !!name || terms.length,
...configuration,
});
enabled: enabled && (!!name || terms.length > 0),
}),
);

const changeAddress = async ({ headers, id, address, language }) =>
fetchFromApi({
Expand Down
5 changes: 4 additions & 1 deletion src/pages/PlaceAddModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ const PlaceAddModal = ({

if (!resp?.placeId) return;

const newPlace = await getPlaceById({ headers, id: resp.placeId });
const newPlace = await getPlaceById({
headers,
queryArguments: { id: resp.placeId },
});
onConfirmSuccess(newPlace);
})();
};
Expand Down
13 changes: 7 additions & 6 deletions src/pages/dashboard/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -730,23 +730,24 @@ const Dashboard = (): any => {

const getServerSideProps = getApplicationServerSideProps(
async ({ req, query, cookies: rawCookies, queryClient }) => {
const user = (await useGetUserQueryServerSide({
const user = await useGetUserQueryServerSide({
req,
queryClient,
})) as User;
});

await Promise.all(
Object.entries(UseGetItemsByCreatorMap).map(([key, hook]) => {
const page =
query.tab === key ? (query.page ? parseInt(query.page) : 1) : 1;
query.tab === key ? parseInt((query.page as string) ?? '1') : 1;

const sortingField = query?.sort?.split('_')[0] ?? SortingField.CREATED;
const sortingOrder = query?.sort?.split('_')[1] ?? SortingOrder.DESC;
const sort = (query?.sort ?? '') as string;
const sortingField = sort.split('_')[0] ?? SortingField.CREATED;
const sortingOrder = sort.split('_')[1] ?? SortingOrder.DESC;

return hook({
req,
queryClient,
creator: user,
creator: user as User,
...(key === 'events' && {
sortOptions: {
field: sortingField,
Expand Down
Loading