@portabletext/react with typed arguments
npm install @portabletext/react @portabletext-typed/react
After using @sanity-typed/types and @sanity-typed/client and you have typed blocks, use PortableText
from this library as you would from @portabletext/react
to get fully typed arguments!
post.ts
:
// import { defineArrayMember, defineField, defineType } from "sanity";
import {
defineArrayMember,
defineField,
defineType,
} from "@sanity-typed/types";
/** No changes using defineType, defineField, and defineArrayMember */
export const post = defineType({
name: "post",
type: "document",
title: "Post",
fields: [
defineField({
name: "content",
type: "array",
title: "Content",
validation: (Rule) => Rule.required(),
of: [
defineArrayMember({ type: "image" }),
defineArrayMember({
type: "block",
of: [defineArrayMember({ type: "file" })],
styles: [
{ title: "Normal", value: "normal" as const },
{ title: "Foo", value: "foo" as const },
],
lists: [
{ title: "Bullet", value: "bullet" as const },
{ title: "Bar", value: "bar" as const },
],
marks: {
decorators: [
{ title: "Strong", value: "strong" as const },
{ title: "Baz", value: "baz" as const },
],
annotations: [
defineArrayMember({
name: "link",
type: "object",
title: "Link",
fields: [
defineField({
name: "href",
type: "string",
validation: (Rule) => Rule.required(),
}),
],
}),
defineArrayMember({
name: "qux",
type: "object",
title: "Qux",
fields: [
defineField({
name: "value",
type: "string",
validation: (Rule) => Rule.required(),
}),
],
}),
],
},
}),
],
}),
],
});
with-portabletext-react.tsx
:
import type { InferGetStaticPropsType } from "next";
import { Fragment } from "react";
// import { PortableText } from "@portabletext/react";
import { PortableText } from "@portabletext-typed/react";
import { client } from "../sanity/client";
export const getStaticProps = async () => ({
props: {
posts: await client.fetch('*[_type=="post"]'),
},
});
const Index = ({ posts }: InferGetStaticPropsType<typeof getStaticProps>) => (
<>
<h1>Posts</h1>
{posts.map(({ _id, content }) => (
<Fragment key={_id}>
<h2>Post</h2>
<PortableText
value={content}
components={{
types: {
// From Siblings
image: ({ isInline, value }) => (
<div>
typeof {isInline} === false,
<br />
typeof {JSON.stringify(value)} === ImageValue,
</div>
),
// From Children
file: ({ isInline, value }) => (
<span>
typeof {isInline} === true,
<span />
typeof {JSON.stringify(value)} === FileValue,
</span>
),
},
block: {
// Non-Default Styles
foo: ({ children, value }) => (
<div>
typeof {JSON.stringify(value)} === PortableTextBlock{"<"} ...
{">"} & {"{"} style: "foo" {"}"},
<span />
{children}
</div>
),
},
list: {
// Non-Default Lists
bar: ({ children, value }) => (
<ul>
<li>
typeof {JSON.stringify(value)} === ToolkitPortableTextList &
{"{"} listItem: "bar" {"}"},
</li>
{children}
</ul>
),
},
listItem: {
// Non-Default Lists
bar: ({ children, value }) => (
<li>
typeof {JSON.stringify(value)} === PortableTextBlock{"<"} ...
{">"} & {"{"} listItem: "bar" {"}"},
<span />
{children}
</li>
),
},
marks: {
// Non-Default Decorators
baz: ({ children, markKey, markType, value }) => (
<span>
typeof {value} === undefined,
<span />
typeof {markKey} === "baz",
<span />
typeof {markType} === "baz",
<span />
{children}
</span>
),
// Non-Default Annotations
qux: ({ children, markKey, markType, value }) => (
<span>
typeof {JSON.stringify(value)} === {"{"}
_key: string,
<span />
_type: "qux",
<span />
value: typeof {value.value} === string,
{"}"},
<span />
typeof {markKey} === string,
<span />
typeof {markType} === "qux",
<span />
{children}
</span>
),
},
}}
/>
</Fragment>
))}
</>
);
export default Index;
The supported Typescript version is now 5.4.2 <= x <= 5.6.3. Older versions are no longer supported and newer versions will be added as we validate it.