diff --git a/.changeset/violet-taxis-report.md b/.changeset/violet-taxis-report.md new file mode 100644 index 000000000..beca4fa83 --- /dev/null +++ b/.changeset/violet-taxis-report.md @@ -0,0 +1,9 @@ +--- +'@faustwp/experimental-app-router': patch +--- + +Added: New util for fetching data on the client side. In a client component (`use client`), you can now use Apollo's `useQuery` to fetch data once your application is wrapped with the `` component. This new component is available via: + +```tsx +import { FaustProvider } from '@faustwp/experimental-app-router/ssr'; +``` diff --git a/examples/next/app-router/app/[slug]/page.tsx b/examples/next/app-router/app/[slug]/page.tsx index 5a3ed3592..80fd253a4 100644 --- a/examples/next/app-router/app/[slug]/page.tsx +++ b/examples/next/app-router/app/[slug]/page.tsx @@ -4,8 +4,8 @@ import { hasPreviewProps } from './hasPreviewProps'; import { PleaseLogin } from '@/components/please-login'; export default async function Page(props) { - const slug = props.params.slug; const isPreview = hasPreviewProps(props); + const id = isPreview ? props.searchParams.p : props.params.slug; let client = isPreview ? await getAuthClient() : await getClient(); @@ -13,19 +13,14 @@ export default async function Page(props) { return ; } - /** - * There is currently a bug in WPGraphQL where you can not query for previews - * using the contentNode type. This bug will need to be resolved for preview - * functionality in the below query to work properly. For now, it returns - * the production ready data for the given contentNode regardless if - * asPreview is true or false. - * - * @see https://github.com/wp-graphql/wp-graphql/issues/1673 - */ const { data } = await client.query({ query: gql` - query GetContentNode($uri: ID!, $asPreview: Boolean!) { - contentNode(id: $uri, idType: URI, asPreview: $asPreview) { + query GetContentNode( + $id: ID! + $idType: ContentNodeIdTypeEnum! + $asPreview: Boolean! + ) { + contentNode(id: $id, idType: $idType, asPreview: $asPreview) { ... on NodeWithTitle { title } @@ -37,7 +32,8 @@ export default async function Page(props) { } `, variables: { - uri: slug, + id, + idType: isPreview ? 'DATABASE_ID' : 'URI', asPreview: isPreview, }, }); diff --git a/examples/next/app-router/app/layout.tsx b/examples/next/app-router/app/layout.tsx index f07342a72..a61a5f7ec 100644 --- a/examples/next/app-router/app/layout.tsx +++ b/examples/next/app-router/app/layout.tsx @@ -2,7 +2,7 @@ import { gql } from '@apollo/client'; import { getClient } from '@faustwp/experimental-app-router'; import Link from 'next/link'; import '@/faust.config.js'; - +import { FaustProvider } from '@faustwp/experimental-app-router/ssr'; export default async function RootLayout({ children }) { const client = await getClient(); @@ -34,24 +34,26 @@ export default async function RootLayout({ children }) { return ( -
-
-

- {data.generalSettings.title} -

+ +
+
+

+ {data.generalSettings.title} +

-
{data.generalSettings.description}
-
+
{data.generalSettings.description}
+
-
    - {data.primaryMenuItems.nodes.map((node) => ( -
  • - {node.label} -
  • - ))} -
-
- {children} + + + {children} +
); diff --git a/examples/next/app-router/app/making-client-queries/page.tsx b/examples/next/app-router/app/making-client-queries/page.tsx new file mode 100644 index 000000000..25668bd95 --- /dev/null +++ b/examples/next/app-router/app/making-client-queries/page.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { gql, useQuery } from '@apollo/client'; + +/** + * You can make client side queries as well with Apollo's `useQuery` hook within + * a client component ('use client' directive). Just make sure the + * is wrapping the app (in app/layout.js) + */ + +export default function Page() { + const { data } = useQuery(gql` + query MyQuery { + generalSettings { + title + } + } + `); + + return <>{data?.generalSettings?.title}; +} diff --git a/examples/next/app-router/tsconfig.json b/examples/next/app-router/tsconfig.json index 1dfdc3db7..30395783f 100644 --- a/examples/next/app-router/tsconfig.json +++ b/examples/next/app-router/tsconfig.json @@ -13,8 +13,8 @@ "noEmit": true, "incremental": true, "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", + "module": "ESNext", + "moduleResolution": "Bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", diff --git a/packages/experimental-app-router/jest.config.js b/packages/experimental-app-router/jest.config.js index 1c9d776f4..a6a716a12 100644 --- a/packages/experimental-app-router/jest.config.js +++ b/packages/experimental-app-router/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { roots: ['/tests'], // Adds Jest support for TypeScript using ts-jest. diff --git a/packages/experimental-app-router/package.json b/packages/experimental-app-router/package.json index 292e8ec1d..9cf535b02 100644 --- a/packages/experimental-app-router/package.json +++ b/packages/experimental-app-router/package.json @@ -1,9 +1,11 @@ { "name": "@faustwp/experimental-app-router", + "type": "module", "version": "0.2.1", "description": "Experimental: A Faust package to support Next.js' App Router", "exports": { ".": "./dist/index.js", + "./ssr": "./dist/ssr.js", "./package.json": "./package.json" }, "types": "dist/index.d.ts", diff --git a/packages/experimental-app-router/src/client.ts b/packages/experimental-app-router/src/client/config.ts similarity index 58% rename from packages/experimental-app-router/src/client.ts rename to packages/experimental-app-router/src/client/config.ts index 19c26e51a..a4726fe6b 100644 --- a/packages/experimental-app-router/src/client.ts +++ b/packages/experimental-app-router/src/client/config.ts @@ -1,17 +1,16 @@ import { - ApolloClient, - InMemoryCache, + ApolloLink, InMemoryCacheConfig, createHttpLink, } from '@apollo/client'; // eslint-disable-next-line import/extensions import { setContext } from '@apollo/client/link/context'; -// eslint-disable-next-line import/extensions -import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc'; -import { fetchAccessToken } from './server/auth/fetchAccessToken.js'; -import { getConfig, getGraphqlEndpoint } from './faust-core-utils.js'; +import { getConfig, getGraphqlEndpoint } from '../faust-core-utils.js'; +import { fetchAccessToken } from '../server/auth/fetchAccessToken.js'; -async function createFaustApolloClient(authenticated = false) { +export function createApolloConfig( + authenticated = false, +): [InMemoryCacheConfig, ApolloLink] { const { possibleTypes } = getConfig(); const inMemoryCacheObject: InMemoryCacheConfig = { @@ -56,28 +55,5 @@ async function createFaustApolloClient(authenticated = false) { * we may set config differently than how we currently do it. */ - return new ApolloClient({ - cache: new InMemoryCache(inMemoryCacheObject), - link: linkChain, - }); -} - -export async function getClient() { - const faustApolloClient = await createFaustApolloClient(false); - const client = registerApolloClient(() => faustApolloClient); - - return client.getClient(); -} - -export async function getAuthClient() { - const token = await fetchAccessToken(); - - if (!token) { - return null; - } - - const faustApolloClient = await createFaustApolloClient(true); - const client = registerApolloClient(() => faustApolloClient); - - return client.getClient(); + return [inMemoryCacheObject, linkChain]; } diff --git a/packages/experimental-app-router/src/client/rsc.tsx b/packages/experimental-app-router/src/client/rsc.tsx new file mode 100644 index 000000000..2d6daa884 --- /dev/null +++ b/packages/experimental-app-router/src/client/rsc.tsx @@ -0,0 +1,34 @@ +// eslint-disable-next-line import/extensions +import { ApolloClient, InMemoryCache } from '@apollo/client'; +// eslint-disable-next-line import/extensions +import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc'; +import { fetchAccessToken } from '../server/auth/fetchAccessToken.js'; +import { createApolloConfig } from './config.js'; + +export function createRSCApolloClient(authenticated = false) { + const [inMemoryCacheObject, linkChain] = createApolloConfig(authenticated); + return new ApolloClient({ + cache: new InMemoryCache(inMemoryCacheObject), + link: linkChain, + }); +} + +export async function getClient() { + const faustApolloClient = createRSCApolloClient(false); + const client = registerApolloClient(() => faustApolloClient); + + return client.getClient(); +} + +export async function getAuthClient() { + const token = await fetchAccessToken(); + + if (!token) { + return null; + } + + const faustApolloClient = createRSCApolloClient(true); + const client = registerApolloClient(() => faustApolloClient); + + return client.getClient(); +} diff --git a/packages/experimental-app-router/src/client/ssr.tsx b/packages/experimental-app-router/src/client/ssr.tsx new file mode 100644 index 000000000..ebe9024ee --- /dev/null +++ b/packages/experimental-app-router/src/client/ssr.tsx @@ -0,0 +1,27 @@ +'use client'; + +// eslint-disable-next-line import/extensions +import { + ApolloNextAppProvider, + NextSSRApolloClient, + NextSSRInMemoryCache, + // eslint-disable-next-line import/extensions +} from '@apollo/experimental-nextjs-app-support/ssr'; +import React, { PropsWithChildren } from 'react'; +import { createApolloConfig } from './config.js'; + +export function createSSRApolloClient(authenticated = false) { + const [inMemoryCacheObject, linkChain] = createApolloConfig(authenticated); + return new NextSSRApolloClient({ + cache: new NextSSRInMemoryCache(inMemoryCacheObject), + link: linkChain, + }); +} + +export function FaustSSRProvider({ children }: PropsWithChildren) { + return ( + createSSRApolloClient(false)}> + {children} + + ); +} diff --git a/packages/experimental-app-router/src/index.tsx b/packages/experimental-app-router/src/index.tsx index abd555d00..26b73f850 100644 --- a/packages/experimental-app-router/src/index.tsx +++ b/packages/experimental-app-router/src/index.tsx @@ -1,4 +1,4 @@ -export { getClient, getAuthClient } from './client.js'; +export { getClient, getAuthClient } from './client/rsc.js'; export { faustRouteHandler } from './server/routeHandler/index.js'; export { fetchAccessToken } from './server/auth/fetchAccessToken.js'; export { onLogout } from './server-actions/logoutAction.js'; diff --git a/packages/experimental-app-router/src/server/auth/fetchTokens.ts b/packages/experimental-app-router/src/server/auth/fetchTokens.ts index 6443b6794..a51583732 100644 --- a/packages/experimental-app-router/src/server/auth/fetchTokens.ts +++ b/packages/experimental-app-router/src/server/auth/fetchTokens.ts @@ -1,5 +1,5 @@ // eslint-disable-next-line import/extensions -import { cookies } from 'next/headers'; +import { cookies } from 'next/headers.js'; import { AuthorizeResponse } from '../routeHandler/tokenHandler.js'; import { getUrl } from '../../lib/getUrl.js'; import { getWpUrl } from '../../faust-core-utils.js'; diff --git a/packages/experimental-app-router/src/ssr.tsx b/packages/experimental-app-router/src/ssr.tsx new file mode 100644 index 000000000..7a989ae98 --- /dev/null +++ b/packages/experimental-app-router/src/ssr.tsx @@ -0,0 +1 @@ +export { FaustSSRProvider as FaustProvider } from './client/ssr.js'; diff --git a/packages/experimental-app-router/tsconfig.json b/packages/experimental-app-router/tsconfig.json index 8465c00b6..1a6420ae9 100644 --- a/packages/experimental-app-router/tsconfig.json +++ b/packages/experimental-app-router/tsconfig.json @@ -1,11 +1,13 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "module": "esnext", + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "dist", "target": "es2017", "rootDir": "src", - "jsx": "react" + "jsx": "react", + "declaration": true }, "exclude": ["node_modules", "dist"], "include": ["src"]