From 8ffb944ae0334222b9b9ae2b5f8f40d9e5ed52b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Thu, 21 Mar 2024 13:45:51 -0300 Subject: [PATCH] feat: prepareFetchHookData --- .changeset/friendly-kids-beam.md | 5 + packages/next/src/data/convertToPath.ts | 2 +- .../data/server/__tests__/fetchHookData.ts | 97 +++++++++++++++- .../next/src/data/server/fetchHookData.ts | 106 +++++++++++------- 4 files changed, 167 insertions(+), 43 deletions(-) create mode 100644 .changeset/friendly-kids-beam.md diff --git a/.changeset/friendly-kids-beam.md b/.changeset/friendly-kids-beam.md new file mode 100644 index 000000000..5983bd890 --- /dev/null +++ b/.changeset/friendly-kids-beam.md @@ -0,0 +1,5 @@ +--- +"@headstartwp/next": patch +--- + +Introducing `prepareFetchHookData` and making it public as it can be useful for projects customizing `fetchHookData` behavior. diff --git a/packages/next/src/data/convertToPath.ts b/packages/next/src/data/convertToPath.ts index 7f1b0ae7d..8c4941955 100644 --- a/packages/next/src/data/convertToPath.ts +++ b/packages/next/src/data/convertToPath.ts @@ -10,5 +10,5 @@ export function convertToPath(args: string[] | undefined) { return '/'; } - return `/${args.join('/')}`; + return `/${args.filter((a) => a.length > 0).join('/')}`; } diff --git a/packages/next/src/data/server/__tests__/fetchHookData.ts b/packages/next/src/data/server/__tests__/fetchHookData.ts index 6aa38821a..68e444bcf 100644 --- a/packages/next/src/data/server/__tests__/fetchHookData.ts +++ b/packages/next/src/data/server/__tests__/fetchHookData.ts @@ -6,7 +6,7 @@ import { useSearch } from '../../hooks/useSearch'; import { useTerms } from '../../hooks/useTerms'; import { usePost } from '../../hooks/usePost'; import { usePostOrPosts } from '../../hooks/usePostOrPosts'; -import { fetchHookData } from '../fetchHookData'; +import { fetchHookData, prepareFetchHookData } from '../fetchHookData'; import { addHookData } from '../addHookData'; test('fetchHookData types', async () => { @@ -31,6 +31,101 @@ test('fetchHookData types', async () => { ).toMatchTypeOf(); }); +describe('prepareFetchHookData', () => { + it('builds path correctly', () => { + const { path } = prepareFetchHookData(usePosts.fetcher(), { + params: { + path: ['2024', '01', '02', 'page-name'], + }, + }); + + expect(path).toBe('/2024/01/02/page-name'); + + const { path: path2 } = prepareFetchHookData(usePosts.fetcher(), { + params: { + path: ['2024', '01', '02', 'page-name', '', ''], + }, + }); + + expect(path2).toBe('/2024/01/02/page-name'); + }); + + it('returns url params properly', () => { + const { urlParams } = prepareFetchHookData(usePost.fetcher(), { + params: { + path: ['2024', '01', '02', 'page-name'], + }, + }); + + expect(urlParams).toMatchObject({ slug: 'page-name' }); + + const { urlParams: urlParams2 } = prepareFetchHookData(usePosts.fetcher(), { + params: { + path: ['category', 'news'], + }, + }); + + expect(urlParams2).toMatchObject({ category: 'news' }); + }); + + it('returns params properly', () => { + setHeadstartWPConfig({ + useWordPressPlugin: true, + integrations: { + polylang: { + enable: true, + }, + }, + }); + + const { params } = prepareFetchHookData( + usePost.fetcher(), + { + // params from next.js ctx + params: { + path: ['2024', '01', '02', 'page-name'], + }, + locale: 'pt', + }, + { + // user passed params + params: { + id: 1, + }, + }, + ); + + expect(params).toEqual({ slug: 'page-name', _embed: true, id: 1, lang: 'pt' }); + + setHeadstartWPConfig({ + useWordPressPlugin: true, + }); + }); + + it('returns cacheKey properly', () => { + const { cacheKey } = prepareFetchHookData( + usePost.fetcher(), + { + // params from next.js ctx + params: { + path: ['2024', '01', '02', 'page-name'], + }, + }, + { + // user passed params + params: { + id: 1, + }, + }, + ); + + expect(cacheKey).toMatchObject({ + args: { _embed: true, id: 1, slug: 'page-name', sourceUrl: '' }, + url: '/wp-json/wp/v2/posts', + }); + }); +}); + describe('fetchHookData', () => { it('handles additionalCacheObjects', async () => { setHeadstartWPConfig({ diff --git a/packages/next/src/data/server/fetchHookData.ts b/packages/next/src/data/server/fetchHookData.ts index 655abf385..f5eb0b488 100644 --- a/packages/next/src/data/server/fetchHookData.ts +++ b/packages/next/src/data/server/fetchHookData.ts @@ -49,6 +49,49 @@ function isPreviewRequest

(params: P, urlParams: P): params is P & PostParams return isPreviewRequest; } +/** + * Prepares all the things for fetchHookData + * + * @param fetchStrategy The fetch strategy to use. Typically this is exposed by the hook e.g: `usePosts.fetcher()` + * @param ctx The Next.js context, either the one from `getServerSideProps` or `getStaticProps` + * @param options See {@link FetchHookDataOptions} + * + * @returns The various things fetchHookData needs + */ +export function prepareFetchHookData( + fetchStrategy: AbstractFetchStrategy, + ctx: GetServerSidePropsContext | GetStaticPropsContext, + options: FetchHookDataOptions = {}, +) { + const { sourceUrl, integrations } = getSiteFromContext(ctx); + const params: Partial

= options?.params || {}; + + fetchStrategy.setBaseURL(sourceUrl); + + let path: string[] = []; + + if (ctx.params) { + path = Array.isArray(ctx.params.path) ? ctx.params.path : [ctx.params.path || '']; + } + + if (integrations?.polylang?.enable && (ctx.locale || ctx.defaultLocale)) { + params.lang = ctx.locale ?? ctx.defaultLocale; + } + + const stringPath = convertToPath(path); + const defaultParams = fetchStrategy.getDefaultParams(); + const urlParams = fetchStrategy.getParamsFromURL(stringPath, params); + + const finalParams = deepmerge.all([defaultParams, urlParams, params]) as Partial

; + + return { + cacheKey: fetchStrategy.getCacheKey(finalParams), + params: finalParams, + urlParams, + path: stringPath, + }; +} + /** * A function that implements data fetching on the server. This should be used in `getServerSideProps` * or `getStaticProps`. @@ -67,7 +110,7 @@ function isPreviewRequest

(params: P, urlParams: P): params is P & PostParams * } catch (e) { * return handleError(e, context); * } - * } + * }ยด * ``` * * @param fetchStrategy The fetch strategy to use. Typically this is exposed by the hook e.g: `usePosts.fetcher()` @@ -83,43 +126,28 @@ export async function fetchHookData | GetStaticPropsContext, options: FetchHookDataOptions = {}, ) { - const { sourceUrl, integrations, debug, preview } = getSiteFromContext(ctx); - const params: Partial

= options?.params || {}; - - fetchStrategy.setBaseURL(sourceUrl); + const { + cacheKey: key, + params, + urlParams, + path, + } = prepareFetchHookData(fetchStrategy, ctx, options); - let path: string[] = []; - - if (ctx.params) { - path = Array.isArray(ctx.params.path) ? ctx.params.path : [ctx.params.path || '']; - } - - if (integrations?.polylang?.enable && (ctx.locale || ctx.defaultLocale)) { - params.lang = ctx.locale ?? ctx.defaultLocale; - } - - const stringPath = convertToPath(path); - const defaultParams = fetchStrategy.getDefaultParams(); - const urlParams = fetchStrategy.getParamsFromURL(stringPath, params); - - const finalParams = deepmerge.all([defaultParams, urlParams, params]) as Partial

; - - // we don't want to include the preview params in the key - const key = fetchStrategy.getCacheKey(finalParams); + const { debug, preview } = getSiteFromContext(ctx); if (debug?.devMode) { log(LOGTYPE.INFO, `[fetchHookData] key for ${key.url}`, key); } if ( - isPreviewRequest(finalParams, urlParams) && + isPreviewRequest(params, urlParams) && typeof ctx.preview !== 'undefined' && typeof ctx.previewData !== 'undefined' ) { - finalParams.id = ctx.previewData.id; - finalParams.revision = ctx.previewData.revision; - finalParams.postType = ctx.previewData.postType; - finalParams.authToken = ctx.previewData.authToken; + params.id = ctx.previewData.id; + params.revision = ctx.previewData.revision; + params.postType = ctx.previewData.postType; + params.authToken = ctx.previewData.authToken; if (debug?.requests) { log(LOGTYPE.DEBUG, 'Preview request detected, using preview data', ctx.previewData); @@ -138,16 +166,12 @@ export async function fetchHookData), - finalParams, + params, ); let additionalCacheObjects; @@ -164,14 +188,14 @@ export async function fetchHookData ({ ...cacheObject, key: serializeKey(cacheObject.key), - isMainQuery: fetchStrategy.isMainQuery(stringPath, params), + isMainQuery: fetchStrategy.isMainQuery(path, params), })); } return { ...normalizedData, key: serializeKey(key), - isMainQuery: fetchStrategy.isMainQuery(stringPath, params), + isMainQuery: fetchStrategy.isMainQuery(path, params), additionalCacheObjects: additionalCacheObjects || null, }; }