Multi-part form data

πŸ§β€β™‚οΈ I've made some adjustments you may want to know about. We now have a new resource route that's responsible for serving images: .
So now any image can be displayed by its ID via: /resources/images/:imageId. So I updated to use this new route to display the images for notes. So that's all ready for us to start storing images for notes.
Another thing I did to help prepare for your work is I put together a fancy ImageChooser component you can use for the user to have a nicer UX for selecting an image than the default file input. It's a bit more involved than you have time for, but feel free to explore how that's implemented if you've got time. You will be making some changes to it during the workshop.
You'll find it at the bottom of . I also updated the loader to load the note's images if it already has images:
import { StatusButton } from '#app/components/ui/status-button.tsx'
import { Textarea } from '#app/components/ui/textarea.tsx'
import { db, updateNote } from '#app/utils/db.server.ts'
import { cn, invariantResponse, useIsSubmitting } from '#app/utils/misc.tsx'

export async function loader({ params }: LoaderFunctionArgs) {
	const note = db.note.findFirst({
		where: {
			id: {
				equals: params.noteId,
			},
		},
	})
	if (!note) {
		throw new Response('Note not found', { status: 404 })
	}
	return json({
		note: {
			title: note.title,
So you can use that to preload the images for the note if it already has images when the user goes to edit the note.
As a reminder, you can check the Diff tab and select the previous solution step vs this problem step to see all the changes I made.
Alright, with that background, I think you're ready to make your adjustments! Good luck!
πŸ‘¨β€πŸ’Ό Thanks Kellie! Alright, we need you to make adjustments so we can start uploading images to the notes page. We'll be making the minimal changes for this and we'll progressively improve it in the next steps. That means we'll be ignoring some of the TypeScript stuff (sorry Lily! 🦺😒).
At a high-level, here's what you'll be adjusting:
  • Update the encType of the form so we can accept file uploads
  • Update the type on our file upload input so it's a file input
  • Properly parse the request in our action so it can handle the file upload using Remix's memory upload handler
  • Render a hidden input for the existing image ID if it does exist so it's preserved if the user's just wanting to update the alt text.
Here's that example again of how to process the file in your action:
import {
	unstable_createMemoryUploadHandler as createMemoryUploadHandler,
	unstable_parseMultipartFormData as parseMultipartFormData,
} from '@remix-run/node'
export const action = async ({ request }: ActionArgs) => {
	const uploadHandler = createMemoryUploadHandler({
		maxPartSize: 1024 * 1024 * 5, // 5 MB
	})
	const formData = await parseMultipartFormData(request, uploadHandler)

	const file = formData.get('avatar')

	// file is a "File" (https://mdn.io/File) polyfilled for node
	// ... etc
}
The emoji team (πŸ¨πŸ’°πŸ¦ΊπŸ’£) will be in there to help guide you through this one. Enjoy!