# Build A Fullstack App with Remix, Prisma & MongoDB: CRUD, Filtering & Sorting
URL: https://www.prisma.io/blog/fullstack-remix-prisma-mongodb-3-By5pmN5Nzo1v
Published: 2022-04-27
Authors: Sabin Adams
Tags: education
Series: Build A Fullstack App with Remix, Prisma & MongoDB
Description: Learn how to build and deploy a fullstack application using Remix, Prisma, and MongoDB. In this article, we will be building the functionality that allows users to send kudos to each other along with some searching and sorting options.
> Blog posts describe Prisma as it was on the date shown and are not kept current. Before implementing from a post, check the documentation index at https://www.prisma.io/docs/llms.txt and the changelog at https://www.prisma.io/changelog.md.
> For an index of every published post, fetch https://www.prisma.io/blog/llms.txt.
Welcome to the third article of this series where you are learning how to build a full-stack application from the ground
up using MongoDB, Prisma, and Remix! In this part, you will build out the main piece of the application which displays
a user's kudos feed and allows them to send kudos to other users.
## Table Of Contents [#table-of-contents]
* [Introduction](#introduction)
* [Development environment](#development-environment)
* [Build a home route](#build-a-home-route)
* [Resource routes](#resource-routes)
* [Add the user list panel](#add-the-user-list-panel)
* [Query for all users and sort the results](#query-for-all-users-and-sort-the-results)
* [Provide the users to the user panel](#provide-the-users-to-the-user-panel)
* [Build the user display component](#build-the-user-display-component)
* [Add the ability to log out](#add-the-ability-to-log-out)
* [Add the ability to send kudos](#add-the-ability-to-send-kudos)
* [Update the Prisma schema](#update-the-prisma-schema)
* [Nested routes](#nested-routes)
* [Fetch a user by their id](#fetch-a-user-by-their-id)
* [Open a portal](#open-a-portal)
* [Build the modal component](#build-the-modal-component)
* [Build the form](#build-the-form)
* [Allow the user to customize their kudo](#allow-the-user-to-customize-their-kudo)
* [Add a kudo display component](#add-a-kudo-display-component)
* [Build the action to send kudos](#build-the-action-to-send-kudos)
* [Build a kudos feed](#build-a-kudos-feed)
* [Build a search bar](#build-a-search-bar)
* [Build the search bar action](#build-the-search-bar-action)
* [Display the most recent kudos](#display-the-most-recent-kudos)
* [Summary & What's next](#summary--whats-next)
[Watch the video on YouTube](https://www.youtube.com/watch?v=Mx9Xsq9JNXo)
## Introduction [#introduction]
In the [last part](https://www.prisma.io/blog/fullstack-remix-prisma-mongodb-2-ZTmOy58p4re8) of this series you built your application's sign in and sign up forms and implemented session-based authentication. You also updated your Prisma schema to account for a new embedded document in the `User` model that will hold a user's profile data.
In this part you will build the main functionality of the application: the kudos feed. Each user will have a feed of kudos other users have sent them. Users will also be able to send other users kudos.
In addition, you will implement some searching and filtering to make it easier to find kudos in the feed.
> The starting point for this project is available in the [part-2](https://github.com/sabinadams/kudos-remix-mongodb-prisma/tree/part-2) branch of the GitHub repository. If you'd like to see the final result of this part, head over to the [part-3](https://github.com/sabinadams/kudos-remix-mongodb-prisma/tree/part-3) branch.
### Development environment [#development-environment]
In order to follow along with the examples provided, you will be expected to ...
* ... have [Node.js](https://nodejs.org) installed.
* ... have [Git](https://git-scm.com/downloads) installed.
* ... have the [TailwindCSS VSCode Extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) installed. *(optional)*
* ... have the [Prisma VSCode Extension](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) installed. *(optional)*
> **Note**: The optional extensions add some really nice intellisense and syntax highlighting for Tailwind and Prisma.
## Build a home route [#build-a-home-route]
The main section of your application will live in a `/home` route. Set up that route by adding a `home.tsx` file in the `app/routes` folder.
This new file should export a function component called `Home` for now, along with a [`loader`](https://remix.run/docs/en/v1/api/conventions#loader) function that redirects to the user to the login screen they are not logged in.
```tsx
// app/routes/home.tsx
import { LoaderFunction } from '@remix-run/node'
import { requireUserId } from '~/utils/auth.server'
export const loader: LoaderFunction = async ({ request }) => {
await requireUserId(request)
return null
}
export default function Home() {
return
Home Page
}
```
This `/home` route will act as the main page of your application rather than the base url.
Currently, the `app/routes/index.tsx` file *(the `/` route)* renders a React component. That route should only ever redirect a user: either to the `/home` or `/login` route. Set up a [resource route](https://remix.run/docs/en/v1/guides/resource-routes) in its place to achieve that functionality.
### Resource routes [#resource-routes]
A resource route is a route that does not render a component, but can instead respond with any type of response. Think of it as a simple API endpoint. In your `/` route's case, you will want it to return a `redirect` response with a `302` status code.
Delete the existing `app/routes/index.tsx` file and replace it with an `index.ts` file where you will define the resource route:
```ts
// app/routes/index.ts
import { LoaderFunction, redirect } from '@remix-run/node'
import { requireUserId } from '~/utils/auth.server'
export const loader: LoaderFunction = async ({ request }) => {
await requireUserId(request)
return redirect('/home')
}
```
> **Note**: The file's extension was changed to `.ts` because this route will never render a component.
The `loader` above will first check if a user is logged in when they hit the `/` route. The `requireUserId` function will redirect to `/login` if there isn't a valid session.
If there is a valid session, the `loader` returns a `redirect` to the `/home` page.

## Add the user list panel [#add-the-user-list-panel]
Start off your home page by building a component that will list the site's users on the left side of the screen.
Create a new file in the `app/components` folder named `user-panel.tsx`:
```tsx
// app/components/user-panel.tsx
export function UserPanel() {
return (
My Team
Users go here
)
}
```
This creates the side panel that will contain the list of users. The component is *static* though, meaning it does not perform any actions or vary in any way.
Before making this component more *dynamic* by adding a list of users, import it into the `app/routes/home.tsx` page and render it onto the page.
```tsx
// app/routes/home.tsx
import { LoaderFunction } from '@remix-run/node'
import { requireUserId } from '~/utils/auth.server'
import { Layout } from '~/components/layout'
import { UserPanel } from '~/components/user-panel'
export const loader: LoaderFunction = async ({ request }) => {
await requireUserId(request)
return null // <- A loader always has to return some value, even if that is null
}
export default function Home() {
return (
)
}
```
The code above imports the new component and the `Layout` component, then renders the new component within the layout.

### Query for all users and sort the results [#query-for-all-users-and-sort-the-results]
Now you need to actually show the list of users within the panel. You should already have a file where user-related functions will live: `app/utils/user.server.ts`.
Add a new function to that file that queries for any users in your database. This function should take in a `userId` parameter and sort the results by the user's first name in *ascending* order:
```ts
// app/utils/user.server.ts
//...
export const getOtherUsers = async (userId: string) => {
return prisma.user.findMany({
where: {
id: { not: userId },
},
orderBy: {
profile: {
firstName: 'asc',
},
},
})
}
```
The `where` filter excludes any documents whose `id` matches the `userId` parameter. This will be used to grab every `user` *except the currently logged in user*.
> **Note**: Notice how easy it is to sort by fields within an embedded document?
In `app/routes/home.tsx`, import that new function and invoke it within the `loader`. Then return the user list using Remix's `json` helper:
```tsx
diff // app/routes/home.tsx
// ...
import {
+ json,
LoaderFunction
} from '@remix-run/node'
+import { getOtherUsers } from '~/utils/user.server'
export const loader: LoaderFunction = async ({ request }) => {
- await requireUserId(request)
- return null
+ const userId = await requireUserId(request)
+ const users = await getOtherUsers(userId)
+ return json({ users })
}
// ...
```
> **Note**: Any code that is run within a [`loader`](https://remix.run/docs/en/v1/guides/data-loading) function is not exposed to the client-side code. You can thank Remix for this awesome feature!
If you had any users in your database and outputted the `users` variable inside of the loader, you should see a list of all users *except yourself*.

> **Note**: The entire `profile` embedded document was retrieved as a nested object without having to explicitly include it.
You will now have the data available. It's time to do something with it!
### Provide the users to the user panel [#provide-the-users-to-the-user-panel]
Set up a new `users` prop in the `UserPanel` component.
```tsx
// app/components/user-panel.tsx
import { User } from '@prisma/client'
export function UserPanel({ users }: { users: User[] }) {
// ...
}
```
The `User` type used here was generated by Prisma and is available via Prisma Client. Remix works very nicely with Prisma because it is extremely easy to achieve end-to-end type safety in a fullstack framework.
> **Note**: End-to-end type safety occurs when the types across your entire stack are kept in sync as the shape of your data changes.
In `app/routes/home.tsx` you may now supply the users to the `UserPanel` component. Import the [`useLoaderData`](https://remix.run/docs/en/v1/api/remix#useloaderdata) hook provided by Remix which gives you access to any data returned from the `loader` function and use it to access the `users` data:
```tsx
// app/routes/home.tsx
import { useLoaderData } from '@remix-run/react'
// ...
export default function Home() {
const { users } = useLoaderData()
return (
)
}
// ...
```
The component will now have the `users` to work with. Now it needs to display them.
## Build the user display component [#build-the-user-display-component]
The list items will be displayed as a circle with the first letter of the user's first and last names for now.
Create a new file in `app/components` named `user-circle.tsx` and add the following component to it:
```tsx
// app/components/user-circle.tsx
import { Profile } from '@prisma/client'
interface props {
profile: Profile
className?: string
onClick?: (...args: any) => any
}
export function UserCircle({ profile, onClick, className }: props) {
return (
)
}
```
This component uses the `Profile` type generated by Prisma because you will be passing in only the `profile` data from the `user` documents.
It also has some configurable options that allow you to provide a click action and add additional classes to customize its style.
In `app/components/user-panel.tsx`, import this new component and render one for each user instead of rendering `
Users go here
`:
```tsx
diff // app/components/user-panel.tsx
import { User } from '@prisma/client'
+import { UserCircle } from '~/components/user-circle'
export function UserPanel({ users }: { users: User[] }) {
return (
{/* ... */}
-
Users go here
+ {users.map(user => (
+
+ ))}
{/* ... */}
)
}
```
Beautiful! Your users will now be rendered in a nice column on the left side of the home page. The only non-functional piece of the side panel at this point is the sign out button.

## Add the ability to log out [#add-the-ability-to-log-out]
Add another *resource route* in `app/routes` called `logout.ts` which will perform a logout action when invoked:
```ts
// app/routes/logout.ts
import type { ActionFunction, LoaderFunction } from "@remix-run/node";
import { redirect } from "@remix-run/node";
import { logout } from "~/utils/auth.server";
export const action: ActionFunction = async ({ request }) => logout(request);
export const loader: LoaderFunction = async () => redirect("/");
```
This route handles two possible actions: POST and GET
* `POST`: This will trigger the `logout` function written in the previous part of this series.
* `GET`: If a `GET` request is made, the user will be sent to the home page.
Add a `form` around your sign out button in `app/components/user-panel.ts` that will post to this route when submitted.
```ts
diff // app/components/user-panel.ts
// ...
export function UserPanel({ users }: props) {
return (
{/* ... */}
+
)
}
```
Your users can now sign out of the application! The user whose session is associated with the `POST` request will be signed out and their session destroyed.
## Add the ability to send kudos [#add-the-ability-to-send-kudos]
When a user in the user list is clicked a modal should pop up that provides a form. Submitting this form will save a kudo in the database.
This form will have the following features:
* A display of which user you are giving kudos to.
* A text area where you can fill out a message to the user.
* Styling options that allow you to pick the post's background color and text color.
* An emoji selector where you can add an emoji to the post.
* An accurate preview of what your post will look like.
### Update the Prisma schema [#update-the-prisma-schema]
There are a couple of data points you will be saving and displaying that are not yet defined in your schema. Here's a list of what needs to change:
1. Add a `Kudo` model with an embedded document to hold the style customizations
2. Add a 1:n relation in the `User` model that defines the kudos a user is the *author* of. Also add a similar relation that defines the kudos a user is a *recipient* of.
3. Add `enum`s for emojis, departments, and colors to define the available options.
```prisma
// prisma/schema.prisma
// ...
enum Emoji {
THUMBSUP
PARTY
HANDSUP
}
enum Department {
MARKETING
SALES
ENGINEERING
HR
}
enum Color {
RED
GREEN
YELLOW
BLUE
WHITE
}
type KudoStyle {
backgroundColor Color @default(YELLOW)
textColor Color @default(WHITE)
emoji Emoji @default(THUMBSUP)
}
model Kudo {
id String @id @default(auto()) @map("_id") @db.ObjectId
message String
createdAt DateTime @default(now())
style KudoStyle?
}
```
```prisma
diff // prisma/schema.prisma
model User {
// ...
+ authoredKudos Kudo[] @relation("AuthoredKudos")
+ kudos Kudo[] @relation("RecievedKudos")
}
model Kudo {
// ...
+ author User @relation(references: [id], fields: [authorId], "AuthoredKudos")
+ authorId String @db.ObjectId
+ recipient User @relation(references: [id], fields: [recipientId], "RecievedKudos")
+ recipientId String @db.ObjectId
}
```
> **Note:** After applying `@default` to a field, if a record in your collection does not have the new required field if will be updated to include that field with the default value the next time it is read.
That's all you'll need to update for now. Run `npx prisma db push`, which will automatically re-generate `PrismaClient`.
### Nested routes [#nested-routes]
You will use a [nested route](https://remix.run/docs/en/v1/guides/routing#what-is-nested-routing) to create the modal that will hold your form. This will allow you to set up a *sub-route* that will be rendered onto the parent route at an `Outlet` that you define.
When a user navigates to this nested route, a modal will be rendered onto the screen without having to re-render the entire page.
To create the nested route, first add a new folder in `app/routes` named `home`.
> **Note**: The naming of that folder is important. Because you have a `home.tsx` file, Remix will recognize any files in the new `home` folder as sub-routes of `/home`.
Within the new `app/routes/home` directory, create a new file named `kudo.$userId.tsx`. This will allow you to handle the modal component as if it were its own route.

The `$userId` portion of this file name is a [route param](https://remix.run/docs/en/v1/guides/data-loading#route-params), which acts as a dynamic value you can provide your application via the URL. Remix will then translate that file name to the route: `/home/kudos/$userId` where `$userId` can be any value.

In that new file export a `loader` function and a React component that renders some text to make sure the dynamic value is working:
```tsx
// app/routes/home/kudo.$userId.tsx
import { json, LoaderFunction } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
// 1
export const loader: LoaderFunction = async ({ request, params }) => {
// 2
const { userId } = params
return json({ userId })
}
export default function KudoModal() {
// 3
const data = useLoaderData()
return
User: {data.userId}
}
```
The code above does a few things:
1. It pulls the `params` field from the loader function.
2. It then grabs the `userId` value.
3. Finally, it retrieves the data from the `loader` function using Remix's `userLoaderData` hook and renders the `userId` onto the screen.
Because this is a nested route, in order to display it you will need to define where the route should be outputted in its parent.
Use Remix's `Outlet` component to specify you want the child route to be rendered as a direct child of the `Layout` component in `app/routes/home.tsx`:
```tsx
diff // app/routes/home.tsx
// ...
import {
useLoaderData,
+ Outlet
} from '@remix-run/react';
// ...
export default function Home() {
const { users } = useLoaderData()
return
+
{/* ... */}
}
```
If you head over to [http://localhost:3000/home/kudo/123](http://localhost:3000/home/kudo/123), you should now see the text "User: 123" displayed at the very top of the page. If you change the value in the URL to something other than `123` you should see that change reflected on the screen.

### Fetch a user by their id [#fetch-a-user-by-their-id]
Your nested route is working, but you still need to retrieve a user's data using the `userId`. Create a new function in `app/utils/user.server.ts` that returns a single user based on their `id`:
```ts
// app/utils/user.server.ts
// ...
export const getUserById = async (userId: string) => {
return await prisma.user.findUnique({
where: {
id: userId,
},
})
}
```
The query above finds the unique record in the database with the given `id`. The [`findUnique`](https://www.prisma.io/docs/orm/v7/reference/prisma-client-reference#findunique) function allows you to filter your query using *uniquely identifying* fields, or fields with values that *must* be unique to that record within your database.
Next:
1. Call that function in the loader exported by `app/routes/home/kudo.$userId.tsx`.
2. Return the results from that loader using the `json` function.
```tsx
// app/routes/home/kudo.$userId.tsx
import { json, LoaderFunction, redirect } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { getUserById } from '~/utils/user.server'
export const loader: LoaderFunction = async ({ request, params }) => {
const { userId } = params
if (typeof userId !== 'string') {
return redirect('/home')
}
const recipient = await getUserById(userId)
return json({ recipient })
}
// ...
```
Next, you need a way to navigate to a nested route with a valid `id`.
In `app/components/user-panel.tsx`, the file where you are rendering the user list, import the `useNavigation` hook Remix provides and use it to navigate to the nested route when a user is clicked.
```tsx
diff // app/components/user-panel.tsx
+import { useNavigate } from '@remix-run/react'
// ...
export function UserPanel({ users }: props) {
+ const navigate = useNavigate()
return (
{/*...*/}
navigate(`kudo/${user.id}`)}
/>
{/*...*/}
)
}
```
Now when your users click on another user in that panel, they will be navigated to a sub-route with that user's information.

If that all looks good, the next step is building the modal component that will display your form.
### Open a portal [#open-a-portal]
To build this modal you will first need to build a helper component that creates a [portal](https://reactjs.org/docs/portals.html), which allows you to render a child component somewhere outside of the parent's document object model (DOM) branch while still allowing the parent component to manage it as if it were a direct child.

> **Note**: This portal will be important because it will allow you to render the modal in a location that does not have any inherited styles or positioning from a parent that could affect the positioning of the modal.
In `app/components` create a new file named `portal.tsx` with the following contents:
```tsx
// app/components/portal.tsx
import { createPortal } from 'react-dom'
import { useState, useEffect } from 'react'
interface props {
children: React.ReactNode
wrapperId: string
}
// 1
const createWrapper = (wrapperId: string) => {
const wrapper = document.createElement('div')
wrapper.setAttribute('id', wrapperId)
document.body.appendChild(wrapper)
return wrapper
}
export const Portal: React.FC = ({ children, wrapperId }) => {
const [wrapper, setWrapper] = useState(null)
useEffect(() => {
// 2
let element = document.getElementById(wrapperId)
let created = false
if (!element) {
created = true
element = createWrapper(wrapperId)
}
setWrapper(element)
// 3
return () => {
if (created && element?.parentNode) {
element.parentNode.removeChild(element)
}
}
}, [wrapperId])
if (wrapper === null) return null
// 4
return createPortal(children, wrapper)
}
```
Here's an explanation of what is going on in this component:
1. A function is defined that generates a `div` with an `id`. That element is then attached to the document's `body`.
2. If an element with the provided `id` does not already exist, invoke the `createWrapper` function to create one.
3. When the `Portal` component is un-mounted, this will destroy the element.
4. Creates a portal to the newly generated `div`.
The result will be that any element or component wrapped in this `Portal` will be rendered as a direct child of the `body` tag, rather than in the current DOM branch as a child of its parent.
Give this a try to see it in action. In `app/routes/home/kudos.$userId.tsx`, import the new `Portal` component and wrap the the returned component with it:
```tsx
diff // app/routes/home/kudo.$userId.tsx
// ...
+import { Portal } from '~/components/portal'
// ... loader ...
export default function KudoModal() {
const { recipient } = useLoaderData()
- return ( /* ... */ )
+ return {/* ... */}
}
```
If you navigate to your nested route, you will see a `div` with an `id` of `"kudo-modal"` is now rendered as a direct child of the `body` rather than where the nested route is being rendered in the DOM tree.

### Build the modal component [#build-the-modal-component]
Now that you have a portal to a safe, begin building the modal component itself. There will be two modals in this application, so build the component in a way that is reusable.
Create a new file at `app/components/modal.tsx`. This file should export a component with the following `props`:
* `children`: The elements to render within the modal.
* `isOpen`: A flag that determines whether or not the modal is being displayed.
* `ariaLabel`: *(optional)* A string to be used as an aria label.
* `className`: *(optional)* A string that allows you to add additional classes to the modal's contents.
Add the following code to create the `Modal` component:
```tsx
// app/components/modal.tsx
import { Portal } from './portal'
import { useNavigate } from '@remix-run/react'
interface props {
children: React.ReactNode
isOpen: boolean
ariaLabel?: string
className?: string
}
export const Modal: React.FC = ({ children, isOpen, ariaLabel, className }) => {
const navigate = useNavigate()
if (!isOpen) return null
return (
navigate('/home')}
>
{/* This is where the modal content is rendered */}
{children}
)
}
```
The `Portal` component is imported and wraps the entirety of the modal to ensure it is rendered in a safe location.
The modal is then defined as a fixed element on the screen with an opaque backdrop using various TailwindCSS helpers.
When the backdrop *(anywhere off of the modal itself)* is clicked, the user will be navigated to the `/home` route causing the modal to close.
## Build the form [#build-the-form]
In `app/routes/home/kudo.$userId.tsx` import the new `Modal` component and render a `Modal` instead of the `Portal` currently being rendered:
```diff
// app/routes/home/kudo.$userId.tsx
- import { Portal } from '~/components/portal';
+ import { Modal } from '~/components/modal';
// ...
export default function KudoModal() {
// ...
return (
-
+
+
-
)
}
```
The modal should now open up when a user from the side panel is clicked.

Your form will need the logged in user's information when it displays a preview of the message, so before building the form add that data to the response from the `loader` function:
```tsx
diff // app/routes/home/kudo.$userId.tsx
+import { getUser } from '~/utils/auth.server'
// ...
export const loader: LoaderFunction = async ({ request, params }) => {
// ...
+ const user = await getUser(request)
- return json({ recipient })
+ return json({ recipient, user })
}
// ...
```
Then make the following changes to the `KudoModal` function in that file:
```tsx
// app/routes/home/kudo.$userId.tsx
// 1
import {
useLoaderData,
useActionData
} from '@remix-run/react'
import { UserCircle } from '~/components/user-circle'
import { useState } from 'react'
import { KudoStyle } from '@prisma/client'
// ...
export default function KudoModal() {
// 2
const actionData = useActionData()
const [formError] = useState(actionData?.error || '')
const [formData, setFormData] = useState({
message: '',
style: {
backgroundColor: 'RED',
textColor: 'WHITE',
emoji: 'THUMBSUP',
} as KudoStyle,
})
// 3
const handleChange = (e: React.ChangeEvent, field: string) => {
setFormData(data => ({ ...data, [field]: e.target.value }))
}
const {
recipient,
user
} = useLoaderData()
// 4
return (
{formError}
)
}
```
This was a big chunk of new code, so take a look at what changes were made:
1. Imports a few components and hooks you will need.
2. Sets up the various form variables you will need to handle the form data and errors.
3. Creates the function that will handle input changes.
4. Renders the basic layout of the form component in place of what was the `
` tag.
### Allow the user to customize their kudo [#allow-the-user-to-customize-their-kudo]
This form also needs to allow the user to select custom styles using select boxes.
Create a new file in `app/components` named `select-box.tsx` that exports a `SelectBox` component:
```tsx
// app/components/select-box.tsx
interface props {
options: {
name: string
value: any
}[]
className?: string
containerClassName?: string
id?: string
name?: string
label?: string
value?: any
onChange?: (...args: any) => any
}
export function SelectBox({
options = [],
onChange = () => {},
className = '',
containerClassName = '',
name,
id,
value,
label,
}: props) {
return (
)
}
```
This component is similar to the `FormField` component in that it is a *controlled component* that takes in some configuration and allows its state to be managed by its parent.
These select boxes will need to be populated with the color and emoji options. Create a helper file to hold the possible options at `app/utils/constants.ts`:
```ts
// app/utils/constants.ts
export const colorMap = {
RED: 'text-red-400',
GREEN: 'text-green-400',
BLUE: 'text-blue-400',
WHITE: 'text-white',
YELLOW: 'text-yellow-300',
}
export const backgroundColorMap = {
RED: 'bg-red-400',
GREEN: 'bg-green-400',
BLUE: 'bg-blue-400',
WHITE: 'bg-white',
YELLOW: 'bg-yellow-300',
}
export const emojiMap = {
THUMBSUP: '👍',
PARTY: '🎉',
HANDSUP: '🙌🏻',
}
```
Now in `app/routes/home/kudo.$userId.tsx`, import the `SelectBox` component and the constants. Also add the variables and functions requried to hook them up to the form's state and render the `SelectBox` components in place of the `{/* Select Boxes Go Here */}` comment:
```tsx
diff // app/routes/home/kudo.$userId.tsx
//...
+import { SelectBox } from '~/components/select-box'
+import { colorMap, emojiMap } from "~/utils/constants";
// ...
export default function KudoModal() {
// ...
+ const handleStyleChange = (e: React.ChangeEvent, field: string) => {
+ setFormData(data => ({
+ ...data, style: {
+ ...data.style,
+ [field]: e.target.value
+ }
+ }))
+ }
+ const getOptions = (data: any) => Object.keys(data).reduce((acc: any[], curr) => {
+ acc.push({
+ name: curr.charAt(0).toUpperCase() + curr.slice(1).toLowerCase(),
+ value: curr
+ })
+ return acc
+ }, [])
+ const colors = getOptions(colorMap)
+ const emojis = getOptions(emojiMap)
return (
{/* ... */}
- {/* Select Boxes Go Here */}
+ handleStyleChange(e, 'backgroundColor')}
+ label="Background Color"
+ containerClassName="w-36"
+ className="w-full rounded-xl px-3 py-2 text-gray-400"
+ />
+ handleStyleChange(e, 'textColor')}
+ label="Text Color"
+ containerClassName="w-36"
+ className="w-full rounded-xl px-3 py-2 text-gray-400"
+ />
+ handleStyleChange(e, 'emoji')}
+ containerClassName="w-36"
+ className="w-full rounded-xl px-3 py-2 text-gray-400"
+ />
{/* ... */}
)
}
```
The select boxes will now appear with all of the possible options.

## Add a kudo display component [#add-a-kudo-display-component]
This form will have a preview section where the user can see an actual rendering of the component the recipient will see.
Create a new file at `app/components` named `kudo.tsx`:
```tsx
// app/components/kudo.tsx
import { UserCircle } from '~/components/user-circle'
import { Profile, Kudo as IKudo } from '@prisma/client'
import { colorMap, backgroundColorMap, emojiMap } from '~/utils/constants'
export function Kudo({ profile, kudo }: { profile: Profile; kudo: Partial }) {
return (