【问题标题】:Error pulling menus wordpress typescript react nextjs错误拉菜单wordpress typescript react nextjs
【发布时间】:2020-09-19 21:42:40
【问题描述】:

我开始使用打字稿做出反应,我一定是犯了一个错误,在打字时,我收到以下错误:“TypeError: Cannot read property 'map' of undefined”

import React from 'react'
import { NextPage } from 'next'

interface HeaderProps {
  menu: Array<{
    title: string
    url: string
  }>
}

const Header: NextPage<HeaderProps> = (props: HeaderProps) => {
...

我正在尝试从 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

【问题讨论】:

    标签: reactjs typescript next.js


    【解决方案1】:

    因此,为此,假设这仍然是您正在努力解决的问题,您可以使用带有片段的查询来从无头 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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-17
      • 2012-10-15
      • 2017-06-30
      相关资源
      最近更新 更多