DEV Community

Rohit Madas
Rohit Madas

Posted on

Sanity V3: Display an Array of References as a Checklist

Sanity IO ( Co-author credits to - RD Pennell for v2 Version as base )

If you're building a Sanity Studio and want to display an array of reference fields as a multi-select checklist, here's a full working solution tailored for Sanity V3.
You’ll define:

A custom input component ReferenceSelect

A schema using this component via the components.input override

// Schema.js
{
 name: "ReferenceMultiSelect",
 description: "Select categories",
 title: "Category Type",
 type: "array",
 of: [
 {
 type: "reference",
 to: { type: "categories" },
 },
 ],
 components: {
 input: ReferenceSelect,
 },
 },

Enter fullscreen mode Exit fullscreen mode
// ReferenceSelect.jsx
import React, { useEffect, useState } from "react";
import { Card, Flex, Checkbox, Box, Text } from "@sanity/ui";
import client from "./client";
import { useId } from "@reach/auto-id";
import { FormField, PatchEvent, set } from "sanity";

const ReferenceSelect = React.forwardRef((props, ref) => {
 const [categories, setCategories] = useState([]);

 useEffect(() => {
 const fetchCountries = async () => {
 await client
 .fetch(
 `*[_type == 'categories']{
 ...,
 _id,
 category
 }`
 )
 .then(setCategories);
 };
 fetchCountries();
 }, []);

 const {
 type, // Schema information
 value, // Current field value
 readOnly, // Boolean if field is not editable
 markers, // Markers including validation rules
 presence, // Presence information for collaborative avatars
 compareValue, // Value to check for "edited" functionality
 onFocus, // Method to handle focus state
 onBlur, // Method to handle blur state
 onChange, // Method to handle patch events,
 } = props;

 const handleClick = React.useCallback(
 (e) => {
 const inputValue = {
 _key: e.target.value.slice(0, 10),
 _type: "reference",
 _ref: e.target.value,
 };

 if (value) {
 if (value.some((country) => country._ref === inputValue._ref)) {
 onChange(
 PatchEvent.from(
 set(value.filter((item) => item._ref != inputValue._ref))
 )
 );
 } else {
 onChange(PatchEvent.from(set([...value, inputValue])));
 }
 } else {
 onChange(PatchEvent.from(set([inputValue])));
 }
 },
 [value]
 );

 const inputId = useId();

 return (
 <FormField
 description={type?.schemaType.description} // Creates description from schema
 title={type?.schemaType.title} // Creates label from schema title
 __unstable_markers={markers} // Handles all markers including validation
 __unstable_presence={presence} // Handles presence avatars
 compareValue={compareValue} // Handles "edited" status
 inputId={inputId} // Allows the label to connect to the input field
 readOnly={readOnly}
 >
 {categories.map((cat, index) => (
 <Card key={index} padding={2}>
 <Flex align="center">
 <Checkbox
 id={cat._id}
 style={{ display: "block" }}
 onClick={handleClick}
 onChange={() => console.log("changed")}
 value={cat._id}
 checked={
 value ? value.some((item) => item._ref === cat._id) : false
 }
 />
 <Box flex={1} paddingLeft={3}>
 <Text>
 <label htmlFor={cat._id}>{cat.category}</label>
 </Text>
 </Box>
 </Flex>
 </Card>
 ))}
 </FormField>
 );
});

export default ReferenceSelect;

Enter fullscreen mode Exit fullscreen mode

🧩 Notes for Integration
🔁 _key is derived from the _ref value to keep the array diff-friendly for Sanity.
⚠️ onClick is used instead of onChange to maintain toggle behavior due to Sanity’s event handling.
💾 This assumes you have a working Sanity V3 setup and a categories document type defined.
Link to my original article on Sanity.

Top comments (0)