Expanding Sanity
The @codedazur/sanity-plugin-fusion package provides a set of adapters for the standard data types provided by Sanity. These adapters are designed to be flexible and extensible, but it is very likely that you’ll want to expand or customize these.
Custom Adapters
If you have additional Sanity schemas that you want to render to React components, you can create your own adapters for them. The process is the same for every type of schema and component, whether that is a tiny atom or an entire section.
Schema
Let’s say your project includes t he following person schema.
export interface PersonData {
_type: "person";
_key: string;
name: string;
dateOfBirth: string;
}
export const personSchema = defineType({
name: "person",
title: "Person",
type: "object",
fields: [
defineField({ name: "name", title: "Name", type: "string" }),
defineField({ name: "dateOfBirth", title: "Date of Birth", type: "date" }),
],
});Component
And, let’s say you want to render it to a component that displays the person’s name and their age.
export interface PersonProps {
name: string;
age: number;
}
export function Person({ name, age }: PersonProps) {
return (
<div>
<h1>{name}</h1>
<p>{age} years old</p>
</div>
);
}Adapter
An adapter is an object that has a transform function and a render function. The transform function is used to transform the data into props and the render function is used to render a component using those props.
Creating an adapter for this schema is quite simple.
import { Adapter } from "@codedazur/fusion-sanity";
import { Person, PersonProps } from "../components/Person";
import { PersonData } from "../schemas/person";
export const personAdapter: Adapter<PersonData, PersonProps> = {
transform: ({ name, dateOfBirth }) => {
return {
name,
age: new Date().getFullYear() - new Date(dateOfBirth).getFullYear(),
};
},
render: Person,
};This adapter will allow the parser to transform the PersonData into PersonProps and render the Person component. Note that if the data and props types are compatible, you can omit the transform function altogether.
Rendering
To provide the adapter to the parser, you can pass it as a key-value pair to the adapters prop, where the key is the schema’s _type.
import { SanityParser } from "@codedazur/fusion-sanity";
import { personAdapter } from "../adapters/person";
export default async function Page() {
const data = await getData(); // [{ _type: "person", _key: "1", name: ...
return (
<SanityParser
sanity={sanity}
data={data}
adapters={{ person: personAdapter }}
/>
);
}Adapter Dependencies
The example above was simple, but what if you want to render a component for a schema that depends on data from other schemas?
Schema
For example, you might have a team schema that contains a list of persons.
import { defineType } from "sanity";
export interface TeamData {
_type: "team";
_key: string;
name: string;
members: PersonData[];
}
export const teamSchema = defineType({
name: "team",
title: "Team",
type: "object",
fields: [
defineField({ name: "name", title: "Name", type: "string" }),
defineField({
name: "members",
title: "Members",
type: "array",
of: [{ type: "person" }],
}),
],
});Component
A common pattern for modular components is to pass prop data for nested components rather than allowing any ReactNode to be passed to the component, so that we can keep some control over the component’s purpose.
import { Person, PersonProps } from "./Person";
export interface TeamProps {
name: string;
members: PersonProps[];
}
export function Team({ name, members }: TeamProps) {
return (
<div>
<h1>{name}</h1>
<ul>
{members.map((member) => (
<Person key={member.name} {...member} />
))}
</ul>
</div>
);
}Adapter
Instead of redefining how the PersonData is transformed into PersonProps, we can leverage the existing personAdapter that we provided to the parser in the steps above.
import { Adapter } from "@codedazur/fusion-sanity";
import { Team, TeamProps } from "../components/Team";
import { PersonData, PersonProps } from "../schemas/person";
import { TeamData } from "../schemas/team";
type Dependencies = {
person: Adapter<PersonData, PersonProps>;
};
export const teamAdapter: Adapter<TeamData, TeamProps, Dependencies> = {
transform: ({ name, members }, context) => {
return {
name,
members: members.map(context.transform),
};
},
render: Team,
};As you can see, this adapter’s transform function uses the context parameter to transform the nested persons using the context.transform function.
Doing so requires us to promise TypeScript that an adapter for _type: "person" data will be present in the parser’s context, which is why we declare a Dependencies type and pass it as the third type argument to the generic Adapter type.
Although deferring to the other adapters present in the parser’s context is the most sustainable approach, it is not a requirement. You can also decide to transform and render people your own way in the teamAdapter’s render function, regardless of what the personAdapter does. Or, you could make use of the personAdapter’s transform function but mutate its output to better suit your Team component’s needs.