dohomi / storyblok-rich-text-react-renderer-ts

A React renderer for Storyblok rich text content based on TS
MIT License
7 stars 1 forks source link

[Help needed] Include custom blok within richtext #4

Closed cgpro closed 1 year ago

cgpro commented 1 year ago

Hi,

it works fine, if I don't include any of my custom bloks in my richtext field.

In my Richtext.tsx I have the following blokResolver (Fields: headline and teaser, both are richtexts): blokResolvers: { ['SplitScreenshot']: (props) => <SplitScreenshot {...props} />, },

and I get the following error: TypeError: Cannot read properties of undefined (reading 'headline')

Do you have any idea, how to solve this? The example in the repo is very minimal. My SplitScreenshot blok itself works fine too.

Sorry, I'm relatively new to storyblok and next.js ;-) An extended example with a simple blok (with a richtext field) within a richtext would be very handy.

dohomi commented 1 year ago

I'd need to see a repro to help. seems you misconfigure the use of the plugin

cgpro commented 1 year ago

Slowly I get my head wrapped around it (I think), but I am afraid it is not best practice ;-)

Richtext.tsx (with import { render } from 'storyblok-rich-text-react-renderer-ts'; etc.)

blokResolvers: {
      ['SplitScreenshot']: (props) => {
        // fix hirarchy: we need an additional blok property
        let extendedProps = {};
        extendedProps['blok'] = props;
        // @ts-ignore
        return <SplitScreenshot {...(extendedProps as SplitScreenshotStoryblok)} />;
      },
    },

SplitScreenshot.tsx

const SplitScreenshot: FunctionComponent<BlokComponentModel<SplitScreenshotStoryblok>> = ({ blok }) => {...}

If I omit the curly braces around blok, I don't have to copy the props into the new 'blok' property. But then it no longer works outside of richtext fields.

Outside of a rich text field the props are structured like this:

{
  blok: {
    headline, teaser and so on
  }
}

Inside of a rich text field the child blok/components props are structured like this:

{
  headline, teaser and so on
}

The property blok ist missing in the hierarchy.

If you see a more elegant approach, I would of course appreciate feedback.

dohomi commented 1 year ago

Check out the https://github.com/dohomi/storyblok-rich-text-react-renderer-ts#advanced-usage

You check signatures of your compoenents similar like this:

import { render } from 'storyblok-rich-text-react-renderer-ts'

function RenderRichText({body}){
  return render(body,{
  defaultBlokResolver: (name, props) => { 
console.log(name,props)
return(
    <div>
      <code>Missing blok resolver for blok type "{name}".</code>
      <pre>
        <code>{JSON.stringify(props, undefined, 2)}</code>
      </pre>
    </div>
  )
  }})
}

The rest of the body part is rendered with fallback components you don't necessarily do anything with them

cgpro commented 1 year ago

That's how I think it's a more clean approach:

 <SplitScreenshot {...({ blok: props } as SplitScreenshotStoryblok & { blok: SplitScreenshotStoryblok })} />

It's working now. Here is my config with some tailwind customizations. Maybe it helps someone to get started.

Richtext.tsx

import Link from 'next/link';
import Image from 'next/image';
import { render } from 'storyblok-rich-text-react-renderer-ts';
import { ReactNode } from 'react';

// child bloks
import SplitScreenshot from '../tailwindui/hero-sections/SplitScreenshot';
import { SplitScreenshotStoryblok } from '../component-types-sb';

export default function RichText(document) {
  return render(document, {
    nodeResolvers: {
      image: (children, props) => <Image {...props} />,
      paragraph: (children) => <p className='mt-8'>{children}</p>,
      ordered_list: (children) => (
        <ol role='list' className='max-w-xl pl-4 mt-8 space-y-2 text-gray-600 list-decimal'>
          {children}
        </ol>
      ),
      bullet_list: (children) => (
        <ul role='list' className='max-w-xl pl-4 mt-8 space-y-2 text-gray-600 list-disc'>
          {children}
        </ul>
      ),
      list_item: (children) => {
        // a new children array, because the old array is read only
        let listChildren: ReactNode[] = [];

        // @ts-ignore
        children.map((child) => {
          // mt-8 is a regular paragraph, within lists we need mt-0
          listChildren.push({ ...child, props: { ...child.props, className: 'mt-0' } });
        });
        return <li className='mt-0'>{listChildren}</li>;
      },
    },
    markResolvers: {
      bold: (children) => <strong className='font-semibold text-gray-900'>{children}</strong>,
      italic: (children) => <i>{children}</i>,
      link: (children, props) => {
        const { href, target, linktype } = props;
        if (linktype === 'email') {
          // Email links: add `mailto:` scheme and map to <a>
          return <a href={`mailto:${href}`}>{children}</a>;
        }
        if (href.match(/^(https?:)?\/\//)) {
          // External links: map to <a>
          return (
            <a href={href} target={target}>
              {children}
            </a>
          );
        }
        // Internal links: map to <Link>
        return (
          <Link href={href} legacyBehavior>
            <a>{children}</a>
          </Link>
        );
      },
    },
    blokResolvers: {
      ['SplitScreenshot']: (props) => {
        return (
          <SplitScreenshot {...({ blok: props } as SplitScreenshotStoryblok & { blok: SplitScreenshotStoryblok })} />
        );
      },
    },
    defaultStringResolver: (str) => <p>{str}</p>,
    defaultBlokResolver: (name, props) => (
      <div>
        <code>Missing blok resolver for blok type {name}.</code>
        <pre>
          <code>{JSON.stringify(props, undefined, 2)}</code>
        </pre>
      </div>
    ),
  });
}

Component.tsx

...
import RichText from '../../_helper/Richtext';
...
<p className='mt-6 text-lg leading-8 text-gray-600' {...storyblokEditable(blok)}>
  {RichText(blok.teaser)}
</p>
...