Creating a Parent/Child Taxonomy - has 5 likes
Create common taxonomy schemas like Categories and Tags with parent/child relationships
Go to Creating a Parent/Child TaxonomySometimes your content lives outside Sanity, but you need to store and reference it from the inside of the Content Lake.
For this, we can create a custom input component which queries an API, populates choices in a dropdown, and saves the value into a Document.
If your content editors are currently copy-and-pasting values from an external service into a field, this guide is for you!
Any service with a queryable API can be turned into a dynamic set of options for a Custom Input.
Possible use cases include:
You can do so much with custom input components so if you'd like to go deeper and learn more, make sure to read the docs.
Also, you should consider the value saved in this Component similar to a weak reference. That is, if the value you have retrieved from your API is removed from that service, your Sanity content will still contain that value, but it will be out of date.
Register a new string
field in our schema along with a simple React Component. Through this guide we'll modify the Component to make it more featured.
The rest of the code examples will look only at this ProductSelector()
component.
// product.js
// Put this in a folder with your other schemas and remember to register it!
import React from 'react'
function ProductSelector() {
return <div>SKU Field</div>
}
export default {
name: 'product',
title: 'Product',
type: 'document',
fields: [
{name: 'title', title: 'Title', type: 'string'},
{
name: 'sku',
title: 'SKU',
type: 'string',
inputComponent: ProductSelector,
},
],
}
Next we'll need some packages:
@sanity/ui
to make our component's appearance suit the rest of the Studio andusehooks-ts
for its useFetch()
React Hook to add some loading/error states to a Fetch requestFrom the command line, in your Studio folder, run:
npm install @sanity/ui usehooks-ts
For this example we're going to use fakestoreapi.com for products, but you could add any publicly queryable API here. A third party API is likely to need some authentication and you will need to adjust the demo to suit.
If your targeted API does not have configurable CORS settings to allow Fetch requests from inside the Studio, you may also need to "wrap" your request in a serverless function to authenticate and return the data you need.
Let's add data fetching, along with error and loading states to our card. Plus a nice confirmation of success – just to make sure everything is working.
import {useFetch} from 'usehooks-ts'
import {Card, Spinner, Text} from '@sanity/ui'
const url = `https://fakestoreapi.com/products`
const cardProps = {shadow: 1, padding: 3, radius: 2}
function ProductSelector() {
const {data, error} = useFetch(url)
if (error)
return (
<Card tone="critical" {...cardProps}>
<Text>There has been an error</Text>
</Card>
)
if (!data) return <Spinner />
return (
<Card tone="positive" {...cardProps}>
<Text>Success! {data.length} Products Found</Text>
</Card>
)
}
All going well, you'll see a green box announcing "Success! 20 Products Found".
This is great! But, not very useful.
Let's make something useful. Add the Select
component to the imports from @sanity/ui
and map over the data to create a dropdown menu showing the title and price of each item – but with the item's id
as the value being saved to the document.
return (
<Card>
<Select>
{data.map((item) => (
<option key={item.id} value={item.id}>
{item.title} (${item.price})
</option>
))}
</Select>
</Card>
)
Now we're getting somewhere! The Select menu looks great and contains all the data we pulled from the API, but it doesn't actually do anything.
We'll need to patch
the document each time the select menu is changed, to save the item id
to the document.
To do this, we've brought in a lot of code from the Custom input components docs. I've trimmed the comments out of the below example, so for more details, read the docs.
Our Component is about to get a bit more complicated – this is so Studio UI like readOnly
and Presence will still work as expected. So it's super important!
Here's our final Component. To recap, it will:
title
and description
import React from 'react'
import {useFetch} from 'usehooks-ts'
import {Card, Spinner, Text, Select} from '@sanity/ui'
import {FormField} from '@sanity/base/components'
import PatchEvent, {set, unset} from '@sanity/form-builder/PatchEvent'
const url = `https://fakestoreapi.com/products`
const cardProps = {shadow: 1, padding: 3, radius: 2}
const ProductSelector = React.forwardRef((props, ref) => {
const {
type,
value,
readOnly,
placeholder,
markers,
presence,
compareValue,
onFocus,
onBlur,
onChange,
} = props
const {data, error} = useFetch(url)
const handleChange = React.useCallback(
// useCallback will help with performance
(event) => {
const inputValue = event.currentTarget.value
// if the value exists, set the data, if not, unset the data
onChange(PatchEvent.from(inputValue ? set(inputValue) : unset()))
},
[onChange]
)
if (error)
return (
<Card tone="critical" {...cardProps}>
<Text>There has been an error</Text>
</Card>
)
if (!data) return <Spinner />
return (
<FormField
description={type.description}
title={type.title}
__unstable_markers={markers}
__unstable_presence={presence}
compareValue={compareValue}
>
<Card>
<Select
ref={ref}
readOnly={readOnly}
value={value}
onFocus={onFocus}
onBlur={onBlur}
onChange={handleChange}
>
{placeholder && <option disabled>{placeholder}</option>}
{data.map((item) => (
<option key={item.id} value={item.id}>
{item.title} (${item.price})
</option>
))}
</Select>
</Card>
</FormField>
)
})
Create a new document, select an option and you should see an ID number written to the sku
field.
There's much more we can do. Some examples:
<Select>
for the more powerful Sanity UI <Autocomplete>
for smarter searching and visual previews of returned data.Create common taxonomy schemas like Categories and Tags with parent/child relationships
Go to Creating a Parent/Child TaxonomyCombine Sanity's blazing-fast CDN with Remix's cached at the edge pages.
Go to How to build a Remix website with Sanity.io and live previewA step-by-step guide to setup Next.js and Sanity Studio with Live Preview
Go to Live Preview with Next.js and Sanity.io: A Complete GuideGet the best of both worlds. Tailwind-styled typography and Portable Text's markup-and-components structure.
Go to ProseableText: Combine Tailwind CSS Typography with Portable Text