Lesson
14
Deployment and finishing touches
Log in to watch a video walkthrough of this lesson
Log in

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-feedbacknpx 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