From ca945357f84edecfa89b8c56e323f9ae281b4e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADcholas=20Andr=C3=A9?= Date: Wed, 17 Jul 2024 13:46:36 -0300 Subject: [PATCH] Feature/query loop block (#810) --- .changeset/two-weeks-applaud.md | 6 +++ .../react/components/BaseBlocksRenderer.tsx | 20 ++++++++- .../components/__tests__/BlocksRenderer.tsx | 25 +++++++++++ .../__snapshots__/BlocksRenderer.tsx.snap | 12 +++++ .../next/src/rsc/data/queries/prepareQuery.ts | 3 +- .../src/rsc/data/queries/queryAppSettings.ts | 4 +- .../rsc/data/queries/queryAuthorArchive.ts | 4 +- .../next/src/rsc/data/queries/queryPost.ts | 4 +- .../src/rsc/data/queries/queryPostOrPosts.ts | 4 +- .../next/src/rsc/data/queries/queryPosts.ts | 4 +- .../next/src/rsc/data/queries/querySearch.ts | 4 +- .../next/src/rsc/data/queries/queryTerms.ts | 4 +- packages/next/src/rsc/data/queries/types.ts | 1 + .../src/app/(single)/[...path]/page.tsx | 5 ++- .../wp-nextjs-app/src/components/Blocks.tsx | 18 ++++++++ .../src/components/Blocks/PostList.tsx | 44 +++++++++++++++++++ 16 files changed, 144 insertions(+), 18 deletions(-) create mode 100644 .changeset/two-weeks-applaud.md create mode 100644 projects/wp-nextjs-app/src/components/Blocks.tsx create mode 100644 projects/wp-nextjs-app/src/components/Blocks/PostList.tsx diff --git a/.changeset/two-weeks-applaud.md b/.changeset/two-weeks-applaud.md new file mode 100644 index 000000000..f5e054094 --- /dev/null +++ b/.changeset/two-weeks-applaud.md @@ -0,0 +1,6 @@ +--- +"@headstartwp/core": minor +"@headstartwp/next": minor +--- + +Introduce `blockContext` prop to BlocksRenderer and `handleError` in query functions diff --git a/packages/core/src/react/components/BaseBlocksRenderer.tsx b/packages/core/src/react/components/BaseBlocksRenderer.tsx index 90869703a..4f714e4c5 100644 --- a/packages/core/src/react/components/BaseBlocksRenderer.tsx +++ b/packages/core/src/react/components/BaseBlocksRenderer.tsx @@ -14,7 +14,10 @@ const { default: parse, domToReact } = HtmlReactParser; /** * The interface any children of {@link BlocksRenderer} must implement. */ -export interface BlockProps { +export interface BlockProps< + BlockAttributes extends IDataWPBlock = IDataWPBlock, + Context extends Record = Record, +> { /** * A test function receives a domNode and returns a boolean value indicating * whether that domNode should be replaced with the React component @@ -64,6 +67,11 @@ export interface BlockProps * The style tag of the domNode as an object. */ style?: Record; + + /** + * An optional context that is passed to all children components + */ + blockContext?: Context; } /** @@ -121,6 +129,11 @@ export interface BlockRendererProps { * Whether to forward the block attributes to the children components. */ forwardBlockAttributes?: boolean; + + /** + * An optional context that is passed to all children components + */ + blockContext?: Record; } interface BaseBlockRendererProps extends BlockRendererProps { @@ -153,6 +166,7 @@ export function BaseBlocksRenderer({ children, settings, forwardBlockAttributes = false, + blockContext, }: BaseBlockRendererProps) { const blocks: ReactNode[] = React.Children.toArray(children); @@ -216,6 +230,10 @@ export function BaseBlocksRenderer({ blockProps.block = { attributes, name, className }; } + if (typeof blockContext !== 'undefined') { + blockProps.blockContext = { ...blockContext }; + } + component = React.createElement( block.type, blockProps, diff --git a/packages/core/src/react/components/__tests__/BlocksRenderer.tsx b/packages/core/src/react/components/__tests__/BlocksRenderer.tsx index 6644830ff..242f4e953 100644 --- a/packages/core/src/react/components/__tests__/BlocksRenderer.tsx +++ b/packages/core/src/react/components/__tests__/BlocksRenderer.tsx @@ -239,4 +239,29 @@ describe('BlocksRenderer', () => { expect(container).toMatchSnapshot(); }); + + it('forward context to the component', () => { + const DivToP = ({ + block, + blockContext, + }: BlockProps<{ blockAttribute: string }, { contextProp: string }>) => { + return ( +

+ {JSON.stringify(block)} - {JSON.stringify(blockContext)} +

+ ); + }; + + const { container } = render( + `} + forwardBlockAttributes + blockContext={{ contextProp: 'this is a context prop' }} + > + isBlockByName(node, '10up/custom-block')} /> + , + ); + + expect(container).toMatchSnapshot(); + }); }); diff --git a/packages/core/src/react/components/__tests__/__snapshots__/BlocksRenderer.tsx.snap b/packages/core/src/react/components/__tests__/__snapshots__/BlocksRenderer.tsx.snap index 3e028f801..ea45e262a 100644 --- a/packages/core/src/react/components/__tests__/__snapshots__/BlocksRenderer.tsx.snap +++ b/packages/core/src/react/components/__tests__/__snapshots__/BlocksRenderer.tsx.snap @@ -10,6 +10,18 @@ exports[`BlocksRenderer forward blockProps to the component 1`] = ` `; +exports[`BlocksRenderer forward context to the component 1`] = ` +
+

+ {"attributes":{"blockAttribute":"this is a block attribute"},"name":"10up/custom-block","className":"my-class"} + - + {"contextProp":"this is a context prop"} +

+
+`; + exports[`BlocksRenderer works correctly with chinese content 1`] = `

( query: NextQueryProps

, _config: HeadlessConfig | undefined = undefined, ) { - const { routeParams, ...rest } = query; + const { routeParams, handleError = true, ...rest } = query; const path = routeParams?.path ?? ''; const siteConfig = routeParams?.site @@ -45,5 +45,6 @@ export function prepareQuery

( options, path: pathname, config: config ?? getHeadstartWPConfig(), + handleError, }; } diff --git a/packages/next/src/rsc/data/queries/queryAppSettings.ts b/packages/next/src/rsc/data/queries/queryAppSettings.ts index df9e0f88d..963f64376 100644 --- a/packages/next/src/rsc/data/queries/queryAppSettings.ts +++ b/packages/next/src/rsc/data/queries/queryAppSettings.ts @@ -13,14 +13,14 @@ export async function queryAppSettings< T extends AppEntity = AppEntity, P extends EndpointParams = EndpointParams, >(q: AppQueryProps

& NextQueryProps

= {}, _config: HeadlessConfig | undefined = undefined) { - const { config, ...query } = prepareQuery

(q, _config); + const { config, handleError, ...query } = prepareQuery

(q, _config); try { const result = await fetchAppSettings(query, config); return result; } catch (error) { - if (error instanceof Error) { + if (error instanceof Error && handleError) { await handleFetchError(error, config, query.path); } throw error; diff --git a/packages/next/src/rsc/data/queries/queryAuthorArchive.ts b/packages/next/src/rsc/data/queries/queryAuthorArchive.ts index 2582a463c..798325f2e 100644 --- a/packages/next/src/rsc/data/queries/queryAuthorArchive.ts +++ b/packages/next/src/rsc/data/queries/queryAuthorArchive.ts @@ -12,14 +12,14 @@ export async function queryAuthorArchive< T extends PostEntity = PostEntity, P extends PostsArchiveParams = PostsArchiveParams, >(q: NextQueryProps

= {}, _config: HeadlessConfig | undefined = undefined) { - const { config, ...query } = prepareQuery

(q, _config); + const { config, handleError, ...query } = prepareQuery

(q, _config); try { const result = await fetchAuthorArchive(query, config); return result; } catch (error) { - if (error instanceof Error) { + if (error instanceof Error && handleError) { await handleFetchError(error, config, query.path); } throw error; diff --git a/packages/next/src/rsc/data/queries/queryPost.ts b/packages/next/src/rsc/data/queries/queryPost.ts index 36241d968..328e25819 100644 --- a/packages/next/src/rsc/data/queries/queryPost.ts +++ b/packages/next/src/rsc/data/queries/queryPost.ts @@ -10,7 +10,7 @@ export async function queryPost< T extends PostEntity = PostEntity, P extends PostParams = PostParams, >(q: NextQueryProps

= {}, _config: HeadlessConfig | undefined = undefined) { - const { config, ...query } = prepareQuery

(q, _config); + const { config, handleError, ...query } = prepareQuery

(q, _config); try { const { isEnabled } = draftMode(); @@ -42,7 +42,7 @@ export async function queryPost< return result; } catch (error) { - if (error instanceof Error) { + if (error instanceof Error && handleError) { await handleFetchError(error, config, query.path); } throw error; diff --git a/packages/next/src/rsc/data/queries/queryPostOrPosts.ts b/packages/next/src/rsc/data/queries/queryPostOrPosts.ts index f3d154953..e133695f2 100644 --- a/packages/next/src/rsc/data/queries/queryPostOrPosts.ts +++ b/packages/next/src/rsc/data/queries/queryPostOrPosts.ts @@ -7,14 +7,14 @@ export async function queryPostOrPosts< T extends PostEntity = PostEntity, P extends PostOrPostsParams = PostOrPostsParams, >(q: NextQueryProps

= {}, _config: HeadlessConfig | undefined = undefined) { - const { config, ...query } = prepareQuery

(q, _config); + const { config, handleError, ...query } = prepareQuery

(q, _config); try { const result = await fetchPostOrPosts(query, config); return result; } catch (error) { - if (error instanceof Error) { + if (error instanceof Error && handleError) { await handleFetchError(error, config, query.path); } throw error; diff --git a/packages/next/src/rsc/data/queries/queryPosts.ts b/packages/next/src/rsc/data/queries/queryPosts.ts index 690341101..adde7507a 100644 --- a/packages/next/src/rsc/data/queries/queryPosts.ts +++ b/packages/next/src/rsc/data/queries/queryPosts.ts @@ -7,14 +7,14 @@ export async function queryPosts< T extends PostEntity = PostEntity, P extends PostsArchiveParams = PostsArchiveParams, >(q: NextQueryProps

= {}, _config: HeadlessConfig | undefined = undefined) { - const { config, ...query } = prepareQuery

(q, _config); + const { config, handleError, ...query } = prepareQuery

(q, _config); try { const result = await fetchPosts(query, config); return result; } catch (error) { - if (error instanceof Error) { + if (error instanceof Error && handleError) { await handleFetchError(error, config, query.path); } throw error; diff --git a/packages/next/src/rsc/data/queries/querySearch.ts b/packages/next/src/rsc/data/queries/querySearch.ts index 7405e288b..f04ed935f 100644 --- a/packages/next/src/rsc/data/queries/querySearch.ts +++ b/packages/next/src/rsc/data/queries/querySearch.ts @@ -13,14 +13,14 @@ export async function querySearch< T extends PostSearchEntity | TermSearchEntity = PostSearchEntity | TermSearchEntity, P extends SearchParams = SearchParams, >(q: NextQueryProps

= {}, _config: HeadlessConfig | undefined = undefined) { - const { config, ...query } = prepareQuery

(q, _config); + const { config, handleError, ...query } = prepareQuery

(q, _config); try { const result = await fetchSearch(query, config); return result; } catch (error) { - if (error instanceof Error) { + if (error instanceof Error && handleError) { await handleFetchError(error, config, query.path); } throw error; diff --git a/packages/next/src/rsc/data/queries/queryTerms.ts b/packages/next/src/rsc/data/queries/queryTerms.ts index be2936f58..45d8f7b50 100644 --- a/packages/next/src/rsc/data/queries/queryTerms.ts +++ b/packages/next/src/rsc/data/queries/queryTerms.ts @@ -7,14 +7,14 @@ export async function queryTerms< T extends TermEntity = TermEntity, P extends TaxonomyArchiveParams = TaxonomyArchiveParams, >(q: NextQueryProps

= {}, _config: HeadlessConfig | undefined = undefined) { - const { config, ...query } = prepareQuery

(q, _config); + const { config, handleError, ...query } = prepareQuery

(q, _config); try { const result = await fetchTerms(query, config); return result; } catch (error) { - if (error instanceof Error) { + if (error instanceof Error && handleError) { await handleFetchError(error, config, query.path); } throw error; diff --git a/packages/next/src/rsc/data/queries/types.ts b/packages/next/src/rsc/data/queries/types.ts index 1838f33d7..41452dff8 100644 --- a/packages/next/src/rsc/data/queries/types.ts +++ b/packages/next/src/rsc/data/queries/types.ts @@ -6,4 +6,5 @@ export type NextQueryProps

= { site?: string; [k: string]: unknown; }; + handleError?: boolean; } & Omit, 'path'>; diff --git a/projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx b/projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx index 4fd359fe4..979cc343b 100644 --- a/projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx +++ b/projects/wp-nextjs-app/src/app/(single)/[...path]/page.tsx @@ -1,5 +1,6 @@ -import { BlocksRenderer, HtmlDecoder } from '@headstartwp/core/react'; +import { HtmlDecoder } from '@headstartwp/core/react'; import { HeadstartWPRoute, queryPost } from '@headstartwp/next/app'; +import Blocks from '../../../components/Blocks'; const Single = async ({ params }: HeadstartWPRoute) => { const { data } = await queryPost({ @@ -20,7 +21,7 @@ const Single = async ({ params }: HeadstartWPRoute) => {

- + ); }; diff --git a/projects/wp-nextjs-app/src/components/Blocks.tsx b/projects/wp-nextjs-app/src/components/Blocks.tsx new file mode 100644 index 000000000..8c6d23424 --- /dev/null +++ b/projects/wp-nextjs-app/src/components/Blocks.tsx @@ -0,0 +1,18 @@ +import { BlocksRenderer } from '@headstartwp/core/react'; +import React from 'react'; +import { isBlockByName } from '@headstartwp/core'; +import { PostList } from './Blocks/PostList'; + +type BlockProps = { + html: string; +}; + +const Blocks: React.FC = ({ html }) => { + return ( + + isBlockByName(node, 'core/query')} /> + + ); +}; + +export default Blocks; diff --git a/projects/wp-nextjs-app/src/components/Blocks/PostList.tsx b/projects/wp-nextjs-app/src/components/Blocks/PostList.tsx new file mode 100644 index 000000000..46f925d89 --- /dev/null +++ b/projects/wp-nextjs-app/src/components/Blocks/PostList.tsx @@ -0,0 +1,44 @@ +import { BlockProps } from '@headstartwp/core/react'; +import { queryPosts } from '@headstartwp/next/app'; + +interface PostListProps + extends BlockProps<{ queryId: number; query: { perPage: number; postType: string } }> {} + +export const PostList: React.FC = async ({ block }) => { + if (!block) { + return null; + } + + const { query } = block.attributes; + + try { + const { + data: { posts }, + } = await queryPosts({ + // todo: map the rest of the query object + params: { per_page: query.perPage, postType: query.postType }, + // setting handle error to false will disable automatic handling of errors + // i.e you have to handle the error yourself + handleError: false, + }); + + return ( + <> +

Post List

+
+					{JSON.stringify({ query }, null, 2)}
+				
+ +
    + {posts.map((post) => ( +
  • + #{post.id} -{post.title.rendered} +
  • + ))} +
+ + ); + } catch (e) { + return 'no posts found'; + } +};