CoursesBuild content apps with Sanity App SDKuseUser

Build content apps with Sanity App SDK

Lesson
11

useUser

Log in to watch a video walkthrough of this lesson
Log in
Video thumbnail
Filter the queried list of documents based on the current user and other selections.
Log in to mark your progress for each Lesson and Task

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.

Create a new component to dynamically filter documents by 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>
)
}
Create another document to toggle an additional filter for the 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.

Update the FeedbackList component
import { 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.

You have 3 uncompleted tasks in this lesson
0 of 3