简体   繁体   English

错误拉菜单 wordpress 打字稿反应 nextjs

[英]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. 现在,对于注入无头导航栏数据的导航栏组件——我使用两个单独的站点分别注入桌面和移动动态导航。 Confers more granular control + separation of concerns 提供更精细的控制 + 关注点分离

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; }

Now for the global layout, and lastly the index page to illustrate how to use apollo with getStaticProps现在是全局布局,最后是索引页来说明如何将 apollo 与 getStaticProps 一起使用

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). pages/index.tsx -- apollo 客户端隐式传入由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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM