Skip to content

Instantly share code, notes, and snippets.

@SimeonGriggs
Created December 10, 2021 11:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SimeonGriggs/5e5f555576da2c880b98342c9ea4184d to your computer and use it in GitHub Desktop.
Save SimeonGriggs/5e5f555576da2c880b98342c9ea4184d to your computer and use it in GitHub Desktop.
Reference Field Custom Input with Restrained Search
/* eslint-disable react/prop-types */
/**
* This is a Custom Input POC for a Reference field with defined search parameters on the target document
* The built-in reference field currently can filter results but only statically, not using the search query
*
* This custom input is NOT recommended as it lacks some of the amazing features of the built-in reference field
* It's also a bit wonky in terms of the UI, but it's a start
* ...and it does solve this one specific use case
*/
import React from 'react'
import {FormField} from '@sanity/base/components'
import {Text, Card, Autocomplete} from '@sanity/ui'
import {SearchIcon} from '@sanity/icons'
import PatchEvent, {set, unset} from '@sanity/form-builder/PatchEvent'
import sanityClient from 'part:@sanity/base/client'
import schema from 'part:@sanity/base/schema'
import Preview from 'part:@sanity/base/preview'
const client = sanityClient.withConfig({apiVersion: `2021-05-19`})
// Custom input for a field that would be initialised like this:
// {
// name: 'link',
// title: 'Link',
// description:
// 'Uses a custom input to search only specific field paths in the given document types',
// type: 'reference',
// to: [{type: 'category'}],
// inputComponent: CustomReferenceSearch,
// options: {searchPath: 'title'},
// },
async function searchDocs(searchTypes, searchPath, searchValue) {
// searchPath can't be a variable in the GROQ Query
const docs = await client.fetch(
`*[!(_id in path("drafts.**")) && _type in $searchTypes && ${searchPath} match $searchValue][0..50]`,
{
searchTypes,
searchValue,
}
)
// console.log(docs)
return docs
}
function createAutocompleteOptions(results, searchPath) {
return results.map((result) => ({
value: result._id,
doc: result,
payload: result[searchPath],
}))
}
const CustomReferenceSearch = React.forwardRef((props, ref) => {
const [results, setResults] = React.useState([])
const {
type, // Schema information
value, // Current field value
readOnly, // Boolean if field is not editable
placeholder, // Placeholder text from the schema
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
// Default: Reference field schema always has a `to` key
const searchTypes = React.useMemo(() => type.to.map((schemaType) => schemaType.name), [type])
// Custom: determine which path in the doc to search for results
const searchPath = React.useMemo(() => type.options.searchPath, [type])
// Creates a change handler for patching data
const handleQueryChange = React.useCallback(
async (searchQuery) => {
// console.log({searchQuery})
let searchResults
// Async callback to search for results
if (searchQuery.length > 2) {
searchResults = await new Promise((resolve) => {
// console.log(`searchin...`)
resolve(searchDocs(searchTypes, searchPath, searchQuery))
})
if (searchResults.length) {
setResults(searchResults)
}
}
},
[onChange]
)
const handleClick = React.useCallback((selectedDoc) => {
const newReference = {
_type: 'reference',
_ref: selectedDoc._id,
}
// console.log(`Setting new reference: ${JSON.stringify(newReference)}`)
onChange(PatchEvent.from(selectedDoc ? set(newReference) : unset()))
}, [])
const handleChange = React.useCallback((change) => {
// console.log(`clicked remove`, {change})
if (!change) {
onChange(PatchEvent.from(unset()))
}
}, [])
if (!searchPath) {
return (
<Card>
<Text>
Schema has no <code>options.searchPath</code> key
</Text>
</Card>
)
}
return (
<FormField
description={type.description} // Creates description from schema
title={type.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
>
<Card>
<Autocomplete
fontSize={[2]}
icon={SearchIcon}
openButton
onQueryChange={handleQueryChange}
onChange={handleChange}
options={createAutocompleteOptions(results)}
padding={[3]}
placeholder={
placeholder ?? `Search the "${searchPath}" key on ${searchTypes.join(', ')} documents`
}
readOnly={readOnly}
renderOption={({doc}) => (
<Card as="button" padding={1} onClick={() => handleClick(doc)}>
<Preview value={doc} type={schema.get(doc._type)} />
</Card>
)}
value={value}
renderValue={(value) => value._ref}
onFocus={onFocus}
onBlur={onBlur}
ref={ref}
/>
</Card>
</FormField>
)
})
CustomReferenceSearch.displayName = 'CustomReferenceSearch'
// Create the default export to import into our schema
export default CustomReferenceSearch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment