I mange tilfeller ønsker vi å registrere informasjon fra brukere eller besøkende på en nettside, for eksempel rating og/eller kommentarer til filmer, produkter eller lignende, eller å føre timer på et prosjekt i et intranett. Datamodellene har ofte et array-felt i en innholdstype for å lagre relatert data til bestemt innhold. I Sanity vil et eksempel på slik data være et felt av felttypen Array, som kan inneholde innholdstypen reviews. Dette array-feltet finnes i innholdstypen product:

Å skrive anmeldelser rett inn i Sanity Studio i array-feltet er enkelt, men hvordan tar vi dem imot fra brukerne via grensesnittet på en nettside? Det skal vi se på nå.
Forutsetninger
Dersom du ønsker å kode dette som et testeksempel, trenger du en konto på Sanity, et Sanity-prosjekt og en React-applikasjon koblet til Sanity-prosjektet. Dersom du ikke har det, kan du gjennomføre tutorialen «Start et prosjekt med React, Sanity og Sass fra scratch» først!
Innholdstyper
I dette eksempelet tenker vi oss innholdstypene product og review, som vist i illustrasjonen over. Med den datamodellen vil Sanity-schemaet for innholdstypen product se slik ut:
export default {
name: 'product',
type: 'document',
title: 'Products',
fields: [
{
name: 'title',
type: 'string',
title: 'Product title'
},
{
name: 'description',
type: 'string',
title: 'Description'
},
{
name: 'price',
type: 'string',
title: 'Price'
},
{
name: 'image',
type: 'image',
title: 'Product image'
},
{
name: 'reviews',
type: 'array',
of: [{type: "review"}],
title: 'Reviews'
},
]
}
Code language: JSON / JSON with Comments (json)
Legg merke til feltet reviews (linje 21-26). Felttypen er array, og arrayet består av elementer av innholdstypen review. La oss nå lage schemaet til innholdstypen review:
export default {
name: 'review',
type: 'object',
title: 'Reviews',
fields: [
{
name: 'name',
type: 'string',
title: 'Name'
},
{
name: 'comment',
type: 'text',
title: 'Comment'
},
{
name: 'rating',
type: 'number',
title: 'Rating'
}
]
}
Code language: JSON / JSON with Comments (json)
Her lager vi en innholdstype som er av typen object (se linje 3). Denne vil ikke dukke opp i Sanity Studio som en egen innholdstype, men kun være tilgjengelig inne på et produkt.
OBS: hvis du koder dette som et eksempel, husk på å koble de nye schemaene til schema.js og sørg for at de dukker opp i Sanity Studio!
Nå har vi innholdsstrukturen klar. Men hvordan tar vi imot data fra et brukergrensesnitt og setter disse inn i arrayen? La oss først lage grensesnittet som skal ta dem imot (her tar jeg forutsetningen om at utseendet stiles med CSS av dere selv).
Grensesnittet vil kunne se noe omtrent slikt ut:

OBS: Siden denne tutorialen fokuserer på å oppdatere et array-felt, kommer vi ikke til å se på componenten Review
og uthenting av data fra Sanity her.
Component: Product
//component Product
import ReviewForm from "./ReviewForm"
import Review from "./Review"
export default function Product() {
//... kode for å hente produktinfo
return (
<article className="product">
{/*HTML for produktinfo*/}
</article>
<section id="reviews">
{ /* reviews-arrayen i product-innholdstypen mappet opp: */
product.reviews.map((review, index) =>
<Review key={index} review={review} />
}
</section>
<section id="add-review">
<ReviewForm />
</section>
)
}
Code language: JavaScript (javascript)
Component: ReviewForm
//component ReviewForm
export default function ReviewForm() {
return (
<form>
<p>
<label for="name">Your name</label>
<input type="text" name="name" id="name" />
</p>
<p>
<label for="name">Your Rating</label>
<select name="rating" id="rating">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</p>
<p>
<label for="name">Your comment</label>
<textarea name="comment" id="comment"></textarea>
</p>
<p><button>Save review</button>
)
}
Code language: JavaScript (javascript)
Få ut data fra <form>
Når vi har skjemaet klart, må vi få tak i dataene når en bruker fyller ut feltene. Det finnes ulike måter å gjøre dette på, men for å gjøre det veldig oversiktlig bruker vi en state for hvert felt, og oppdaterer statene ved endringer i feltene:
//component ReviewForm
import {useState} from "react"
export default function ReviewForm() {
//Prepare states for field data:
const [name, setName] = useState(null)
const [rating, setRating] = useState(null)
const [comment, setComment] = useState(null)
//Create functions for handling data:
const handleNameChange = (e) => {
e.preventDefault()
setName(e.target.value)
}
const handleRatingChange = (e) => {
e.preventDefault()
setRating(e.target.value)
}
const handleCommentChange = (e) => {
e.preventDefault()
setComment(e.target.value)
}
return (
<form>
<p>
<label for="name">Your name</label>
<input type="text" name="name" id="name" value={name} onChange={handleNameChange} />
</p>
<p>
<label for="name">Your Rating</label>
<select name="rating" id="rating" onChange={handleRatingChange}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</p>
<p>
<label for="name">Your comment</label>
<textarea name="comment" id="comment" value={comment} onChange={handleCommentChange} />
</p>
<p><button>Save review</button>
)
}
Code language: JavaScript (javascript)
Med kodeoppdateringen vil endringer i skjemaet lagres i states, og være tilgjengelige når vi skal sette dem inn i Sanity. Før vi starter den jobben, må vi gjøre Sanity mulig å skrive til.
OBS: Dersom du allerede har aktivert credentials i CORS-origin og har et editor-token tilgjengelig i Sanity-databasen, kan du hoppe over neste steg. Dersom dette hørtes ukjent ut, sørg for at neste steg er utført.
Sett opp skrivetilgang i Sanity og Sanity-klienten
Gå inn på sanity.io/manage og finn ditt prosjekt. Klikk deg inn på prosjektet, og inn på fanen «API». Her må du først tillate credentials i CORS-origin til din applikasjon.
CORS origin med credentials
Hvis du kjører react-applikasjonen lokalt, er det sannsynligvis http://localhost:3000 som er URL-en du må gi tilgang til. Husk å huke av for «Allow credentials».
OBS: Hvis du har en CORS-origin til localhost:3000 allerede UTEN credentials, slett denne og opprett en ny i henhold til steget over.

Token (tilgangsnøkkel)
Når du har satt opp CORS origin, scroll videre ned til overskriften Token. Her skal du opprette en token med editor-rettigheter, som betyr at denne tokenen (token betyr i praksis «tilgangsnøkkel») har både lese- og skriverettigheter.
OBS: Kopier tokenen til et lokalt tekstdokument så snart du har laget den. Du vil ikke få tilgang til å se denne igjen etter den er opprettet, så ta vare på den!

Client med skrivetilgang
Du har muligens en Sanity-client allerede, som ser noe ala dette ut:
//utils/sanity/client.js
import sanityClient from '@sanity/client'
const options = {
projectId: "xyz123",
dataset: "production"
}
const client = sanityClient({
...options,
apiVersion: "2021-10-21",
useCdn: true
});
Code language: JavaScript (javascript)
Denne klienten har ikke skrivetilgang. Vi lager en client til, som vi bruker i de tilfellene vi trenger skrivetilgang:
import sanityClient from '@sanity/client'
const options = {
projectId: "xyz123",
dataset: "production"
}
const client = sanityClient({
...options,
apiVersion: "2021-10-21",
useCdn: true
});
export const writeClient = sanityClient({
...options,
token: "the-token-you-created-at-sanity.io/manage",
useCdn: false
})
Code language: JavaScript (javascript)
Du må selvfølgelig bytte ut projectId
med din egen prosjektid, og token
skal være tilgangsnøkkelen du lagde i forrige steg.
OBS: Det er absolutt ikke beste praksis å la token ligge i koden slik som dette. Token er en privat tilgangsnøkkel som ikke skal deles. I dette eksempelet gjør vi det slik for enkelhet, men i et prosjekt som skal publiseres er det bedre praksis å legge denne i en .env-fil. Les mer om .env (environment)-filer i react-prosjekter her.
Nå har vi opprettet skrivetilgang til Sanity, og har en client som lar react-applikasjonen vår skrive til databasen.
Asynkron funksjonalitet for å oppdatere array-felt i Sanity
Nå skal vi lage funksjonalitet som skjer når vi trykker Lagre-knappen i et review-skjema. Denne funksjonen skal kjøre en service – eller en spørring mot Sanity-databasen som ber om å opprette et nytt element i arrayen reviews
på et produkt.
For å få til det, må vi også kjenne til ID-en til produktet vi skal oppdatere! Vi antar i koden at vi har hentet produktinformasjonen til et produkt i componenten Product
. Da sender vi med produktid-en som en prop
til ReviewForm
-componenten:
//component Product
import ReviewForm from "./ReviewForm"
import Review from "./Review"
import {useState} from "react"
export default function Product() {
const [product, setProduct] = useState(null)
//... kode for å hente produktinfo fra Sanity og lagre i state product her
return (
<article className="product">
{/*HTML for produktinfo*/}
</article>
<section id="reviews">
{ /* reviews-arrayen i product-innholdstypen mappet opp: */
product.reviews.map((review, index) =>
<Review key={index} review={review} />
}
</section>
<section id="add-review">
<ReviewForm productid={product?._id} />
</section>
)
}
Code language: JavaScript (javascript)
Under forutsetning at vi har lagret produktinformasjonen i en state kalt product
, sender vi her med product._i
d inn i ReviewForm
-componenten på linje 21. Nå må vi også oppdatere ReviewForm
til å ta imot denne propen. Deretter kan vi lage funksjonen som skal kjøre når vi sender skjemaet:
//component ReviewForm
import {useState} from "react"
export default function ReviewForm({projectid}) {
//Prepare states for field data:
const [name, setName] = useState(null)
const [rating, setRating] = useState(null)
const [comment, setComment] = useState(null)
//Create functions for handling data:
const handleNameChange = (e) => {
e.preventDefault()
setName(e.target.value)
}
const handleRatingChange = (e) => {
e.preventDefault()
setRating(e.target.value)
}
const handleCommentChange = (e) => {
e.preventDefault()
setComment(e.target.value)
}
const handleSubmit = async (e) => {
e.preventDefault()
const result = await updateReview(prosjektid,name,rating,comment)
console.log(result)
}
return (
<form>
<p>
<label for="name">Your name</label>
<input type="text" name="name" id="name" value={name} onChange={handleNameChange} />
</p>
<p>
<label for="name">Your Rating</label>
<select name="rating" id="rating" onChange={handleRatingChange}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</p>
<p>
<label for="name">Your comment</label>
<textarea name="comment" id="comment" value={comment} onChange={handleCommentChange} />
</p>
<p><button onClick={handleSubmit}>Save review</button>
)
}
Code language: JavaScript (javascript)
Det vil fortsatt ikke skje noe lagring når vi klikker Save review-knappen enda, siden vi prøver å kalle en service updateReview som ikke er laget enda. Men den skal vi lage nå! Forutsatt at vi har en mappe i /src
kalt utils/sanity
(der vi lagret clienten vi laget tidligere), kan vi lage en mappe services som vi oppretter en fil kalt reviewServices.js
i.
//utils/sanity/services/reviewServices.js
import {writeClient} from "../client"
export async function updateReview(id, n, r, c) {
const result = await writeClient.patch(id)
.setIfMissing({reviews: []})
.append("reviews", [{name: n, rating: r, comment: c}]
.commit({autoGenerateKeys: true})
.then(() => {return "Review added successfully!"})
.catch((err) => {return "Review save failed: " + err.message})
return result
}
Code language: JavaScript (javascript)
La oss bryte ned koden for å se hva vi gjør her:
- Vi importerer
writeClient
-en vi lagde tidligere. Dette for å ha skrivetilgang og få lov til å sende data til Sanity-databasen. - Vi lager en asynkron funksjon, en service, kalt
updateReview
som tar imot fire parametere: id (prosjektid), n (name), r (rating) og c (comment). - Vi kaller metoden
writeClient.patch()
, og sender med parameterenid
..patch
er en metode Sanity bruker for å oppdatere allerede eksisterende data. Siden produktet allerede eksisterer, og reviews lagres i et array-felt på et produkt, skal vi nå oppdatere eksisterende innhold ved å legge til mer informasjon i et felt, ikke opprette et helt nytt dokument. - Vi kaller deretter metoden
setIfMissing()
, og forteller at dersom feltet reviews ikke allerede har data, gjør vi klar en array som kan ta imot dataene vi sender nå. - Deretter kaller vi metoden
.append()
, som har som oppgave og sette data inn på slutten av en array. Vi sender med to parametere; feltnavnet og dataobjectet. Dataobjectet må ha keys som matcher schema-et til dette dataene, ergo må name-, rating- og comment-nøklene hete det samme som de tilsvarende field-names i schemaet reviews. .commit({autoGenerateKeys: true})
sørger for at alle elementer i arrayen får en unik _key, og kan dermed pekes ut for videre administrasjon, eksempelvis oppdatering eller sletting (ikke del av denne tutorialen).then(() => { //message }
kobler vi på for å returnere en melding dersom alt går bra etter vi har satt inn data..catch((err) => { //message }
brukes for å returnere en feilmelding dersom ting ikke fungerer.
Når vi klikker Save review-knappen i ReviewForm
-propen nå, kaller vi updateReview
-servicen vi nettopp har laget, og sender med dataene vi har lagret i statene i ReviewForm
. Når updateReview
-servicen kjører, ber denne Sanity om å patche, eller oppdatere, dokumentet med id-en vi har sendt med servicen. Oppdateringen er å sette et nytt element med data inn i et array-felt.
Da skal vi være i boks! Nå vil det være mulig å oppdatere et array-felt i et document i Sanity direkte fra React-grensesnittet. Du sitter igjen med en filstruktur som ser omtrent slik ut:

For mange states?
Det finnes mange mer effektive måter å ta imot data fra skjemaet på enn å lage en state for hver input. Se tutorialen How to build forms in react fra Digital Ocean for et godt eksempel på hvordan hooken useReducer kan brukes for å effektivisere uthentingen av data.
Lære mer?
Her er noen fornuftige kilder til dokumentasjon som beskriver delene vi må gjennom for å få til det vi har gjort i denne tutorialen: