storyblok / storyblok-astro

Astro SDK for Storyblok CMS
161 stars 29 forks source link

Can't nest Storyblok components #811

Closed vvaldesc closed 3 months ago

vvaldesc commented 4 months ago

storyblok.com

I can't use one component within another and I can't find any information in the documentation.

The input parameter of the Main block is supposed to be another blok, I understand that in code this will be a storyblok component, when I nest main with another component it fails and I'm not able to fix it, my only alternative is not to use nested elements right now.


Expected Behavior

No error, StoryblokComponent working properly

Current Behavior

Cannot render StoryblokComponent. 'blok' prop is undefined.

components/StoryblokComponent.astro:16:9

import components from "virtual:storyblok-components";
import options from "virtual:storyblok-options";
import camelcase from "camelcase";
import type { SbBlokData } from "../dist/types";
import type { AstroComponentFactory } from "astro/runtime/server/index.js";

export interface Props {
  blok: SbBlokData;
  [prop: string]: unknown;
}

const { blok, ...props } = Astro.props;

if (!blok) {
  throw new Error(
    "Cannot render StoryblokComponent. 'blok' prop is undefined."
  );
}

/**
 * convert blok components to camel case to match vite-plugin-storyblok-components
 */
let key = camelcase(blok.component);

const componentFound: boolean = key in components;

let Component: AstroComponentFactory;

if (!componentFound) {
  if (!options.enableFallbackComponent)
    throw new Error(
      `Component could not be found for blok "${blok.component}"! Is it defined in astro.config.mjs?`
    );
  else {
    Component = components["FallbackComponent"];
  }
} else {
  Component = components[key];
}

Steps to Reproduce

CMS web:

image

Main is the nestable element.

image

Main props:

image

Index.astro:

---
title: "Home"
import { useStoryblokApi } from '@storyblok/astro'
// @ts-ignore
import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'
import Layout from '@/layouts/Layout.astro';

const storyblokApi = useStoryblokApi()
const { data } = await storyblokApi.get('cdn/stories/home', {
  version: 'draft',
})

const story = data.story
---
<Layout title='Home'>
  <!--<Slider blok={story.content}/>-->
  <StoryblokComponent blok={story.content} />
  <!-- <Index_main_nav/> -->
</Layout>

Main.astro:

---
// @ts-ignore
import { storyblokEditable } from "@storyblok/astro";
import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'

const { blok } = Astro.props
---
<div {...storyblokEditable(blok)} class="container" >
    <StoryblokComponent
        className=""
        blok={blok.content}
    />
</div>

<style>
    .container {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        width: 90%;
        margin: 0 auto;
    }
</style>

astro config:

import { defineConfig } from 'astro/config';
import tailwind from "@astrojs/tailwind";
import svelte from "@astrojs/svelte";
import react from "@astrojs/react";
import icon from "astro-icon";
import storyblok from "@storyblok/astro";
import basicSsl from '@vitejs/plugin-basic-ssl'
import metaTags from "astro-meta-tags";

// https://astro.build/config
export default defineConfig({
  redirects: {
    '/home': '/'
  },
  vite: {
    plugins: [basicSsl()],
    server: {
      https: true,
    },
  },
  integrations: [tailwind(), svelte(), react(), icon(), metaTags(),
    storyblok({
      accessToken: XXXXXXXX
      region: "eu",
      bridge: true,
      apiOptions: {}, // storyblok-js-client options
      componentsDir: "src",
      enableFallbackComponent: false,
      customFallbackComponent: "",
      useCustomApi: false,
      components: {
        page: 'components/Storyblok_native/Page',
        feature: 'components/Storyblok_native/Feature',
        grid: 'components/Storyblok_native/Grid',
        teaser: 'components/Storyblok_native/Teaser',
        testblog: 'components/Storyblok_native/Testblog',
        animated_main_div: 'components/Animated_main_div',
        main: 'components/Main',
        slider: 'sections/Slider'
      }
    }),
  ]
});

Package.json:

{
  "name": "tfc-provisional",
  "type": "module",
  "version": "0.0.1",
  "scripts": {
    "dev": "astro dev --port 4322",
    "start": "astro dev --port 4322",
    "build": "astro check && astro build",
    "preview": "astro preview --port 4322",
    "astro": "astro"
  },
  "dependencies": {
    "@astrojs/check": "^0.5.10",
    "@astrojs/react": "^3.3.2",
    "@astrojs/svelte": "^5.4.0",
    "@astrojs/tailwind": "^5.1.0",
    "@storyblok/astro": "^4.0.5",
    "@types/react": "^18.3.1",
    "@types/react-dom": "^18.3.0",
    "astro": "^4.7.1",
    "astro-icon": "^1.1.0",
    "astro-meta-tags": "^0.3.0",
    "astro-seo": "^0.8.3",
    "axios": "^1.6.8",
    "eslint-plugin-react": "^7.34.1",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "svelte": "^4.2.15",
    "tailwindcss": "^3.4.3",
    "typescript": "^5.4.5"
  },
  "devDependencies": {
    "@iconify-json/mdi": "^1.1.66",
    "@vitejs/plugin-basic-ssl": "^1.1.0",
    "prettier": "^3.2.5",
    "prettier-plugin-astro": "^0.13.0"
  }
}

Thank you for your attention

mhunt commented 4 months ago

I'm seeing undefined as well. However, I was able to resolve this by restoring my version of the component in Storyblok's backend, and using the name I setup originally. This could have been an issue with the data not pulling in with the proper field name.


import { storyblokEditable } from '@storyblok/astro'
import StoryblokComponent from '@storyblok/astro/StoryblokComponent.astro'

const { blok } = Astro.props

// if (!blok.services) {
//   throw new Error(
//     "Cannot render StoryblokComponent. 'services' prop is undefined."
//   );
// }

// Log the entire 'blok' to verify its structure
//console.log('Full blok:', JSON.stringify(blok, null, 2));
console.log('Services:', blok.services);

---

<main {...storyblokEditable(blok)}>
  {
    blok.services?.map((blok) => {
      return <StoryblokComponent blok={blok} />
    })
  }
</main>```
dipankarmaikap commented 3 months ago

@vvaldesc I just checked with multiple nested components, and it's working as expected. However, I noticed an issue in your Main.astro component. I believe you're implementing it incorrectly, which is causing the problem. Your Main.astro file should be quite similar to your Page.astro file.

As per the screenshot you shared, you have named the field block in your Storyblok schema, so it will be blok.block. Since it's an array, you need to map it and then pass each block to the StoryblokComponent like this: <StoryblokComponent blok={blok} />.

Main.astro

---
import { storyblokEditable } from "@storyblok/astro";
import StoryblokComponent from "@storyblok/astro/StoryblokComponent.astro";

const { blok } = Astro.props;
---

<div {...storyblokEditable(blok)} class="container">
  {
    blok.block?.map((blok) => {
      return <StoryblokComponent blok={blok} />;
    })
  }
</div>

<style>
  .container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 90%;
    margin: 0 auto;
  }
</style>
dipankarmaikap commented 3 months ago

Hi @vvaldesc, We are closing this PR as we have not heard back from you. In the future, you can reopen it if you still think this is an issue.

vvaldesc commented 3 months ago

Thanks for your attention, I will try this soon.