React - How to Display a List of Schemas/Content Types but Only Allow the User to Select One

13 replies
Last updated: Jun 29, 2022
Hey 👋 Just a quick question, is it possible to display a list of schemas/content types but only allow the user to select one?
May 6, 2022, 3:32 PM
Like radio inputs somewhat?
May 6, 2022, 3:47 PM
Just made a quick example and attached a screenshot (ignore the names they make little sense 😆). Hoping to allow a user to select one, and only one, of the two options (in this example, Navigation Item or Link). I am just using an array type at the moment so can limit it to 1 using validation but this catches the errors after they’ve added too much content (they assume they did it by accident) which doesn’t seem great. Any help is super appreciated 😄
May 6, 2022, 3:56 PM
Ah! I see.
May 6, 2022, 3:57 PM
We have exactly the same thing at work and we solved it with a
.max(1)
validation rule.
May 6, 2022, 3:57 PM
It may seem annoying that you can effectively have several items in a draft, but it’s actually pretty handy for editors, rather than having to delete their item first before creating a new one.
May 6, 2022, 3:58 PM
And you ensure that a production document can’t have more than 1 with the validation rule. Win-win. 🙂
May 6, 2022, 3:58 PM
Yeh good points! Thanks for the quick reply 🙂. It would be cool to see how others have dealt with it
May 6, 2022, 4:02 PM
May 6, 2022, 5:11 PM
user A
has previously helped me with a custom array part which should do what you’re after! Once the array has the maximum number of items (set using
validation: (Rule) => Rule.max(x)
), the ‘Add item…’ button is removed:
May 6, 2022, 5:47 PM
Here’s the
LimitArray.js
part:
import React from "react";
import { isReferenceSchemaType } from "@sanity/types";
import { AddIcon } from "@sanity/icons";
import { Button, Grid, Menu, MenuButton, MenuItem } from "@sanity/ui";
import { useId } from "@reach/auto-id";

const LimitArray = React.forwardRef((props, ref) => {
    const { type, readOnly, children, onCreateValue, onAppendItem, value } = props;
    const menuButtonId = useId();
    const insertItem = React.useCallback(
        (itemType) => {
            const item = onCreateValue(itemType);
            onAppendItem(item);
        },
        [onCreateValue, onAppendItem]
    );
    const handleAddBtnClick = React.useCallback(() => {
        insertItem(type.of[0]);
    }, [type, insertItem]);
    if (readOnly) {
        return null;
    }
    const maxLength = type.validation[0]._rules.find((rule) => rule.flag === "max");
    if (maxLength && value && value.length >= maxLength.constraint) {
        return null;
    }
    return (
        <Grid
            gap={1}
            style={{ gridTemplateColumns: "repeat(auto-fit, minmax(100px, 1fr))" }}
            ref={ref}
        >
            {type.of.length === 1 ? (
                <Button icon={AddIcon} mode="ghost" onClick={handleAddBtnClick} text="Add item" />
            ) : (
                <MenuButton
                    button={<Button icon={AddIcon} mode="ghost" text="Add item…" />}
                    id={menuButtonId || ""}
                    menu={
                        <Menu>
                            {type.of.map((memberDef, i) => {
                                const referenceIcon =
                                    isReferenceSchemaType(memberDef) &&
                                    (<http://memberDef.to|memberDef.to> || []).length === 1 &amp;&amp;
                                    <http://memberDef.to[0].icon;|memberDef.to[0].icon;>
                                const icon =
                                    memberDef.icon || memberDef.type?.icon || referenceIcon;
                                return (
                                    &lt;MenuItem
                                        key={i}
                                        text={memberDef.title || memberDef.type?.name}
                                        onClick={() =&gt; insertItem(memberDef)}
                                        icon={icon}
                                    /&gt;
                                );
                            })}
                        &lt;/Menu&gt;
                    }
                /&gt;
            )}
            {children}
        &lt;/Grid&gt;
    );
});

export default LimitArray;
You need to add this file to your
parts
in your
sanity.json
as follows:
{
    "implements": "part:@sanity/form-builder/input/array/functions",
    "path": "./schemas/parts/LimitArray.js"
}
And then you can use this by setting
validation
on an array field:
{
    title: "Content",
    name: "content",
    type: "array",
    of: [{ type: "img" }, { type: "vid" }, { type: "word" }],
    validation: (Rule) =&gt; Rule.max(2),
},
May 6, 2022, 5:48 PM
Wow, thanks for sharing 👍
May 6, 2022, 6:45 PM
Just tried it and it works perfectly 👌 Thanks again!
May 6, 2022, 7:37 PM
2 months later, still coming in clutch. Thanks
user A
and
user P
Jun 29, 2022, 8:02 PM

Sanity.io – build remarkable experiences at scale

Sanity is a customizable solution that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Categorized in