@johannes-lindgren/storyblok-react v0.0.2-development
About
This library allows you to seamlessly integrate Storyblok into your application.
- Easy integration with Storyblok's visual editor
- 100% Typescript coverage
- Easy-to-use content delivery client (based on storyblok-js-client)
- Includes types from
@johannes-lindgren/storyblok-js
- Advanced
<RichText/>
component to render content inline. - Components for rendering dynamic content
In addition...
- All block components becomes clickable in the visual editor immediately after they've been created — even those that
have not been saved. (Neither storyblok-react
or @storyblok/storyblok-editable supports this feature. In these
libraries, only those blocks that have the
_editable
property become clickable. Blocks that haven't been saved do not have this property).
Getting Started
Follow these guide to integrate the core features of this library into your project.
Installation
Install @johannes-lindgren/storyblok-react
using npm:
npm install @johannes-lindgren/storyblok-react
Alternatively, using yarn:
yarn add @johannes-lindgren/storyblok-react
Fetch Content
Fetch content with the ContentDeliveryClient
:
const client = new ContentDeliveryClient(publicToken)
const story = await client.getStory('/dir/subdir/my-feature')
Content Types
If you use TypeScript, define your content types as such:
type FeatureData = {
type: 'info' | 'error'
body: string
}
More details below.
Content Components
Create your React components for rendering content with createBlockComponent()
:
import {makeBlockComponent} from "@johannes-lindgren/storyblok-react";
export const Feature = makeBlockComponent<FeatureData>(({block}) => (
<div className="feature">
<h2>{block.headline}</h2>
</div>
), 'feature')
This will ensure that your app is nicely integrated with the Storyblok visual editor. The type of the block
attribute is of the same type of the generic argument to makeBlockComponent()
, with a few changes:
- The
_uid
andcomponent
properties are automatically added. - All properties of the type argument are made optional. (Even if you define your your component fields to be mandatory, they can still be missing during the preview.)
Use the component as such:
<Feature block={story.content as Feature}/>
Render Dynamically
Often you want to render a component of an unknown type. I.e. the component
property of the given block is dynamic. By creating your block components with makeBlockComponent()
, you unlock the dynamic Storyblok components:
export const { DynamicBlock } = makeStoryblokComponents({
blockComponents: [Feature], // Register your block components here
})
Now you can render your content dynamically with:
<DynamicBlock block={story.content}/>
If no corresponding component has been registered in makeStoryblokComponents()
, a Fallback component will be displayed.
Note that makeBlockComponent()
has to be called with a second component for the mapping to work.
Preview Mode
makeStoryblokComponents()
also returns the <DynamicStory/>
component:
export const { DynamicStory } = makeStoryblokComponents({
blockComponents: [Feature],
})
Now you can render the story as such:
<DynamicStory story={story}/>
This has the advantage that it allows you to enable the live-preview with Storyblok's visual editor. Enable preview by wrapping the component within a <PreviewProvider>
:
<PreviewProvider
previewToken={previewToken}
enabled
>
<DynamicStory story={story}/>
</PreviewProvider>
That is all you need to enable all features of the visual editor!
Rich Text
makeStoryblokComponents()
also returns the <RichText/>
component:
export const {RichText} = makeStoryblokComponents({
blockComponents: [Feature],
})
Now you can render your rich text as such:
import {RichText as RichTextData} from "@johannes-lindgren/storyblok-js";
import {RichText} from "@resume-builder/components/storyblok-components";
export type SectionData = {
title: string
text: RichTextData
}
export const Section = makeBlockComponent<SectionData>(
({block}) => {
return (
<>
<Typography variant='h2'>{block.title}</Typography>
<RichText richText={block.text}/>
</>
)
}
)
If the end-user adds blocks to the rich text, these will be rendered within the rich text, presuming that corresponding block components have been registered within makeStoryblokComponents()
.
Details
The sections below provide additional detail to the features mentioned in the getting started section.
Resolve Relations & Links
To resolve relations,
simply pass an array to the ContentDeliveryClient.getStory()
:
const resolve_links = 'story'
const resolve_relations = ['page.otherStory']
client.getStory(slugs, {
version: previewToken ? 'draft' : 'published',
resolve_links,
resolve_relations,
language,
})
To resolve relations in preview mode, you also need to supply the values to the ` as such:
const resolve_links = 'story'
const resolve_relations = ['page.otherStory']
return (
<PreviewProvider
previewToken={previewToken}
enabled={!!previewToken}
resolveRelations={['page.feature']}
resolveLinks={'story'}
>
<LayoutStory story={layoutStory}>
<DynamicStory story={story}/>
</LayoutStory>
</PreviewProvider>
)
Any component can access the preview context with React hooks:
const preview = usePreview()
This can be useful to display different information depending on the end-user. For example, components that renders with error can be hidden in non-preview mode, but indicate the error with bright colors in preview mode. This is what the default fallback component does.
Custom Fallback Components
You can substitute the built-in default fallback components to your own.
Create a component as such:
export const MyBlockFallback = makeBlockComponent(({block}) => (
<Alert severity='error'>Unknown component type `{block.component}`</Alert>
))
We can omit the second argument, as this React component does not correspond to a specific Storyblok component.
Use the fallback in your dynamic components as such:
export const {DynamicStory, DynamicBlock, RichText} = makeStoryblokComponents({
blockComponents: [],
BlockFallback: MyBlockFallback
})
Similarly, you can create your own fallbacks for Rich Text nodes.
Story Components
makeStoryComponent()
works similar to makeBlockComponent()
, but it also enables live-updates when the end-user is in preview.
Any components that are rendered within a story components can access the story from the context with react hooks:
const story = useStory()
If you want to render story properties other than content
- for example the name
, then create your own custom story component with makeStoryComponent()
:
export const DynamicStory = makeStoryComponent(({story, children}) => (
<article>
<h1>{story.name}</h1>
<DynamicBlock block={story.content}/>
</article>
))
Now you can use this component instead of the one from makeStoryblokComponents()
.
TypeScript
See @johannes-lindgren/storyblok-js
for the various Storyblok types.
Examples & Use Cases
Some common use cases are outlined in these subsections.
Nested Blocks
If you need to render a block within a dynamic block, use <DynamicBlock/>
(from makeStoryblokComponents()
):
export type GridData = {
items: Block[]
}
export const Grid = makeBlockComponent<GridData>(({block}) => (
<>
{block.items?.map(item => (
<DynamicBlock block={item} key={item._uid}/>
))}
</>
))
Layouts
If you want to define your layouts with stories, you will typically need to pass a child prop to the story component.
export type LayoutData = {
header: HeaderBlock[]
footer: FooterBlock[]
}
export const LayoutStory = makeStoryComponent<LayoutData, { children?: ReactNode }>(({story, children}) => (
<Layout block={story.content}>
{children}
</Layout>
))
export const Layout = makeBlockComponent<LayoutData, { children?: ReactNode }>(({block, children}) => (
<>
<DynamicBlock block={block?.header?.[0]}/>
<Toolbar/>
<Main>
{children}
</Main>
<DynamicBlock block={block?.footer?.[0]}/>
</>
))
Use the story component instead of the Layout block, or the preview will not work!
2 years ago
2 years ago