Lesson
6
Add drag-and-drop elements
Go beyond "click-to-edit" with additional affordances for rearranging arrays in your front end
Log in to mark your progress for each Lesson and Task
Update the
post
schema type fields to include an array of "related posts" to render at the bottom of your post
type documents.src/sanity/schemaTypes/postType.ts
export const postType = defineType({ // ...all other settings fields: [ // ...all other fields defineField({ name: "relatedPosts", type: "array", of: [{ type: "reference", to: { type: "post" } }], }), ],});
Update your single post query to return the array and resolve any references.
src/sanity/lib/queries.ts
export const POST_QUERY = defineQuery(`*[_type == "post" && slug.current == $slug][0]{ _id, title, body, mainImage, publishedAt, "categories": coalesce( categories[]->{ _id, slug, title }, [] ), author->{ name, image }, relatedPosts[]{ _key, // required for drag and drop ...@->{_id, title, slug} // get fields from the referenced post }}`);
Update your types now that the GROQ query has changed.
npm run typegen
Create a new component to render the related Posts
src/components/RelatedPosts.tsx
// src/components/RelatedPosts.tsx
"use client";
import Link from "next/link";import { createDataAttribute } from "next-sanity";import { POST_QUERYResult } from "@/sanity/types";import { client } from "@/sanity/lib/client";import { useOptimistic } from "next-sanity/hooks";
const { projectId, dataset, stega } = client.config();export const createDataAttributeConfig = { projectId, dataset, baseUrl: typeof stega.studioUrl === "string" ? stega.studioUrl : "",};
export function RelatedPosts({ relatedPosts, documentId, documentType,}: { relatedPosts: NonNullable<POST_QUERYResult>["relatedPosts"]; documentId: string; documentType: string;}) { const posts = useOptimistic< NonNullable<POST_QUERYResult>["relatedPosts"] | undefined, NonNullable<POST_QUERYResult> >(relatedPosts, (state, action) => { if (action.id === documentId && action?.document?.relatedPosts) { // Optimistic document only has _ref values, not resolved references return action.document.relatedPosts.map( (post) => state?.find((p) => p._key === post._key) ?? post ); } return state; }); if (!posts) { return null; } return ( <aside className="border-t"> <h2>Related Posts</h2> <div className="not-prose text-balance"> <ul className="flex flex-col sm:flex-row gap-0.5" data-sanity={createDataAttribute({ ...createDataAttributeConfig, id: documentId, type: documentType, path: "relatedPosts", }).toString()} > {posts.map((post) => ( <li key={post._key} className="p-4 bg-blue-50 sm:w-1/3 flex-shrink-0" data-sanity={createDataAttribute({ ...createDataAttributeConfig, id: documentId, type: documentType, path: `relatedPosts[_key=="${post._key}"]`, }).toString()} > <Link href={`/post/${post?.slug?.current}`}>{post.title}</Link> </li> ))} </ul> </div> </aside> );}
You will notice data-sanity
attributes being added to the wrapping and individual tags of the list. As well as a useOptimistic hook to apply these changes in the UI immediately, while the mutation in the content lake is still happening.
Update the
Post
component to include the RelatedPosts
component.src/components/RelatedPosts.tsx
import { Author } from "@/components/Author";import { Categories } from "@/components/Categories";import { components } from "@/sanity/portableTextComponents";import { PortableText } from "next-sanity";import { POST_QUERYResult } from "@/sanity/types";import { PublishedAt } from "@/components/PublishedAt";import { Title } from "@/components/Title";import { urlFor } from "@/sanity/lib/image";import Image from "next/image";import { RelatedPosts } from "@/components/RelatedPosts";
export function Post(props: NonNullable<POST_QUERYResult>) { const { _id, title, author, mainImage, body, publishedAt, categories, relatedPosts, } = props;
return ( <article className="grid lg:grid-cols-12 gap-y-12"> <header className="lg:col-span-12 flex flex-col gap-4 items-start"> <div className="flex gap-4 items-center"> <Categories categories={categories} /> <PublishedAt publishedAt={publishedAt} /> </div> <Title>{title}</Title> <Author author={author} /> </header> {mainImage ? ( <figure className="lg:col-span-4 flex flex-col gap-2 items-start"> <Image src={urlFor(mainImage).width(400).height(400).url()} width={400} height={400} alt="" /> </figure> ) : null} {body ? ( <div className="lg:col-span-7 lg:col-start-6 prose lg:prose-lg"> <PortableText value={body} components={components} /> <RelatedPosts relatedPosts={relatedPosts} documentId={_id} documentType="post" /> </div> ) : null} </article> );}
Add a few Related Posts to any post document. Now within Presentation, you should be able to drag-and-drop to reorder their position, and see the content change in the Studio.
You have 5 uncompleted tasks in this lesson
0 of 5