代码之家  ›  专栏  ›  技术社区  ›  Jesse Winton

传递渲染道具在下一个13中不起作用

  •  0
  • Jesse Winton  · 技术社区  · 1 年前

    我正试图在Next 13和应用程序目录中为Contentful的实时预览功能构建一个自定义组件。我的想法是创建一个客户端组件,通过允许传递泛型类型来接受数据道具,类型安全。这样地:

    LivePreviewWrapper.tsx

    'use client'
    
    import { useContentfulLiveUpdates } from '@contentful/live-preview/react'
    
    const isFunction = <T extends CallableFunction = CallableFunction>(value: unknown): value is T =>
      typeof value === 'function'
    
    export const runIfFunction = <T, U>(valueOrFn: T | ((...fnArgs: U[]) => T), ...args: U[]) => {
      return isFunction(valueOrFn) ? valueOrFn(...args) : valueOrFn
    }
    
    type MaybeRenderProp<P> = React.ReactNode | ((props: P) => React.ReactNode)
    
    type LivePreviewWrapperProps<T> = {
      children: MaybeRenderProp<{
        updatedData: T
      }>
      data: T
    }
    
    export const LivePreviewWrapper = <T extends Record<string, unknown>>({
      children,
      data
    }: LivePreviewWrapperProps<T>) => {
      const updatedData = useContentfulLiveUpdates<T>(data, { locale: 'en-US' })
    
      return runIfFunction(children, { updatedData })
    }
    

    然后,我可以将其拉入页面组件,向下传递我从Contentful中提取的数据,包括类型,然后获得实时更新。但是,当我尝试在页面组件中以这种方式运行它时,会出现以下错误:

    Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
      <... data={{...}} children={function}>
                                 ^^^^^^^^^^
    

    这就是我的页面组件的样子:

    import { notFound } from 'next/navigation'
    
    import { ContentfulCta, type ContentfulCtaProps } from '~/components/contentful/cta'
    import { ContentfulGrid, type ContentfulGridProps } from '~/components/contentful/grid'
    import { ContentfulHero, type ContentfulHeroProps } from '~/components/contentful/hero'
    import { ContentfulHighlight, type ContentfulHighlightProps } from '~/components/contentful/highlight'
    import { ContentfulHorizontalTabs, type ContentfulHorizontalTabsProps } from '~/components/contentful/horizontal-tabs'
    import { ContentfulSpeaker, type ContentfulSpeakerProps } from '~/components/contentful/speaker'
    import { ContentfulTextSession, type ContentfulTextSessionProps } from '~/components/contentful/text'
    import { LogoList } from '~/components/marketing/logo-list'
    import { LivePreviewWrapper } from '~/components/shared/live-preview-wrapper'
    import { getContentfulParams, getPageBySlug, type ContentfulPage } from '~/models/contentful'
    import { ContentfulProvider } from '~/providers/contentful'
    import { type TypePageSkeleton } from '~/types/generated/contentful'
    import { type PageParamsWithSearch } from '~/types/helpers'
    import { formatParamsSlug } from '~/utils/core'
    
    import '@contentful/live-preview/style.css'
    
    type LandingPageParams = PageParamsWithSearch<{ slug: string[] }>
    
    export const generateStaticParams = async () => {
      const pages = await getContentfulParams<TypePageSkeleton>('page')
    
      return pages.items.map((page) => ({
        params: {
          slug: formatParamsSlug(page.fields.slug)
        }
      }))
    }
    
    export const revalidate = 60
    export const dynamic = 'force-dynamic'
    
    export default async function ContentfulLandingPage({ params, searchParams }: LandingPageParams) {
      const isDraftMode = searchParams?.draftMode === 'enabled'
      const data = await getPageBySlug({ slug: params.slug, isDraftMode })
    
      if (!data) {
        notFound()
      }
    
      return (
        <ContentfulProvider>
          <main className='pb-8 lg:pb-12'>
            <LivePreviewWrapper<ContentfulPage> data={data}>
              {({ updatedData }) => {
                return (
                  <div className='space-y-8'>
                    {updatedData.fields.body.map((entry) => {
                      if (!entry) return null
                      switch (entry.sys.contentType.sys.id) {
                        case 'hero': {
                          return <ContentfulHero key={entry.sys.id} entry={entry as ContentfulHeroProps} />
                        }
    
                        case 'highlightSession': {
                          return <ContentfulHighlight key={entry.sys.id} entry={entry as ContentfulHighlightProps} />
                        }
    
                        case 'grid': {
                          return <ContentfulGrid key={entry.sys.id} entry={entry as ContentfulGridProps} />
                        }
    
                        case 'textSession': {
                          return <ContentfulTextSession key={entry.sys.id} entry={entry as ContentfulTextSessionProps} />
                        }
    
                        case 'speaker': {
                          return <ContentfulSpeaker key={entry.sys.id} entry={entry as ContentfulSpeakerProps} />
                        }
    
                        case 'cta': {
                          return <ContentfulCta key={entry.sys.id} entry={entry as ContentfulCtaProps} />
                        }
    
                        case 'horizontalTabs': {
                          return (
                            <ContentfulHorizontalTabs key={entry.sys.id} entry={entry as ContentfulHorizontalTabsProps} />
                          )
                        }
    
                        case 'customersLogos': {
                          return <LogoList key={entry.sys.id} />
                        }
    
                        default:
                          console.warn(entry.sys.contentType, 'was not handled')
                          return null
                      }
                    })}
                  </div>
                )
              }}
            </LivePreviewWrapper>
          </main>
        </ContentfulProvider>
      )
    }
    

    我不明白为什么会返回这个错误;有人知道如何修复它吗?我不知道还能尝试什么。它似乎与 useContentfulLiveUpdates 钩子,但类型 updatedData 是一个对象。

    0 回复  |  直到 1 年前
        1
  •  0
  •   Reuben Drummond    1 年前

    现阶段,函数在Next.js中不可串行化。渲染道具作品RSC->RSC或RCC->RCC而不是RSC->碾压混凝土,但它似乎。在这种情况下,在我看来,编译器可以很容易地变得足够智能来处理这种情况,但我认为Next.js 13应用程序目录和RSC需要一些时间才能成熟。

    在我看来,创建一个中间客户端组件是可行的。

    "use client"
    // ... 
    
    export const MyLivePreview = ({ data }: { data: WhateverDataType }) => <LivePreviewWrapper<ContentfulPage> data={data}>
              {({ updatedData }) => {
                return (
                  <div className='space-y-8'>
                    {updatedData.fields.body.map((entry) => {
                      if (!entry) return null
                      switch (entry.sys.contentType.sys.id) {
                        case 'hero': {
                          return <ContentfulHero key={entry.sys.id} entry={entry as ContentfulHeroProps} />
                        }
    
                        case 'highlightSession': {
                          return <ContentfulHighlight key={entry.sys.id} entry={entry as ContentfulHighlightProps} />
                        }
    
                        case 'grid': {
                          return <ContentfulGrid key={entry.sys.id} entry={entry as ContentfulGridProps} />
                        }
    
                        case 'textSession': {
                          return <ContentfulTextSession key={entry.sys.id} entry={entry as ContentfulTextSessionProps} />
                        }
    
                        case 'speaker': {
                          return <ContentfulSpeaker key={entry.sys.id} entry={entry as ContentfulSpeakerProps} />
                        }
    
                        case 'cta': {
                          return <ContentfulCta key={entry.sys.id} entry={entry as ContentfulCtaProps} />
                        }
    
                        case 'horizontalTabs': {
                          return (
                            <ContentfulHorizontalTabs key={entry.sys.id} entry={entry as ContentfulHorizontalTabsProps} />
                          )
                        }
    
                        case 'customersLogos': {
                          return <LogoList key={entry.sys.id} />
                        }
    
                        default:
                          console.warn(entry.sys.contentType, 'was not handled')
                          return null
                      }
                    })}
                  </div>
                )
              }}
            </LivePreviewWrapper>
    

    然后

    export default async function ContentfulLandingPage({ params, searchParams }: LandingPageParams) {
      const isDraftMode = searchParams?.draftMode === 'enabled'
      const data = await getPageBySlug({ slug: params.slug, isDraftMode })
    
      if (!data) {
        notFound()
      }
    
      return (
        <ContentfulProvider>
          <main className='pb-8 lg:pb-12'>
            <MyLivePreview data={data} />
          </main>
        </ContentfulProvider>
      )
    }
    

    令人烦恼的是,根据我使用Next.js 13应用程序目录的经验,需要进行大量的代码拆分。我想编译器会变得更聪明,知道什么是服务器,什么是客户端。

    如果这有帮助,请告诉我(注意,我实际上并没有测试任何这些)。否则,我们将乐意帮助诊断问题。

        2
  •  0
  •   Sweety SK    1 年前

    使用useServer钩子可能会解决您的问题,请检查代码,为了进一步参考,我将附上链接

    堆栈溢出: Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server"

    下一个文档: https://nextjs.org/docs/getting-started/react-essentials#when-to-use-server-and-client-components

    github: https://github.com/vercel/next.js/discussions/47846

    // page.tsx
    
    "use server";
    
    import { ClientComponent } from './ClientComponent.tsx';
    
    async function deleteItem(itemId: string) {
      "use server"; // mark function as a server action (fixes the error)
    
      // TODO add item deletion logic
      return null;
    }
    
    export function Page() {
      return <ClientComponent deleteItem={deleteItem} />
    }
    // ClientComponent.tsx
    
    export function ClientComponent({ deleteItem }) {
      return (
        <button onClick={async () => {
          await deleteItem("foobar");
          alert("item has been deleted");
        }}>
          delete item
        </button>
      );
    }
    
        3
  •  -2
  •   Robert Mihai Ionas    1 年前

    像这样添加useServer:

    const MyLiveComponent = ({ updatedData }) => {
      return (
        <div className='space-y-8'>
          {updatedData.fields.body.map((entry) => {
                      if (!entry) return null
                      switch (entry.sys.contentType.sys.id) {
                        case 'hero': {
                          return <ContentfulHero key={entry.sys.id} entry={entry as ContentfulHeroProps} />
                        }
    
                        case 'highlightSession': {
                          return <ContentfulHighlight key={entry.sys.id} entry={entry as ContentfulHighlightProps} />
                        }
    
                        case 'grid': {
                          return <ContentfulGrid key={entry.sys.id} entry={entry as ContentfulGridProps} />
                        }
    
                        case 'textSession': {
                          return <ContentfulTextSession key={entry.sys.id} entry={entry as ContentfulTextSessionProps} />
                        }
    
                        case 'speaker': {
                          return <ContentfulSpeaker key={entry.sys.id} entry={entry as ContentfulSpeakerProps} />
                        }
    
                        case 'cta': {
                          return <ContentfulCta key={entry.sys.id} entry={entry as ContentfulCtaProps} />
                        }
    
                        case 'horizontalTabs': {
                          return (
                            <ContentfulHorizontalTabs key={entry.sys.id} entry={entry as ContentfulHorizontalTabsProps} />
                          )
                        }
    
                        case 'customersLogos': {
                          return <LogoList key={entry.sys.id} />
                        }
    
                        default:
                          console.warn(entry.sys.contentType, 'was not handled')
                          return null
                      }
                    })}
        </div>
      );
    };
    
    export default function ContentfulLandingPage({ params, searchParams }: LandingPageParams) {
    const isDraftMode = searchParams?.draftMode === 'enabled'
      const data = await getPageBySlug({ slug: params.slug, isDraftMode })
    
      if (!data) {
        notFound()
      }
      return (
        <ContentfulProvider>
          <main className='pb-8 lg:pb-12'>
            <LivePreviewWrapper<ContentfulPage> data={data}>
              {useServer(() => <MyLiveComponent />)}
            </LivePreviewWrapper>
          </main>
        </ContentfulProvider>
      );
    }