#Component switcher based on CMS API Data

1 messages · Page 1 of 1 (latest)

violet cloud
#

Hello! It's me again... It's becoming a regular occurrence... my apologies. 🥲 😅

To continue with my project based on my Headless CMS and Astro, I need a ComponentSwitcher to display the appropriate template according to the data I'm retrieving from my variable.

My variable can have 5 different values:

const template = 'Template 1' || 'Template 2' || 'Template 3' || 'Template 4' || 'Template 5';

Based on its value, I'd like to display the appropriate component.

Let's take a <Hero /> component for example:

If the template value = Template 1 , I display the <Hero1 /> component and so on.

My question is how to do this efficiently given that my variables come from my CMS API.

@primal field Florian has already given me a lead but I can't get it to work.

He said that maybe @umbral escarp could help me with this part? or someone else 😅

Thanks in advance !

umbral escarp
#

good lead from @primal field bc i'm definitely doing that with Strapi as a backend as we speak

primal field
#

for reference #general message

umbral escarp
#

do you use typescript @violet cloud ?

violet cloud
#

let's say, I'm trying 😅
coming back from 3 years of PHP to JS/TS

#

But yeah i said to use TS inside the project when i created it

umbral escarp
#

ok

#

so, 1st we'll assume you get your components config from the cms with an api

#

what you need is to have a common property in your api to be able to know which component it is

#

i'll use strapi as an example bc that's what i'm using

#

in strapi, everything has a __component: string prop

#

SO.

#
export type PageType = {
  components: ComponentType[]
}

export async function getPages() {
  const res = await fetch('my-api/pages/')

  const pages = await res.json() as PageType[]
  return pages
}
#

that's for fetching the pages and having minimal type definitions

#

now Component is missing

#

i'll take 2 components as a subset, then you can copypaste to expand the idea

#
type ComponentType1 = {
  __component: 'template1'
  prop1: string
}

type ComponentType2 = {
  __component: 'template2'
  prop2: string
}

export type ComponentType = ComponentType1 | ComponentType2
#

then you can do your components in your astro files such as:

---
import { ComponentType1 } from './component-types'

type Props = ComponentType1
const { prop1 } = Astro.props
---

{prop1}
#

now the component switcher...

#
---
import AstroComponent1 from 'Component1.astro'
import AstroComponent2 from 'Component2.astro'

import { ComponentType } from './component-types'

type Props = ComponentType

const { __component, ...props } = Astro.props


const TYPE_TO_COMPONENT: Record<ComponentType['__component'], any> = {
  'template1': AstroComponent1,
  'template2': AstroComponent2,
}

const Component = TYPE_TO_COMPONENT[__component]
if (!Component) throw new Error(`unexpected component type: ${__component}`)
---

<Component {...props} />
#

and then:

---
import ComponentSwitcher from './ComponentSwitcher.astro'
import { PageType } from '../api/getPages'

type Props = PageType

const { components } = Astro.props
---

{components.map(component => <ComponentSwitcher {...component} />}
#

the only thing left for you to do is a getPageById and then link a slug properly

#

although

#

read this carefully if you plan to add shit ton of JS per component

violet cloud
#

So I have read several times your code and have some questions

Actually I have a class WordpressServices where i fetch my data
Can i use these different functions to fetch and types or is it better to create a new one ?

public static async getPageBySlug(slug) {
    try {
      const res = await fetch(`https://pages.lu/wordpress/wp-json/wp/v2/pages?slug=${slug}`);
      const [data] = await res.json();
      return data;
    } catch (error) {
      console.error("Une erreur s'est produite lors de la récupération des données :", error);
    }
  }

( I use this fetch in every compoment file to get my data )

#

For example, this is my <Hero> compoment :

---
import { WordpressServices } from "../../data/wordpress";
interface Props {
  slug: string;
}
const {slug} = Astro.props;
const page = await WordpressServices.getPageBySlug(slug);
const bandeau = page.acf.bandeau_hero;
const color = page.acf.couleur_principale;```
---
```html
<section class="lg:h-screen w-full relative">
  <img class="absolute w-full h-full object-cover aspect-video" src={page.acf.background_hero.url} alt={page.acf.background_hero.description}>
  <div class="absolute w-full h-3/4 top-0 left-0 bg-gradient-to-b from-black to-transparent z-2"></div>
  <div class="absolute w-full h-1/2 bottom-0 left-0 bg-gradient-to-t from-black to-transparent z-2"></div>
  <div class="container mx-auto flex items-center flex-col justify-center h-screen lg:h-full">
      <div data-aos="fade-up" class="text-3xl xl:text-6xl text-white z-3 font-semibold font-montserrat w-3/4 mb-8">
        <Fragment set:html={page.acf.titre_hero} />
      </div>
      <a data-aos="fade-up" data-aos-delay="300" class="bg-[--primary-color] font-montserrat text-white font-semibold py-4 px-16 rounded-full transition-all z-3" href="">{page.acf.cta_hero}</a>
  </div>
  <div class="relative lg:absolute w-full bottom-0 left-0 bg-[--primary-color] z-3">
    <div class="container mx-auto flex gap-8 lg:gap-4 justify-center flex-wrap lg:flex-row xl:justify-between items-center font-montserrat text-white text-xl py-8">
      {
        bandeau.map((item) => (
          <div class="flex items-center justify-center text-sm lg:text-base">
            <img class="mr-4" src={item.icone.url} alt={item.icone.description}>
            <span>{item.texte}</span>
          </div>
        ))
      }
    </div>
  </div>
</section>
umbral escarp
umbral escarp
#

probably the piece you're missing is the page itself

#

it looks like this src/pages/[...page].astro:

---
export async function getStaticPaths() {
  const pageSlugs = await WordpressServices.getPageSlugs() // this you'll need to do. the goal is to retrieve all the pages as { pageId: string, url: string }

  return pageSlugs.map(({ pageId, pageUrl }) => ({
    params: { page: pageUrl },
    props: { pageId },
  }))
}

type Props = { pageId: string }
---

<PageLayout pageId={Astro.props.pageId} />
umbral escarp
#

so the idea is:

  1. in src/pages/[...page].astro you have getStaticPaths which fetch all the slugs of all your wordpress and pass only the page ids as prop
  2. you have a PageLayout which will fetch all the data of one page at a time
  3. in your PageLayout you basically iterate on your array of components, and pass each item in a ComponentSwitcher
  4. the ComponentSwitcher is basically a huge fancy TS compliant switch/case which will invoke the proper component and pass the rest of the props blindly
  5. each component are small independent pieces
#

that's the top -> bottom flow

#

i recommend having a PageLayout even tho it's not necessary per se, because it will allow you to create exceptions cases such as, you create a 404.astro and you manually write inside <PageLayout pageId="hardcoded-id-of-your-404" />