useUser

Now that feedback documents can be marked as assigned to specific users, it would be useful to filter the feedback list of documents to just those the current user is responsible for.
The useDocuments
hook you setup initially in FeedbackList
only has a documentType
option set:
documentType: 'feedback'
However, this hook can also take filter
and params
options which may be dynamically updated by the application. Let's add some UI elements which will dynamically filter the list of returned documents.
status
import { Button, Grid } from "@sanity/ui"
type StatusSelectorProps = { status: string setStatus: (nextStatus: string) => void}
const STATUSES = ["All", "Pending", "Spam", "Approved"]
export function StatusSelector({ status, setStatus }: StatusSelectorProps) { return ( <Grid columns={[2, 2, 2, 4]} gap={1}> {STATUSES.map((statusOption) => ( <Button key={statusOption} mode={statusOption.toLowerCase() === status ? "default" : "ghost"} onClick={() => setStatus(statusOption.toLowerCase())} text={statusOption} /> ))} </Grid> )}
assignee
field.import { Switch, Inline, Text, Card } from "@sanity/ui"import { useCurrentUser } from "@sanity/sdk-react"import { Dispatch, SetStateAction } from "react"
type OnlyMineProps = { userId: string | null setUserId: Dispatch<SetStateAction<string | null>>}
export function OnlyMine({ userId, setUserId }: OnlyMineProps) { const currentUser = useCurrentUser()
return ( <Card border padding={2}> <Inline space={2}> <Text size={1} as="label" htmlFor="only-mine"> Only mine </Text> <Switch id="only-mine" disabled={!currentUser} checked={userId === currentUser?.id} onClick={() => { if (currentUser) { setUserId((currentId) => currentId === currentUser.id ? null : currentUser.id ) } }} /> </Inline> </Card> )}
Now you'll need to import these into the FeedbackList
and set a filter
that will conditionally use the params
.
FeedbackList
componentimport { Suspense, useState } from "react"import { type DocumentHandle, useDocuments } from "@sanity/sdk-react"import { Stack, Button, Spinner } from "@sanity/ui"
import { FeedbackPreview } from "./FeedbackPreview"import { StatusSelector } from "./StatusSelector"import { OnlyMine } from "./OnlyMine"
type FeedbackListProps = { selectedFeedback: DocumentHandle | null setSelectedFeedback: (feedback: DocumentHandle | null) => void}
export function FeedbackList({ selectedFeedback, setSelectedFeedback,}: FeedbackListProps) { const [userId, setUserId] = useState<string | null>(null) const [status, setStatus] = useState("all")
const { data, hasMore, loadMore } = useDocuments({ documentType: "feedback", filter: ` select(defined($userId) => assignee == $userId, true) && select( $status == "pending" => !defined(status) || status == "pending", $status == "spam" => status == $status, $status == "approved" => status == $status, true ) `, params: { userId, status }, orderings: [{ field: "_createdAt", direction: "desc" }], batchSize: 10, })
return ( <Stack space={2} padding={5}> <StatusSelector status={status} setStatus={setStatus} /> <OnlyMine userId={userId} setUserId={setUserId} /> {data?.map((feedback) => { const isSelected = selectedFeedback?.documentId === feedback.documentId
return ( <Button key={feedback.documentId} onClick={() => setSelectedFeedback(feedback)} mode={isSelected ? "ghost" : "bleed"} tone={isSelected ? "primary" : undefined} > <Suspense fallback={<Spinner />}> <FeedbackPreview {...feedback} /> </Suspense> </Button> ) })} {hasMore && <Button onClick={loadMore} text="Load more" />} </Stack> )}
You should now be able to click the buttons to filter based on user assignment or document status. Our app's really useful now!
The GROQ filter we wrote is a bit gnarly! The select()
function is used here to only filter by a param value if it is not null
.
First it uses defined()
to check if $userId
is not null
. If not, it will only find documents where the assignee
field matches $userId
. If it is null
, the value of the assignee field is not used as part of the filter.
It also applies selective filtering looking at the value of the status
field—first checking for documents without that value (or the value of "pending"), then only showing "spam" or "approved" documents if that's what the current filter matches. Lastly, it just returns everything regardless of the status
field.
We can go further. Let's link your app and the Studio more closely together.