因此,为此,假设这仍然是您正在努力解决的问题,您可以使用带有片段的查询来从无头 wordpress 外部获取数据(强烈建议您使用 WP-GraphQL 插件)
fragment DynamicNavFragment on WordpressMenuItem {
id
label
path
parentId
}
然后对于查询,您使用文件顶部的# 导入片段:
# import DynamicNavFragment from '../Partials/dynamic-nav-fields.graphql'
query DynamicNav(
$idHead: ID!
$idTypeHead: WordpressMenuNodeIdTypeEnum!
$idFoot: ID!
$idTypeFoot: WordpressMenuNodeIdTypeEnum!
) {
Header: wordpress {
menu(id: $idHead, idType: $idTypeHead) {
menuItems(where: { parentId: 0 }) {
edges {
node {
...DynamicNavFragment
childItems {
edges {
node {
...DynamicNavFragment
childItems {
edges {
node {
...DynamicNavFragment
}
}
}
}
}
}
}
}
}
}
}
Footer: wordpress {
menu(id: $idFoot, idType: $idTypeFoot) {
menuItems(where: { parentId: 0 }) {
edges {
node {
...DynamicNavFragment
childItems {
edges {
node {
...DynamicNavFragment
}
}
}
}
}
}
}
}
}
要从此代码生成类型定义,我们需要一个 codegen.yml 文件,如下所示:
overwrite: true
schema:
${ONEGRAPH_API_URL_YML}:
headers:
Authorization: Bearer ${WORDPRESS_AUTH_REFRESH_TOKEN_YML}
documents: 'graphql/**/*.graphql'
generates:
graphql/generated/graphql.tsx:
plugins:
- typescript:
constEnums: false
enumsAsTypes: false
numericEnums: false
namingConvention: keep
futureProofEnums: false
enumsAsConst: false
onlyOperationTypes: false
maybeValue: T | null | undefined
noExport: false
enumPrefix: true
fieldWrapperValue: T
wrapFieldDefinitions: true
skipTypename: false
nonOptionalTypename: false
useTypeImports: false
avoidOptionals: true
declarationKind:
input: interface
type: interface
- typescript-operations:
declarationKind:
input: interface
type: interface
avoidOptionals: true
exportFragmentSpreadSubTypes: true
- typescript-react-apollo:
addDocBlocks: true
reactApolloVersion: 3
documentMode: documentNodeImportFragments
config:
maybeValue: T | null | undefined
declarationKind:
input: interface
type: interface
documentNodeImportFragments: true
reactApolloVersion: 3
withHooks: true
numericEnums: false
namingConvention: keep
withHOC: false
avoidOptionals: true
withComponent: false
exportFragmentSpreadSubTypes: true
addDocBlocks: true
graphql/graphql.schema.graphql:
plugins:
- schema-ast
config:
commentDescriptions: true
graphql/graphql.schema.json:
plugins:
- introspection
config:
commentDescriptions: true
hooks:
afterAllFileWrite:
- prettier --write
这会下降几层以获得在导航(标题)中动态注入的子路径和子子路径。现在为了使用数据本身,我可以包含我的导航栏 + 动态导航和布局组件。这是一段代码,但映射它,你会跟着走(这都是nextjs+typescript)
无头导航栏.tsx
import { useState, FC, useRef, Fragment } from 'react';
import cn from 'classnames';
import { useRouter } from 'next/router';
import Link from 'next/link';
import { Transition, Listbox } from '@headlessui/react';
import { NavArrow } from '@/components/UI/Icons';
import { DynamicNavQuery } from '@/graphql/generated/graphql';
import css from './headless-navbar.module.css';
import { DynamicNavDissected } from '@/types/dynamic-nav';
export interface HeadlessNavbarProps
extends DynamicNavDissected {
header: DynamicNavQuery['Header'];
className?: string;
}
const HeadlessNavbar: FC<HeadlessNavbarProps> = ({
header,
className,
node
}) => {
const [isOpen, setIsOpen] = useState(false);
const [subOpen, setSubOpen] = useState(false);
const [selectedCategory, setSelectedCategory] = useState(node);
const { pathname } = useRouter();
const refAcceptor = useRef() as React.MutableRefObject<HTMLDivElement>;
return (
<>
{header != null &&
header.menu != null &&
header.menu.menuItems != null &&
header.menu.menuItems.edges != null &&
header.menu.menuItems.edges.length > 0 ? (
header.menu.menuItems.edges.map((top, i) => {
return top != null &&
top.node != null &&
top.node.label != null ? (
<>
<Link
href={top.node.path}
as={top.node.path}
passHref
scroll={true}
key={top.node.id}
>
<a
id='top'
className={cn(className, {
[css.active]: pathname === top.node.path,
[css.link]: pathname !== top.node.path
})}
>
<p>{top.node.label}</p>
</a>
</Link>
{top.node.childItems != null &&
top.node.childItems.edges != null &&
top.node.childItems.edges.length > 0 ? (
<div className='relative z-150 -ml-2'>
<button
onClick={() => setIsOpen(!isOpen)}
id='sub-menu'
aria-haspopup={true}
aria-expanded={true}
type='button'
className={cn(css.topButton, {
'lg:-translate-x-4 ring-secondary-0 ring-1 rotate-0': isOpen,
'lg:-translate-x-5 ring-redditNav ring-0 -rotate-90': !isOpen
})}
>
<NavArrow className='select-none lg:w-5 lg:h-5' />
</button>
<Transition
show={isOpen}
enter='transition ease-out duration-200 '
enterFrom='transform opacity-0 translate-y-1'
enterTo='transform opacity-100 translate-y-0'
leave='transition ease-in duration-150'
leaveFrom='transform opacity-100 translate-y-0'
leaveTo='transform opacity-0 translate-y-1'
>
<div className={cn(css.transitionAlpha, '')}>
<div
className={css.transitionBeta}
ref={refAcceptor}
role='menu'
aria-orientation='vertical'
aria-labelledby='sub-menu'
>
<div className={css.transitionGamma}>
{top!.node!.childItems!.edges!.map((sub, j) => {
return sub != null &&
sub.node != null &&
sub.node.label != null &&
sub.node.parentId != null ? (
<Listbox
key={sub.node.id}
value={selectedCategory}
onChange={setSelectedCategory}
>
{({ open }) => (
<>
<div className={cn(css.divOpen)}>
<p className='text-lg'>{sub!.node!.label!}</p>
<Listbox.Button
aria-haspopup={true}
id='sub'
aria-expanded={true}
onClick={() => setSubOpen(!subOpen)}
className={cn(css.topButton, {
'lg:-translate-x-4 ring-secondary-0 ring-1 rotate-0': open,
'lg:-translate-x-5 ring-redditSearch ring-0 -rotate-90': !open
})}
>
<NavArrow className='select-none lg:w-5 lg:h-5' />
</Listbox.Button>
</div>
<Transition
show={open}
enter='transition ease-out duration-100'
enterFrom='transform opacity-0 scale-95'
enterTo='transform opacity-100 scale-100'
leave='transition ease-in duration-75'
leaveFrom='transform opacity-100 scale-100'
leaveTo='transform opacity-0 scale-95'
>
<Listbox.Options
static
className='outline-none select-none focus:outline-none'
>
{sub!.node!.childItems != null &&
sub!.node!.childItems.edges != null &&
sub!.node!.childItems.edges.length > 0 ? (
sub!.node!.childItems!.edges!.map(
(subsub, k) => {
return subsub != null &&
subsub.node != null &&
subsub.node.label != null &&
subsub.node.parentId != null ? (
<>
{open && (
<Listbox.Option
key={subsub.node.id}
className={cn(
css.subsub,
'text-base font-medium list-none outline-none'
)}
value={subsub!.node!.label}
>
<>
<Link
href={subsub!.node!.path}
passHref
key={k++}
>
<a
id='subsub'
className={cn(css.subsubanchor)}
key={subsub!.node!.id}
>
{subsub!.node!.label}
</a>
</Link>
</>
</Listbox.Option>
)}
</>
) : (
<></>
);
}
)
) : (
<></>
)}
</Listbox.Options>
</Transition>
</>
)}
</Listbox>
) : (
<></>
);
})}
</div>
</div>
</div>
</Transition>
</div>
) : (
<></>
)}
</>
) : (
<></>
);
})
) : (
<></>
)}
</>
);
};
export default HeadlessNavbar;
注意:DynamicNavDissected 在@/types 目录中定义如下
import {
Maybe,
DynamicNavFragmentFragment
} from '@/graphql/generated/graphql';
export interface DynamicNavDissected {
node?: Maybe<
{ __typename?: 'WordpressMenuItem' } & {
childItems: Maybe<
{
__typename?: 'WordpressMenuItemToMenuItemConnection';
} & {
edges: Maybe<
Array<
Maybe<
{
__typename?: 'WordpressMenuItemToMenuItemConnectionEdge';
} & {
node: Maybe<
{
__typename?: 'WordpressMenuItem';
} & DynamicNavFragmentFragment
>;
}
>
>
>;
}
>;
} & DynamicNavFragmentFragment
>;
}
headless-navbar.module.css
.active {
@apply p-2 rounded-md text-base text-secondary-0 font-bold 2xl:text-lg 2xl:tracking-tight;
&:hover {
@apply text-opacity-75 transition-opacity transform-gpu duration-300;
}
}
.link {
@apply p-2 rounded-md text-base text-secondary-0 font-semibold 2xl:text-lg 2xl:tracking-tight;
&:hover {
@apply text-opacity-75 transition-opacity transform-gpu duration-300;
}
}
.topButton {
@apply bg-redditNav rounded-full flex ml-4 items-center my-auto text-secondary-0 transition-transform transform-gpu ease-in-out duration-200 outline-none md:py-0 lg:ml-0 lg:mx-auto;
&:focus {
@apply outline-none;
}
}
.bottomButton {
@apply bg-redditSearch rounded-full inline-flex ml-4 items-center my-auto text-secondary-0 transition-transform transform-gpu ease-in-out delay-200 duration-200 outline-none md:py-0 lg:ml-0 lg:mx-auto;
&:focus {
@apply outline-none;
}
}
.subsub p {
@apply ml-10 !important;
}
.transitionAlpha {
@apply relative z-150 -ml-4 mt-3 transform px-2 w-screen max-w-sm sm:px-0 lg:absolute lg:ml-0 lg:left-1/2 lg:-translate-x-1/2 lg:mx-auto;
}
.transitionBeta {
@apply flex-grow rounded-lg ring-opacity-5 overflow-hidden lg:shadow-lg lg:ring-1 lg:ring-black;
}
.transitionGamma {
@apply relative grid bg-redditSearch text-secondary-0 sm:gap-8 sm:p-8 lg:flex-col lg:gap-3 lg:py-6 transition-transform transform-gpu;
}
.subanchor {
@apply -m-1 p-3 flex items-start rounded-md lg:-m-3;
&:hover {
@apply bg-redditNav;
}
}
.subsubanchor {
@apply -m-1 p-3 flex items-start rounded-md lg:-m-3 lg:ml-2;
&:hover {
@apply bg-redditNav;
}
}
.subtosubsub {
@apply relative px-5 bg-redditSearch text-secondary-0 flex-grow sm:gap-8 lg:gap-3;
}
.divOpen {
@apply grid grid-cols-3 w-full min-w-full transition-transform ease-in-out duration-300 transform-gpu;
}
现在,对于注入无头导航栏数据的导航栏组件——我使用两个单独的站点分别注入桌面和移动动态导航。提供更精细的控制+关注点分离
navbar.tsx
import { FC, useState, useEffect } from 'react';
import cn from 'classnames';
import NavbarLinks from './navbar-links';
import css from './navbar.module.css';
import { Transition } from '@headlessui/react/dist';
import { Logo } from '../../UI';
import Link from 'next/link';
import throttle from 'lodash.throttle';
import MenuIcon from '../../UI/Icons/menu-icon';
import XIcon from '../../UI/Icons/x-icon';
interface NavbarProps {
root?: string;
Desktop?: React.ReactNode;
Mobile?: React.ReactNode;
}
const Navbar: FC<NavbarProps> = ({ root, Desktop, Mobile }) => {
const [menuOpen, setMenuOpen] = useState(true);
const [isOpen] = useState(false);
const [hasScrolled, setHasScrolled] = useState(false);
useEffect(() => {
const handleScroll = throttle(() => {
const offset = 0;
const { scrollTop } = document.documentElement;
const scrolled = scrollTop > offset;
setHasScrolled(scrolled);
}, 200);
document.addEventListener('scroll', handleScroll);
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [hasScrolled]);
return (
<>
<nav className={cn(root, css.root, css.stickyNav)}>
<div
className={cn(
css.stickyNav,
{ 'shadow-magical': hasScrolled },
'max-w-full mx-auto px-4 sm:px-6 lg:px-8 font-sans text-secondary-0 transform-gpu duration-500 ease-in-out transition-all'
)}
>
<div
className={cn(
'flex flex-row-reverse justify-between transform-gpu duration-500 ease-in-out transition-all',
css.stickyNav,
{
'h-24': !hasScrolled,
'h-20': hasScrolled
}
)}
>
<div className='flex'>
<div className='-ml-2 mr-2 flex items-center lg:hidden w-full min-w-full'>
<button
className='inline-flex items-center justify-center p-2 rounded-md text-secondary-0 hover:text-opacity-80 hover:bg-opacity-80 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-secondary-0'
aria-expanded={false}
onClick={() => setMenuOpen(!menuOpen)}
>
<span className='sr-only'>Open Main Menu</span>
{menuOpen ? (
<MenuIcon
className={cn('h-8 w-8 focus:outline-none', {
hidden: !menuOpen,
block: menuOpen
})}
/>
) : (
<XIcon
className={cn('h-8 w-8 focus:outline-none', {
hidden: menuOpen,
block: !menuOpen
})}
/>
)}
</button>
</div>
<div className='hidden lg:ml-6 lg:flex lg:items-center lg:space-x-4'>
{Desktop ?? <NavbarLinks />}
</div>
</div>
<div className='flex items-center'>
<div className='flex-shrink-0'>
<div className='lg:mx-4 lg:flex-shrink-0 lg:flex lg:items-center'>
<div className='ml-3 '>
<div className=''>
<span className='sr-only'>The Faderoom Inc.</span>
<Link href='/' passHref scroll={true}>
<a className='#logo'>
<Logo
className={cn(
css.svg,
'cursor-default focus:outline-none transition-all transform-gpu ease-in-out duration-500',
{
'w-18 h-18': !hasScrolled,
'w-14 h-14': hasScrolled
}
)}
/>
</a>
</Link>
</div>
<Transition
show={isOpen && !hasScrolled}
>
<Transition.Child
enter='transition ease-out duration-200'
enterFrom='transform opacity-0 scale-95'
enterTo='transform opacity-100 scale-100'
leave='transition ease-in duration-200'
leaveFrom='transform opacity-100 scale-100'
leaveTo='transform opacity-0 scale-95'
>
<div
role='menu'
aria-orientation='vertical'
aria-labelledby='user-menu'
className={
'origin-top-right absolute right-0 mt-2 h-40 w-44 rounded-md shadow-lg ring-2 ring-red-900 outline-none grid grid-cols-1 bg-secondary-0 z-50 px-3 py-2 hover:bg-opacity-80'
}
>
<NavbarLinks
root={cn('px-3 py-2 hover:bg-secondary-0')}
/>
</div>
</Transition.Child>
</Transition>
</div>
</div>
</div>
</div>
</div>
</div>
<div
className={cn('lg:hidden text-secondary-0', {
block: !menuOpen,
hidden: menuOpen
})}
>
<div className='px-2 pt-2 pb-3 space-y-1 sm:px-3 align-middle'>
{Mobile ?? (
<NavbarLinks
root={cn(
'block px-3 py-2 rounded-md text-2xl font-medium text-secondary-0'
)}
/>
)}
</div>
</div>
</nav>
</>
);
};
export default Navbar;
navbar.module.css
.epiRoot {
@apply sticky top-0 bg-black z-40 transition-all duration-150;
}
.root {
@apply bg-redditNav select-none;
}
.svg {
z-index: 100;
@apply block sm:content-center sm:mx-auto;
}
.stickyNav {
position: sticky;
z-index: 100;
top: 0;
backdrop-filter: saturate(180%) blur(20px);
transition: 0.1 ease-in-out;
&.root {
transition: 0.1 ease-in-out;
}
}
.navLinks div > a {
@apply text-lg lg:text-xl;
}
现在是全局布局,最后是索引页面来说明如何将 apollo 与 getStaticProps 一起使用
layout.tsx
import { FooterFixture } from './Footer';
import { HeadlessFooter } from './HeadlessFooter';
import { HeadlessNavbar } from './HeadlessNavbar';
import { Navbar } from './Navbar';
import { Meta } from './Meta';
import cn from 'classnames';
import {
DynamicNavQuery,
useDynamicNavQuery,
WordpressMenuNodeIdTypeEnum
} from '@/graphql/generated/graphql';
import {
ApolloError,
Button,
Fallback,
Modal,
LandingHero
} from '../UI';
import { useGlobal } from '../Context';
import { useAcceptCookies } from '@/lib/use-accept-cookies';
import Head from 'next/head';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
const dynamicProps = {
loading: () => <Fallback />
};
const LoginView = dynamic(
() => import('../Auth/wp-login-holder'),
dynamicProps
);
const Featurebar = dynamic(
() => import('./Featurebar'),
dynamicProps
);
export interface LayoutProps {
Header: DynamicNavQuery['Header'];
Footer: DynamicNavQuery['Footer'];
className?: string;
title?: string;
hero?: React.ReactNode;
children?: React.ReactNode;
}
function AppLayout({
Header,
Footer,
className,
title,
children,
hero
}: LayoutProps) {
const { acceptedCookies, onAcceptCookies } = useAcceptCookies();
const { displayModal, modalView, closeModal } = useGlobal();
const { loading, error, data } = useDynamicNavQuery({
variables: {
idHead: 'Header',
idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
idFoot: 'Footer'
},
notifyOnNetworkStatusChange: true
});
const router = useRouter();
Header =
data != null && data.Header != null ? data.Header : undefined;
Footer =
data != null && data.Footer != null ? data.Footer : undefined;
return (
<>
<Head>
<title>{title ?? '✂ The Fade Room Inc. ✂'}</title>
</Head>
<Meta />
{error ? (
<>
<ApolloError error={error} />
</>
) : loading && !error ? (
<Fallback />
) : (
<Navbar
Desktop={<HeadlessNavbar header={Header} />}
Mobile={
<HeadlessNavbar
header={Header}
className={
'block px-3 py-2 rounded-md text-base font-semibold text-secondary-0 hover:bg-redditSearch'
}
/>
}
/>
)}
<>
{router.pathname === '/' ? <LandingHero /> : hero}
<Modal open={displayModal} onClose={closeModal}>
{modalView === 'LOGIN_VIEW' && <LoginView />}
</Modal>
<div className={cn('bg-redditSearch min-h-full', className)}>
<main className='fit min-h-full'>{children}</main>
{error ? (
<>
<ApolloError error={error} />
</>
) : loading && !error ? (
<Fallback />
) : (
<FooterFixture
children={<HeadlessFooter footer={Footer} />}
/>
)}
<div className='font-sans z-150'>
<Featurebar
title='This site uses cookies to improve your experience. By clicking, you agree to our Privacy Policy.'
hide={acceptedCookies}
className='prose-lg sm:prose-xl bg-opacity-90 sm:text-center'
action={
<Button
type='submit'
variant='slim'
className='mx-auto text-secondary-0 text-center rounded-xl border-secondary-0 border-1 hover:bg-gray-700 hover:bg-opacity-80 hover:border-secondary-0 duration-500 ease-in-out transform-gpu transition-colors'
onClick={() => onAcceptCookies()}
>
Accept Cookies
</Button>
}
/>
</div>
</div>
</>
</>
);
}
export default AppLayout;
pages/index.tsx -- apollo 客户端隐式传入由
GetStaticPropsResult 指示的道具(它们各自的查询也存在)。
reviews 不是由 apolloclient 处理,而是由 fetching 方法处理,所以必须显式传入
import { initializeApollo, addApolloState } from '@/lib/apollo';
import { AppLayout } from '@/components/Layout';
import {
GetStaticPropsContext,
GetStaticPropsResult,
InferGetStaticPropsType
} from 'next';
import {
LandingDataQuery,
LandingDataQueryVariables,
LandingDataDocument,
DynamicNavDocument,
DynamicNavQueryVariables,
DynamicNavQuery,
WordpressMenuNodeIdTypeEnum
} from '@/graphql/generated/graphql';
import { LandingCoalesced } from '@/components/Landing';
import { BooksyReviews } from '@/components/Landing/Booksy';
import { getLatestBooksyReviews } from '@/lib/booksy';
import { BooksyReviewFetchResponse } from '@/types/booksy';
import {
useWordpressLoginMutation,
WordpressLoginMutation,
WordpressLoginMutationVariables,
WordpressLoginDocument
} from '@/graphql/generated/graphql';
export function Index({
other,
popular,
Header,
Footer,
places,
businessHours,
reviews
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<>
<AppLayout
title={'✂ The Fade Room Inc. ✂'}
Header={Header}
Footer={Footer}
>
<LandingCoalesced
other={other}
popular={popular}
places={places}
businessHours={businessHours}
>
<BooksyReviews reviews={reviews.reviews} />
</LandingCoalesced>
</AppLayout>
</>
);
}
export async function getStaticProps(
ctx: GetStaticPropsContext
): Promise<
GetStaticPropsResult<{
other: LandingDataQuery['other'];
popular: LandingDataQuery['popular'];
places: LandingDataQuery['Places'];
businessHours: LandingDataQuery['businessHours'];
Header: DynamicNavQuery['Header'];
Footer: DynamicNavQuery['Footer'];
reviews: BooksyReviewFetchResponse;
// Context: WordpressLoginMutation;
}>
> {
const apolloClient = initializeApollo();
await apolloClient.query<
DynamicNavQuery,
DynamicNavQueryVariables
>({
query: DynamicNavDocument,
variables: {
idHead: 'Header',
idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
idFoot: 'Footer'
}
});
await apolloClient.query<
LandingDataQuery,
LandingDataQueryVariables
>({
query: LandingDataDocument,
variables: {
other: 'all',
popular: 'popular-service',
path: process.env.NEXT_PUBLIC_GOOGLE_PLACES_PATH!,
googleMapsKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY!
}
});
const response = await getLatestBooksyReviews();
const reviews: BooksyReviewFetchResponse = await response.json();
return addApolloState(apolloClient, {
props: {
reviews
},
revalidate: 60
});
}
export default Index;
为了完整起见,这是我的@/lib/apollo.ts文件
import { useMemo } from 'react';
import {
ApolloClient,
InMemoryCache,
NormalizedCacheObject,
HttpLink
} from '@apollo/client';
import {
getAccessToken,
getAccessTokenAsCookie
} from './cookies';
import { setContext } from '@apollo/client/link/context';
import type {
PersistentContext,
CookieOptions
} from '@/types/index';
import { getCookiesFromContext } from '@/utils/regex-parsing';
import {
WORDPRESS_USER_COOKIE_EXPIRE,
WORDPRESS_USER_TOKEN_COOKIE
} from './const';
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
let apolloClient:
| ApolloClient<NormalizedCacheObject>
| undefined;
function createApolloClient(
options?: CookieOptions
): ApolloClient<NormalizedCacheObject> {
const authLink = setContext((_, { ...headers }: Headers) => {
const token =
getAccessToken(options) ?? getAccessTokenAsCookie(options);
if (!token) {
return {};
}
return {
headers: {
...headers,
'Content-Type': 'application/json; charset=utf-8',
'X-JWT-REFRESH': `Bearer ${process.env
.WORDPRESS_AUTH_REFRESH_TOKEN!}`,
'x-jwt-auth': token ? `Bearer ${token}` : ''
}
};
});
const httpLink = new HttpLink({
uri: `${process.env.NEXT_PUBLIC_ONEGRAPH_API_URL}`,
credentials: 'same-origin',
...(typeof window !== undefined && { fetch })
});
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: authLink.concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
wordpress: {
merge(existing, incoming, { mergeObjects }) {
// Invoking nested merge functions
return mergeObjects(existing, incoming);
}
},
google: {
merge(existing, incoming, { mergeObjects }) {
// Invoking nested merge functions
return mergeObjects(existing, incoming);
}
}
}
}
}
})
});
}
export function initializeApollo(
initialState: any | any = null,
context?: PersistentContext
) {
const _apolloClient =
apolloClient ??
createApolloClient({
cookies: getCookiesFromContext(
context ?? WORDPRESS_USER_TOKEN_COOKIE
)
});
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Merge the existing cache into data passed from getStaticProps/getServerSideProps
const data = { ...existingCache, ...initialState };
// const data = deepmerge(initialState, existingCache, { clone: false });
// Restore the cache with the merged data
_apolloClient.cache.restore(data);
}
// for SSG and SSR ALWAYS create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function addApolloState(client: any, pageProps: any) {
if (pageProps?.props) {
pageProps.props[
APOLLO_STATE_PROP_NAME
] = client.cache.extract();
}
return pageProps;
}
export function useApollo(pageProps: any) {
const state = pageProps[APOLLO_STATE_PROP_NAME];
const store = useMemo(() => initializeApollo(state), [state]);
return store;
}