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

Request: show examples of using GraphQL interfaces and (typed) fragment data #9

Open
justlevine opened this issue Jul 28, 2023 · 6 comments
Labels
enhancement New feature or request

Comments

@justlevine
Copy link

WordPress-flavored Headless is simplest when we design our queries using GraphQL interfaces and reusable fragments. We see this pattern in faust already with content blocks.

GraphQL-Codegen handles this with the useFragment() decorator, a recent pattern that that's not the most obvious to implement when using Apollo.

It would be extremely helpful if this scaffold included examples that show users how to effectively employ these patterns with Faust.

For example: a wp-templates/singular that uses nodeByUri to get data for either a post or page, a primaryMenuItems that passes when strict ts is enabled, or a ts-valid setup for Faust Blocks that doesnt throw linting errors against the ContentBlock prop type, since the attribute fragments are only stored as refs until decorated.

@blakewilson
Copy link
Contributor

Thanks for this feedback @justlevine!

We will be addressing this as soon as we get consensus on our RFC for multiple queries and implement this within the blueprints.

@mariusorani
Copy link

When i try to add the editorBlocks inside the Component.query of the page.tsx i got this error:
Type '{}' is missing the following properties from type 'DocumentNode': kind, definitions

this is the whole page.tsx:

import { gql } from "../__generated__";
import { WordPressBlock } from "@faustwp/blocks";
import Head from "next/head";
import EntryHeader from "../components/entry-header";
import Footer from "../components/footer";
import Header from "../components/header";
import { GetPageQuery } from "../__generated__/graphql";
import { DocumentNode } from 'graphql';
import { FaustTemplate } from "@faustwp/core";
import { flatListToHierarchical } from '@faustwp/core';
import { WordPressBlocksViewer } from '@faustwp/blocks';
import blocks from '../wp-blocks';


const Component: FaustTemplate<GetPageQuery> = ({loading, data}) => {
  // Loading state for previews
  if (loading) {
    return <>Loading...</>;
  }

  const { title: siteTitle, description: siteDescription } =
    data.generalSettings;
  const menuItems = data.primaryMenuItems.nodes;
  const { title, content, editorBlocks} = data.page;
  const blockList = flatListToHierarchical(editorBlocks, { childrenKey: 'innerBlocks' });

  return (
    <>
      <Head>
        <title>{`${title} - ${siteTitle}`}</title>
      </Head>

      <Header
        siteTitle={siteTitle}
        siteDescription={siteDescription}
        menuItems={menuItems}
      />

      <main className="container">
        <EntryHeader title={title} />
        <div dangerouslySetInnerHTML={{ __html: content }} />
        <WordPressBlocksViewer blocks={blockList} />
      </main>

      <Footer />
    </>
  );
};

Component.variables = ({ databaseId }, ctx) => {
  return {
    databaseId,
    asPreview: ctx?.asPreview,
  };
};

Component.query = gql(`
  ${blocks.CoreParagraph.fragments.entry}
  ${blocks.CoreColumns.fragments.entry}
  ${blocks.CoreColumn.fragments.entry}
  ${blocks.CoreCode.fragments.entry}
  ${blocks.CoreButtons.fragments.entry}
  ${blocks.CoreButton.fragments.entry}
  ${blocks.CoreQuote.fragments.entry}
  ${blocks.CoreImage.fragments.entry}
  ${blocks.CoreSeparator.fragments.entry}
  ${blocks.CoreList.fragments.entry}
  ${blocks.CoreHeading.fragments.entry}
  query GetPage($databaseId: ID!, $asPreview: Boolean = false) {
    page(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
      title
      content
      editorBlocks {
        name
        __typename
        renderedHtml
        id: clientId
        parentId: parentClientId
        ...${blocks.CoreParagraph.fragments.key}
        ...${blocks.CoreColumns.fragments.key}
        ...${blocks.CoreColumn.fragments.key}
        ...${blocks.CoreCode.fragments.key}
        ...${blocks.CoreButtons.fragments.key}
        ...${blocks.CoreButton.fragments.key}
        ...${blocks.CoreQuote.fragments.key}
        ...${blocks.CoreImage.fragments.key}
        ...${blocks.CoreSeparator.fragments.key}
        ...${blocks.CoreList.fragments.key}
        ...${blocks.CoreHeading.fragments.key}
      }
    }
    generalSettings {
      title
      description
    }
    primaryMenuItems: menuItems(where: { location: PRIMARY }) {
      nodes {
        id
        uri
        path
        label
        parentId
        cssClasses
        menu {
          node {
            name
          }
        }
      }
    }
  }
`);

export default Component;

I would be grateful for any help.

@justlevine
Copy link
Author

I use WordPressTemplate instead and it works:

import { WordPressTemplate } from '@faustwp/core/dist/mjs/getWordPressProps';
import dynamic from 'next/dynamic';
import { gql } from '@graphqlTypes/gql';

const Component = dynamic( () =>
	import( '../features/WpTemplates/Page' ).then( ( mod ) => mod.Page )
);

const Page: WordPressTemplate = ( props ) => {
	return <Component { ...props } />;
};

Page.variables = ( { uri } ) => {
	return {
		uri,
	};
};

Page.query = gql( `
	query GetPageNode($uri: ID!) {
		siteConfig {
			...SiteIdentityFrag
			...FooterSettingsFrag
		}
		...PrimaryMenuItemsFrag
		currentPage: page(id: $uri, idType: URI) {
			id
			uri
			title
			editorBlocks {
				...BlockContentEditorBlockFrag
			}
			...FeaturedImageFrag
			...NodeWithSeoFrag
			pageSettings {
				axewpPageHeader
			}
		}
	}
` );

export default Page;

The dynamic import is because Faust loads all the possible templates in app.ts 🤷‍♂️ In that case, it's the dynamic component that gets wrapped as a FaustTemplate:

import dynamic from 'next/dynamic';
import { Main } from '@/components/elements/Main/Main';
import { PageHeader } from '@/components/elements/PageHeader/PageHeader';
import { Header } from '@/components/layouts/Header/Header';
import { Layout } from '@/components/layouts/Layout/Layout';
import { BlockContent } from '@/features/WpBlocks/components/BlockContent';
import { getFragmentData } from '@graphqlTypes/fragment-masking';
import {
	MediaItemFragFragmentDoc,
	FeaturedImageFragFragmentDoc,
	FooterSettingsFragFragmentDoc,
	NodeWithSeoFragFragmentDoc,
	PrimaryMenuItemsFragFragmentDoc,
	SiteIdentityFragFragmentDoc,
	BlockContentEditorBlockFragFragment,
	GetPageNodeQuery,
	MenuItemNodeFragFragment,
	SeoFragFragment,
} from '@graphqlTypes/graphql';
import type { FaustTemplate } from '@faustwp/core/dist/mjs/getWordPressProps';

const Footer = dynamic( () =>
	import( '../../components/layouts/Footer/Footer' ).then( ( mod ) => mod.Footer )
);

export const Page: FaustTemplate< GetPageNodeQuery > = ( { data, loading } ) => {
	const identityFrag = getFragmentData( SiteIdentityFragFragmentDoc, data?.siteConfig );
	const logo = getFragmentData(
		MediaItemFragFragmentDoc,
		identityFrag?.identity?.axewpLogoLight?.node
	);

	const footerFrag = getFragmentData( FooterSettingsFragFragmentDoc, data?.siteConfig );
	const footer = footerFrag?.footerSettings?.axewpFooterContent;

	const primaryMenuFrag = getFragmentData( PrimaryMenuItemsFragFragmentDoc, data );
	const menu = primaryMenuFrag?.primaryMenuItems?.nodes;

	const seoFrag = getFragmentData( NodeWithSeoFragFragmentDoc, data?.currentPage );
	const seo = seoFrag?.seo;

	const editorBlocks = data?.currentPage?.editorBlocks;
	const pageTitle =
		data?.currentPage?.pageSettings?.axewpPageHeader || data?.currentPage?.title || '';

	const featuredImageFrag = getFragmentData( FeaturedImageFragFragmentDoc, data?.currentPage );
	const featuredImage = getFragmentData(
		MediaItemFragFragmentDoc,
		featuredImageFrag?.featuredImage?.node
	);

	return (
		<Layout loading={ !! loading } seo={ seo as SeoFragFragment }>
			<Header logo={ logo } menu={ menu as MenuItemNodeFragFragment[] } />
			<Main
				className="wp-block-group is-layout-constrained"
				style={ {
					marginTop: 'var(--wp--preset--spacing--30)',
				} }
			>
				<PageHeader
					title={ pageTitle }
					image={ featuredImage }
					className="wp-block-group alignwide has-global-padding"
				/>
				{ !! editorBlocks?.length && (
					<BlockContent blocks={ editorBlocks as BlockContentEditorBlockFragFragment[] } />
				) }
			</Main>
			<Footer content={ footer ?? '' } />
		</Layout>
	);
};

@mariusorani
Copy link

Thank you for your reply @justlevine! I tried the approach you mentioned above but still no luck, and the same error. :/

import { gql } from "../__generated__";
import { WordPressTemplate } from '@faustwp/core/dist/mjs/getWordPressProps';
import dynamic from 'next/dynamic';
import blocks from '../wp-blocks';

const Component = dynamic( () =>
	import( '../features/WpTemplates/Page' ).then( ( mod ) => mod.Page )
);

const Page: WordPressTemplate = ( props ) => {
	return <Component { ...props } />;
};

Page.variables = ({ databaseId }, ctx) => {
  return {
    databaseId,
    asPreview: ctx?.asPreview,
  };
};

Page.query = gql(`
  query GetPage($databaseId: ID!, $asPreview: Boolean = false) {
    generalSettings {
      title
      description
    }
    primaryMenuItems: menuItems(where: { location: PRIMARY }) {
      nodes {
        id
        uri
        path
        label
        parentId
        cssClasses
        menu {
          node {
            name
          }
        }
      }
    }
    currentPage: page(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
      title
      content
      editorBlocks {
        name
        __typename
        renderedHtml
        id: clientId
        parentId: parentClientId
        ...${blocks.CoreParagraph.fragments.key}
        ...${blocks.CoreColumns.fragments.key}
        ...${blocks.CoreColumn.fragments.key}
        ...${blocks.CoreCode.fragments.key}
        ...${blocks.CoreButtons.fragments.key}
        ...${blocks.CoreButton.fragments.key}
        ...${blocks.CoreQuote.fragments.key}
        ...${blocks.CoreImage.fragments.key}
        ...${blocks.CoreSeparator.fragments.key}
        ...${blocks.CoreList.fragments.key}
        ...${blocks.CoreHeading.fragments.key}
      }
    }
  }
`);

export default Page;

and the WpTemplates/Page.tsx:

import Head from "next/head";
import EntryHeader from "../../components/entry-header";
import Footer from "../../components/footer";
import Header from "../../components/header";
import { GetPageQuery } from "../../__generated__/graphql";
import type { FaustTemplate } from '@faustwp/core/dist/mjs/getWordPressProps';
import { flatListToHierarchical } from '@faustwp/core';
import { WordPressBlocksViewer } from '@faustwp/blocks';

export const Page: FaustTemplate<GetPageQuery> = ({ data, loading }) => {
    // Loading state for previews
    if (loading) {
      return <>Loading...</>;
    }
    
    const { title: siteTitle, description: siteDescription } =
      data.generalSettings;
    const menuItems = data.primaryMenuItems.nodes;
    const { title, content, editorBlocks} = data.currentPage;
    const blockList = flatListToHierarchical(editorBlocks, { childrenKey: 'innerBlocks' });
  
    return (
      <>
        <Head>
          <title>{`${title} - ${siteTitle}`}</title>
        </Head>
  
        <Header
          siteTitle={siteTitle}
          siteDescription={siteDescription}
          menuItems={menuItems}
        />
  
        <main className="container">
          <EntryHeader title={title} />
          <div dangerouslySetInnerHTML={{ __html: content }} />
          <WordPressBlocksViewer blocks={blockList} />
        </main>
  
        <Footer />
      </>
    );
  };

can't figure it out what i'm doing wrong

@blakewilson
Copy link
Contributor

Hi @mariusorani, if you are still experiencing this, could you open a new issue on the faustjs repo?

https://github.com/wpengine/faustjs/issues/new/choose

@blakewilson
Copy link
Contributor

@justlevine Sorry for the lack of traction on this. we've been in the transition from using an internal Jira board to now using GitHub projects. I've added this item to our backlog, but not sure when we can prioritize it.

When we pull this into progress we'll update you here!

@blakewilson blakewilson removed their assignment Mar 4, 2024
@ChrisWiegman ChrisWiegman added the enhancement New feature or request label Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants