CoursesBuild content apps with Sanity App SDKDeployment and finishing touches

Build content apps with Sanity App SDK

Lesson
14

Deployment and finishing touches

Log in to watch a video walkthrough of this lesson
Log in
Video thumbnail
You have a working app. It's time to share it with your authoring team and tidy up some rough edges.
Log in to mark your progress for each Lesson and Task

You can deploy your custom application at any time. Just like your Sanity Studio, it can be deployed from the command line.

# in /app-feedback
npx sanity deploy

The first time you deploy your application, you’ll be prompted for a title. Once deployed, your app receives a unique ID which should be added to your app's sanity.cli.ts file for smoother future deployments.

Update your sanity.cli.ts file once your app is deployed with its ID
import { defineCliConfig } from "sanity/cli"
export default defineCliConfig({
app: {
organizationId: "YOUR_ORG_ID",
entry: "./src/App.tsx",
id: "YOUR_APP_ID",
},
})

You should now see your Feedback application in the left hand column of your Sanity dashboard.

Using Suspense throughout the application has given us a way to render loading spinners while data is fetched. This is great at first, but you may notice some annoying behavior when clicking through multiple documents in the feedback list.

The spinner appears almost every time you change documents. Even though the responses for these documents are cached, this occasional disappearance and re-rendering of the editing form is visual noise that we could do without.

Fortunately, React gives us a function called startTransition, which we can use to prevent more than one loading spinner when we change the selected document.

Update the Feedback component to use startTransition
import { startTransition, Suspense, useState } from "react"
import { DocumentHandle } from "@sanity/sdk-react"
import { Card, Flex, Grid, Spinner } from "@sanity/ui"
import { styled } from "styled-components"
import { FeedbackList } from "./FeedbackList"
import { FeedbackEdit } from "./FeedbackEdit"
const ScreenHeightCard = styled(Card)`
height: 100vh;
overflow: scroll;
`
export function Feedback() {
const [selectedFeedback, setSelectedFeedback] =
useState<DocumentHandle | null>(null)
const updateSelectedFeedback = (handle: DocumentHandle | null) =>
startTransition(() => setSelectedFeedback(handle))
return (
<Grid columns={5}>
<ScreenHeightCard columnStart={1} columnEnd={3}>
<Suspense fallback={<Loading />}>
<FeedbackList
setSelectedFeedback={updateSelectedFeedback}
selectedFeedback={selectedFeedback}
/>
</Suspense>
</ScreenHeightCard>
<ScreenHeightCard borderLeft columnStart={3} columnEnd={6}>
<Suspense fallback={<Loading />}>
{selectedFeedback ? (
<FeedbackEdit selectedFeedback={selectedFeedback} />
) : null}
</Suspense>
</ScreenHeightCard>
</Grid>
)
}
function Loading() {
return (
<Flex justify="center" align="center" width="fill" height="fill">
<Spinner />
</Flex>
)
}

Now, when we click through documents in the list, the previously selected document should remain visible until the next document has finished loading.

It is possible to get a pending state from the useTransition hook. Take a look in the React documentation for more details.

For performance reasons, we have used useDocumentProjection in the document list and useDocument in the editing form.

You may notice this creates a small discrepancy when changing the status of a document. It happens immediately in the editing form but takes a second to update in the document list.

Since we only need to see optimistic updates on the currently selected document—as that is the one being edited—we could create an optimistic preview component to use in the document list only for the currently selected document.

Create a new optimistic preview component for the document list
import { useRef } from "react"
import { DocumentHandle, useDocument } from "@sanity/sdk-react"
import { Box, Stack, Text } from "@sanity/ui"
import { StatusBadge } from "./StatusBadge"
type FeedbackPreviewData = {
_createdAt: string
content: string | null
author: string | null
email: string | null
status: string | null
}
export function FeedbackPreviewSelected(props: DocumentHandle) {
const previewRef = useRef<HTMLDivElement>(null)
const { data } = useDocument<FeedbackPreviewData>({ ...props })
const author = typeof data?.author === "string" ? data.author : "..."
const email = typeof data?.email === "string" ? data.email : "..."
const content = typeof data?.content === "string" ? data.content : "..."
const createdAt =
typeof data?._createdAt === "string" ? data._createdAt.split("T")[0] : "..."
const status = typeof data?.status === "string" ? data.status : "PENDING"
return (
<Stack ref={previewRef} space={3}>
<Text size={2} weight="semibold" textOverflow="ellipsis">
{author}
</Text>
<Text muted size={1} textOverflow="ellipsis">
{email} {createdAt}
</Text>
<Text size={2} textOverflow="ellipsis">
{content}
</Text>
<Box>
<StatusBadge status={status} fontSize={1} />
</Box>
</Stack>
)
}
Update the FeedbackList component to selectively render the correct component
{isSelected ? (
<FeedbackPreviewSelected {...feedback} />
) : (
<FeedbackPreview {...feedback} />
)}

If you change a Feedback item from "Approved" to "Spam" now it should be reflected immediately in the selected document preview.

Much better! Let's test what you've learned in the final lesson.

You have 4 uncompleted tasks in this lesson
0 of 4