[英]Error pulling menus wordpress typescript react nextjs
I'm starting in react with typescript and I must be making a mistake, in typing, I get the following error: "TypeError: Cannot read property 'map' of undefined"我开始对打字稿做出反应,我一定是犯了一个错误,在打字时,我收到以下错误:“TypeError:无法读取未定义的属性‘地图’”
import React from 'react'
import { NextPage } from 'next'
interface HeaderProps {
menu: Array<{
title: string
url: string
}>
}
const Header: NextPage<HeaderProps> = (props: HeaderProps) => {
...
I'm trying to pull menus from Wordpress我正在尝试从 Wordpress 中提取菜单
Header.getInitialProps = async () => {
const res = await fetch('http://localhost/wp-json/myroutes/menu')
const json = await res.json()
return {
props: {
menu: json
}
}
}
export default Header
So for this, assuming this is still an issue you're grappling with, you can use a query with a fragment like so to get the data externally from headless wordpress (highly advise you use the WP-GraphQL plugin)因此,为此,假设这仍然是您正在解决的问题,您可以使用带有这样的片段的查询来从无头 wordpress 外部获取数据(强烈建议您使用 WP-GraphQL 插件)
fragment DynamicNavFragment on WordpressMenuItem {
id
label
path
parentId
}
Then for the query, you import the fragment using a #
at the top of the file:然后对于查询,您使用文件顶部的
#
导入片段:
# 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
}
}
}
}
}
}
}
}
}
To generate type definitions from this code, us a codegen.yml file as follows:要从此代码生成类型定义,请使用 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
This goes down several layers to get sub-paths and sub-sub-paths dynamically injected in the nav (header).这向下几层以获得子路径和子子路径动态注入导航(标题)。 Now for using the data itself, I can include my navbar + dynamic nav and layout components.
现在为了使用数据本身,我可以包含我的导航栏 + 动态导航和布局组件。 It is a bit of code, but map through it and you'll follow along (this is all nextjs+typescript)
这是一些代码,但通过它映射,你会跟着做(这都是 nextjs+typescript)
headless-navbar.tsx无头导航栏.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;
NOTE: DynamicNavDissected
is defined in the @/types
directory as follows注意:
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 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;
}
Now, for the Navbar component into which the headless-navbar data is injected -- I use two separate sites to inject Desktop vs Mobile dynamic nav respectively.
navbar.tsx导航栏.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导航栏.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; }
layout.tsx布局.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 client implicitly passes in props indicated by GetStaticPropsResult
(their respective queries are also present).
GetStaticPropsResult
指示的道具(它们各自的查询也存在)。
reviews
is not handled by apolloclient but by a fetching method, so it has to be explicitly passed in
reviews
不是由 apolloclient 处理,而是由获取方法处理,因此必须显式传入
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;
For the sake of thoroughness, here is my @/lib/apollo.ts
file为了彻底起见,这是我的
@/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; }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.