Using the Sanity studio client to update documents in custom components
8 replies
Last updated: Jan 25, 2022
T
I have created a custom component, which takes complex array as a value, schema is defined like this:
And type workoutgroup schema is:
From within the custom component which is client side js code, I need to make queries to update
However when client is initialised it requires auth token, which has write permissions (admin) and I shouldn’t be keeping the auth token in my JS bundle for security reasons.
I have tried using PatchEvent to make these queries, but it doesn’t seem to work.
My current idea is to move client queries to separate node proxy server and do it this way: FE -> node proxy service (which has auth token set, privately) -> Sanity
Does that feel like the best approach?
{ name: 'sections', title: 'Sections', type: 'array', inputComponent: SectionItemList, of: [ { type: DocumentTypes.workoutgroup, weak: true, }, ], },
export const workoutGroup = createDocumentSchema({ name: DocumentTypes.workoutgroup, type: 'object', fields: [ { name: 'label', title: 'Workout group name', type: 'string', validation: (Rule: any) => Rule.required(), }, { name: 'startTime', title: 'Start time', type: 'string', }, { name: 'exercises', title: 'Excercises', type: 'array', of: [ { type: DocumentTypes.workoutgroupexercise, weak: true, }, ], }, ], });
label,
startTimecreate new
DocumentTypes.workoutgroupupdate
exercises. I used
sanityClientand made queries like this:
await client .patch(document._id) .set({ [`sections[_key=="${key}"].label`]: newValue.toString() }) .commit();
I have tried using PatchEvent to make these queries, but it doesn’t seem to work.
My current idea is to move client queries to separate node proxy server and do it this way: FE -> node proxy service (which has auth token set, privately) -> Sanity
Does that feel like the best approach?
Jan 25, 2022, 4:54 PM
D
since this is within the studio, you can use the studio's client instead:
You won't have to expose your key this way
import sanityClient from 'part:@sanity/base/client' const client = sanityClient.withConfig({apiVersion: '2022-01-26'})
Jan 25, 2022, 5:01 PM
T
My custom component is a huge component, with many sub components nested, but the core looks like this:
/* eslint-disable no-console */ import { TrashIcon, CopyIcon } from '@sanity/icons'; import { Stack, Label, Card, Flex, Inline, Button, useToast, Tooltip, Box, Text, } from '@sanity/ui'; import debounce from 'lodash.debounce'; import { nanoid } from 'nanoid'; import { withDocument } from 'part:@sanity/form-builder'; import React, { FC, useRef, useCallback, useState, } from 'react'; import { IWorkoutSectionItem, IDocument } from '../../interfaces/common'; import { DocumentTypes } from '../../schemas/common/baseDocument'; import { client } from '../../utils/browserClient'; import DeleteConfirmationModal from './DeleteConfirmationModal'; import WorkoutSectionExercises from './ExerciseItemList'; import WorkoutSectionLabelInput from './LabelInput'; import WorkoutSectionsTimeInput from './StartTimeInput'; interface IWorkoutSectionItemProps extends IWorkoutSectionItem { document: IDocument; } const DEBOUNCE_TIME = 500; const WorkoutSectionItem: FC<IWorkoutSectionItemProps> = ({ label, startTime, exercises, _key: key, document, }) => { const [ confirmationIsOpen, setConfirmationIsOpen ] = useState(false); const onClose = useCallback(() => setConfirmationIsOpen(false), []); const onOpen = useCallback(() => setConfirmationIsOpen(true), []); const startDateRef = useRef(); const labelRef = useRef(); const toast = useToast(); const onLabelChange = useCallback(debounce(async (newValue) => { try { await client .patch(document._id) .set({ [`sections[_key=="${key}"].label`]: newValue.toString() }) .commit(); toast.push({ status: 'success', title: 'Successfully updated workout group title', }); } catch (e) { toast.push({ status: 'error', title: 'Failed to update workout group title', }); console.log(e); } }, DEBOUNCE_TIME), [ key ]); const onStartTimeChange = useCallback(debounce(async (newValue) => { try { await client .patch(document._id) .set({ [`sections[_key=="${key}"].startTime`]: newValue.toString() }) .commit(); toast.push({ status: 'success', title: 'Successfully updated workout group start time', }); } catch (e) { toast.push({ status: 'error', title: 'Failed to update workout group start time', }); console.log(e); } }, DEBOUNCE_TIME), [ key ]); const deleteWorkoutGroup = useCallback(async () => { try { await client .patch(document._id) .unset([ `sections[_key=="${key}"]` ]) .commit(); toast.push({ status: 'success', title: `Successfully removed "${label}"`, }); } catch (e) { toast.push({ status: 'error', title: 'Failed to remove workout group', }); console.log(e); } onClose(); }, [ key, label, onClose ]); const cloneWorkoutGroup = useCallback(async () => { const clonedWorkoutGroup = { _type: DocumentTypes.workoutgroup, _key: nanoid(), label: `${label} (copy)`, startTime, exercises, }; try { await client .patch(document._id) .prepend('sections', [ clonedWorkoutGroup ]) .commit(); toast.push({ status: 'success', title: `Successfully cloned "${label}"`, }); } catch (e) { toast.push({ status: 'error', title: 'Failed to clone workout group', }); console.log(e); } }, [ exercises, label, startTime ]); return ( <React.Fragment> <Card padding={[ 0, 0, 5, 5 ]} style={{ borderBottom: '1px dashed black' }} shadow={0}> <Flex> <Card flex={1} marginRight={4}> <WorkoutSectionLabelInput ref={labelRef.current} value={label} label="Workout group title" onChange={onLabelChange} /> </Card> <Card marginRight={4}> <WorkoutSectionsTimeInput ref={startDateRef.current} value={startTime} onChange={onStartTimeChange} label="Start time" /> </Card> <Card> <Stack space={2}> <Label>Actions</Label> <Inline space={[ 0, 0, 2 ]}> <Tooltip content={( <Box padding={2}> <Text muted size={1}> Delete workout group </Text> </Box> )} fallbackPlacements={[ 'right', 'left' ]} placement="top" portal > <Button fontSize={0} icon={TrashIcon} tone="primary" onClick={onOpen} style={{ cursor: 'pointer' }} /> </Tooltip> <Tooltip content={( <Box padding={2}> <Text muted size={1}> Clone workout group </Text> </Box> )} fallbackPlacements={[ 'right', 'left' ]} placement="top" portal > <Button fontSize={0} icon={CopyIcon} tone="secondary" onClick={cloneWorkoutGroup} style={{ cursor: 'pointer' }} /> </Tooltip> </Inline> </Stack> </Card> </Flex> <WorkoutSectionExercises exercises={exercises} workoutGroupKey={key} /> </Card> <DeleteConfirmationModal isOpen={confirmationIsOpen} onClose={onClose} title={`Do you really want to delete workout group "${label}"?`} deleteCallback={deleteWorkoutGroup} /> </React.Fragment> ); }; export default withDocument(WorkoutSectionItem);
Jan 25, 2022, 5:12 PM
T
Looks like
Does the job!
import sanityClient from 'part:@sanity/base/client'
Jan 25, 2022, 5:14 PM
T
Doing some further tests.
Jan 25, 2022, 5:14 PM
T
Just to confirm it uses sanity studio users loggin token and then it all depends on users permissions?
Jan 25, 2022, 5:14 PM
T
Cool. Thank you guys!
Jan 25, 2022, 5:15 PM
Sanity– build remarkable experiences at scale
Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.