storyblok / storyblok-react

React SDK for Storyblok CMS
MIT License
121 stars 36 forks source link

using <StoryblokComponent /> in a client Component ("use client") #1161

Open kingmauri opened 3 months ago

kingmauri commented 3 months ago

Describe the issue you're facing

I'm using Next with AppRouter. I'm not sure if this is a bug or if I've implemented something incorrectly. As soon as I use the StoryblokComponent in a client component, the log tells me that the respective component cannot be found (as if it was not registered). As soon as I remove "use client", it works. Is this a known bug, or am I misunderstanding something? Thank you very much.

'use client';
export default function Test({blok}: {blok: TestProps}) {

return (
        <div>
            {blok.testimonials.map(
              (nestedBlok: TestimonialStoryblok, index) => (
                <>

                {/* // when I call the blok manually it works */}
                <Testimonial
                  blok={nestedBlok}
                  key={nestedBlok._uid}
                  index={index}
                  visibleTestimonial={visibleTestimonial}
                ></Testimonial>

                {/* // this doesn't work. output:  "Component testimonial doesn't exist." */}
                <StoryblokComponent
                blok={nestedBlok}
                key={nestedBlok._uid} />
                </>
              )
            )}
          </div>
)
}

Reproduction

-

Steps to reproduce

No response

System Info

-

Used Package Manager

npm

Error logs (Optional)

No response

Validations

negprof commented 1 week ago

I do have the same problem.

edodusi commented 1 week ago

Hi @kingmauri @negprof a question for you: are you following the guide on the README and specifically this part https://github.com/storyblok/storyblok-react?tab=readme-ov-file#nextjs-using-app-router---live-editing-support ?

negprof commented 1 week ago

Hello @edodusi !

I have implemented both approaches in my project, and I manage them using a staging instance for Next.js with:

 <StoryblokProvider>
              <html>
              <body>
              <NavbarWrapper/>
              {children}
              <Footer/>
              </body>
              </html>
            </StoryblokProvider>

and a production instance with:

 <html>
            <body>
            <NavbarWrapper/>
            {children}
            <Footer/>
            </body>
            <StoryblokBridgeLoader options={{}}/>
            </html>

My code works with the StoryblokProvider method in development but fails in production. Is there a way to import all components within a single "use client" component? I suspect the client-side environment isn’t aware of the components. In my case, I have a slider that wraps various components selected by the user in Storyblok using blok.body.

Edit:

I found out that the hydration error is gone when wrapping the "use client" component with: <StoryblokProvider>, but getting a new error:

ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
    at Module.default (./components/storyblok/SwiperSB.jsx:3:42)
    at eval (./components/storyblok/StoryblokProvider.js:66:77)

Edit 2:

The whole problem solves when using storyblokInit inside the "use client" component. In my case:

const SwiperSB = ({blok}) => {

    storyblokInit({
        accessToken: `${process.env.STORYBLOK_API_TOKEN}`,
        use: [apiPlugin],
        components: {
            swiper: SwiperSB,
            SwiperSlideSb: SwiperSlideSb,
            ReviewBubbleSB: ReviewBubbleSB,
            singleImage: SingleImage,
        },
    });
    return (
        <div key={blok._uid} {...storyblokEditable(blok)}>
            <Swiper
            >
                {blok.body.map((nestedBlok, index) => (
                    <SwiperSlide key={nestedBlok._uid}>
                        <StoryblokComponent index={index} blok={nestedBlok}/>
                    </SwiperSlide>
                ))}
            </Swiper>
        </div>
    )

};

export default SwiperSB;

The question is, what would be the correct way to handle this for multiply components? This way, the layout.jsx is still full rsc, but only the "slider section" is rendered on the client.

edodusi commented 1 week ago

@negprof can I ask you to try the package at this PR? https://github.com/storyblok/storyblok-react/pull/1201

You can install it with this command for example

yarn add https://pkg.pr.new/@storyblok/react@c5971a8.tgz

In the PR description you can also find some instructions, be sure to also check the changed README here https://github.com/storyblok/storyblok-react/pull/1201/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5

It should be easy to integrate but let me know if you find some hiccups. I want to see if the problem that you have is related to our current implementation, that we are fixing in the mentioned PR, or some error on your side.

Thank you

note: you should not call storyblokInit in a component, try the suggested approach in the PR please

negprof commented 1 week ago

Hey @edodusi, thank you for your time. I reviewed your PR and was wondering if the proposed solution fully addresses my issue. With your approach, we still have "use client" wrapping the entire project, which prevents us from taking full advantage of SSR because the client moduletree will be bigger. Ideally, I'd prefer to use "use client" only within a specific component. In my case I dont´t need liveediting for the production instance, but I need a answer how to render with inside a "use client" component. Maybe there need to be something like a mapper function for the client in this case. Any suggestions?

edodusi commented 1 week ago

@negprof with this solution your root component is not "use client", look at app/layout.tsx this is a server component. If you then wrap your other components in a client component (like StoryblokProvider) this does not make them client components, they actually remain server components thus you are fully leveraging server rendering.

negprof commented 5 days ago

thanks for your informations @edodusi