Skip to content

Latest commit

 

History

History
160 lines (119 loc) · 5.61 KB

MIGRATING.md

File metadata and controls

160 lines (119 loc) · 5.61 KB

Migrating to @portabletext/react v2

@portabletext/react@1 allowed configuring custom components through React context. In v2, this functionality has been removed in order to allow using the component with React Server Components (RSC).

If you were previously using the context provider, we now suggest creating a "wrapper component" (eg predefines which React components to use), or defining your own context that holds the components.

Migrating from @sanity/block-content-to-react to @portabletext/react

This document outlines the differences between @portabletext/react and @sanity/block-content-to-react so you can adjust your code to use the newer @portabletext/react.

The goal of the new package is to make the module more ergonomic to use, and more in line with what you'd expect from a React component.

BlockContent renamed to PortableText

PortableText is an open-source specification, and as such we're giving it more prominence through the library and component renaming.

Also note that the component is now a named export, not the default export as in @sanity/block-content-to-react:

// From:
import BlockContent from '@sanity/block-content-to-react'
<BlockContent {/* ... */} />

// ✅ To:
// Not the default export anymore
import { PortableText } from '@portabletext/react'
<PortableText {/* ... */} />

blocks renamed to value

This component renders any Portable Text content or custom object (such as codeBlock, mapLocation or callToAction). As blocks is tightly coupled to text blocks, we've renamed the main input to value.

// From:
<BlockContent blocks={[/* ... */]} />

// ✅ To:
<PortableText value={[/* ... */]} />

serializers renamed to components

"Serializers" are now named "Components", which should make their role as custom renderers of content more understandable for React developers.

// From:
<BlockContent {/* ... */} serializers={{
  marks: {/* ... */},
  types: {/* ... */},
  list: {/* ... */},
  // ...
}} />

// ✅ To:
<PortableText {/* ... */} components={{
  marks: {/* ... */},
  types: {/* ... */},
  list: {/* ... */},
  // ...
}} />

New component properties

The properties for custom components (previously "serializers") have changed slightly:

  • Blocks: node has been renamed to value
  • Marks: mark has been renamed to value

Easier customization of individual block styles

Previously, if you wanted to override you'd need to override the rendering of headings, blockquotes, or other block styles, you'd need to re-define the entire block renderer (serializers.types.block):

// From:
const BlockRenderer = (props) => {
  const {style = 'normal'} = props.node

  if (/^h\d/.test(style)) {
    const level = style.replace(/[^\d]/g, '')
    return React.createElement(style, {className: `heading-${level}`}, props.children)
  }

  if (style === 'blockquote') {
    return <blockquote>- {props.children}</blockquote>
  }

  // Fall back to default handling
  return BlockContent.defaultSerializers.types.block(props)
}

;<BlockContent blocks={input} serializers={{types: {block: BlockRenderer}}} />

You are now able to provide different React components for different block styles - handy if you just want to override the rendering of headings, but not other styles, for instance.

// ✅ To:
<PortableText
  value={input}
  components={{
    block: {
      // Customize block types with ease
      h1: ({children}) => <h1 className="text-2xl">{children}</h1>,

      // Same applies to custom styles
      customHeading: ({children}) => (
        <h2 className="text-lg text-primary text-purple-700">{children}</h2>
      ),
    },
  }}
/>

No container rendered

Previously the component would render a container element around the rendered blocks, unless a single block was given as the input. This was done because at the time the module was written, React did not support returning fragments (eg multiple children without a parent container element). This component requires React >= 17, which means we can use <React.Fragment> when multiple blocks are present.

Images aren't handled by default anymore

We've removed the only Sanity-specific part of the module, which was image handling. You'll have to provide a component to specify how images should be rendered yourself in this new version.

We've seen the community have vastly different preferences on how images should be rendered, so having a generic image component included out of the box felt unnecessary.

import urlBuilder from '@sanity/image-url'
import {getImageDimensions} from '@sanity/asset-utils'

// Barebones lazy-loaded image component
const SampleImageComponent = ({value}) => {
  const {width, height} = getImageDimensions(value)
  return (
    <img
      src={urlBuilder().image(value).width(800).fit('max').auto('format').url()}
      alt={value.alt || ' '}
      loading="lazy"
      style={{
        // Avoid jumping around with aspect-ratio CSS property
        aspectRatio: width / height,
      }}
    />
  )
}

// You'll now need to define your own image component
;<PortableText
  value={input}
  components={{
    // ...
    types: {
      image: SampleImageComponent,
    },
  }}
/>

Written in Typescript

The new module is written in TypeScript - which means a better experience when you're building with TypeScript yourself, but also with editors/IDEs which provide auto-completing the available props and warnings about mistypes.