/plushcap/analysis/strapi/epic-next-js-14-tutorial-part-5-file-upload-using-server-actions

Epic Next JS 14 Tutorial Part 5: File upload using server actions

What's this blog post about?

In this tutorial, we will learn how to create a Dashboard layout with an Account section where the user can update their first name, last name, bio, and image using Next.js Server Actions. We will also cover file uploads in Next.js. First, let's create our profile-actions.ts file inside the actions folder under data/actions directory. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 "use server"; import { z } from "zod"; import qs from "qs"; import { getUserMeLoader } from "@/data/services/get-user-me-loader"; import { mutateData } from "@/data/services/mutate-data"; import { flattenAttributes } from "@/lib/utils"; export async function updateProfileAction( userId: string, prevState: any, formData: FormData ) { const rawFormData = Object.fromEntries(formData); const query = qs.stringify({ populate: "*", }); const payload = { firstName: rawFormData.firstName, lastName: rawFormData.lastName, bio: rawFormData.bio, }; const responseData = await mutateData( "PUT", `/api/users/${userId}?${query}`, payload ); if (!responseData) { return { ...prevState, strapiErrors: null, message: "Ops! Something went wrong. Please try again.", }; } if (responseData.error) { return { ...prevState, strapiErrors: responseData.error, message: "Failed to Register.", }; } const flattenedData = flattenAttributes(responseData); return { ...prevState, message: "Profile Updated", data: flattenedData, strapiErrors: null, }; } In this file, we are importing the necessary dependencies and creating a server action called updateProfileAction. This action takes three parameters: userId, prevState, and formData. The first parameter is the user's ID, which we will use to identify the user in our Strapi API. The second parameter is the previous state of the form, which we will use to update the form data after a successful update. The third parameter is the form data that the user submitted. Next, let's create our profile-form.tsx file inside the forms folder under components/forms directory. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 "use client"; import React from "react"; import { useFormState } from "react-dom"; import { cn } from "@/lib/utils"; import { uploadProfileImageAction } from "@/data/actions/profile-actions"; import { SubmitButton } from "@/components/custom/SubmitButton"; import ImagePicker from "@/components/custom/ImagePicker"; import { ZodErrors } from "@/components/custom/ZodErrors"; import { StrapiErrors } from "@/components/custom/StrapiErrors"; interface ProfileImageFormProps { id: string; url: string; alternativeText: string; } const initialState = { message: null, data: null, strapiErrors: null, zodErrors: null, }; export function ProfileImageForm({ data, className, }: { data: Readonly<ProfileImageFormProps>, className?: string, }) { const uploadProfileImageWithIdAction = uploadProfileImageAction.bind( null, data?.id ); const [formState, formAction] = useFormState( uploadProfileImageWithIdAction, initialState ); return ( <form className={cn("space-y-4", className)} action={formAction}> <div className=""> <ImagePicker id="image" name="image" label="Profile Image" defaultValue={data?.url || ""} /> <ZodErrors error={formState.zodErrors?.image} /> <StrapiErrors error={formState.strapiErrors} /> </div> <div className="flex justify-end"> <SubmitButton text="Update Image" loadingText="Saving Image" /> </div> </form> ); } In this file, we are importing the necessary dependencies and creating a client component called ProfileImageForm. This component takes two parameters: data and className. The first parameter is an object containing the user's profile image information, which includes the ID, URL, and alternative text of the image. The second parameter is an optional string that specifies additional CSS classes to apply to the form. Next, let's create our get-user-me-loader.ts file inside the services folder under data/services directory. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 import { getAuthToken } from "./get-token"; import { mutateData } from "./mutate-data"; import { flattenAttributes } from "@/lib/utils"; import { getStrapiURL } from "@/lib/utils"; export async function getUserMeLoader() { const authToken = await getAuthToken(); if (!authToken) throw new Error("No auth token found"); const baseUrl = getStrapiURL(); const url = new URL("/api/users/me", baseUrl); try { const response = await fetch(url, { headers: { Authorization: `Bearer ${authToken}` }, }); const dataResponse = await response.json(); return flattenAttributes(dataResponse); } catch (error) { console.error("Error getting user me:", error); throw error; } } In this file, we are importing the necessary dependencies and creating a server action called getUserMeLoader. This action takes no parameters and returns an object containing the user's profile information. Next, let's create our mutate-data.ts file inside the services folder under data/services directory. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import { getAuthToken } from "./get-token"; import { getStrapiURL } from "@/lib/utils"; export async function mutateData(method: string, path: string, payload?: any) { const baseUrl = getStrapiURL(); const authToken = await getAuthToken(); ififififififififififififififififififififififififififififififif

Company
Strapi

Date published
April 3, 2024

Author(s)
-

Word count
5420

Hacker News points
None found.

Language
English


By Matt Makai. 2021-2024.