FileUpload is designed for a two-step flow: the component first sends the selected file to a dedicated upload endpoint, then stores the returned file URL inside a hidden input so the final form can submit only the URL.tsx
import { FileUpload } from "@arkyn/components";
action you provide, and that endpoint is responsible for processing the file, saving it somewhere, and returning the final URL.FileUpload creates a FormData payload.fileName key.action URL using the configured HTTP method.FileUpload reads that URL from response[fileResponseName].name prop.FileUpload sends a multipart/form-data request to the route defined in action.file - the selected file, unless you change the field name with fileName.action - the endpoint that receives the upload request.method - the HTTP verb used for the request. Default is POST.fileName - the form-data key used for the file. Default is file.acceptFile - the file picker filter. This helps the user choose the right file type, but it is not a server-side validation rule.fileResponseName - the property name read from the JSON response. Default is url.error property, the component shows that message as the upload error.{ documentUrl: '...' }, you must set
fileResponseName="documentUrl".Ou arraste e solte o arquivo aqui
tsx
<FileUpload name="document" action="/api/file-upload" label="Document" />
tsx
import type { Route } from "+/api.fileUpload";import { type FileUpload, parseFormData } from "@mjackson/form-data-parser";import { FileAdapter } from "~/infra/adapters/fileAdapter";import { HttpAdapter } from "~/infra/adapters/httpAdapter";import { environmentVariables } from "../config/environmentVariables";export async function action({ request }: Route.ActionArgs) {const uploadHandler = async (fileUpload: FileUpload): Promise<string> => {if (fileUpload.fieldName !== "file") {throw HttpAdapter.badRequest("Invalid field name");}const fileAdapter = new FileAdapter({awsAccessKeyId: environmentVariables.AWS_ACCESS_KEY_ID,awsSecretAccessKey: environmentVariables.AWS_SECRET_ACCESS_KEY,awsRegion: environmentVariables.AWS_REGION,awsS3Bucket: environmentVariables.AWS_S3_BUCKET,awsDomain: environmentVariables.AWS_DOMAIN,});return await fileAdapter.uploadFile(fileUpload);};const formData = await parseFormData(request, uploadHandler);return { url: formData.get("file") };}
FileUpload.tsx
import { generateId } from "@arkyn/shared";import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";import type { FileUpload } from "@mjackson/form-data-parser";import { Readable } from "stream";type ConstructorProps = {awsRegion: string;awsAccessKeyId: string;awsSecretAccessKey: string;awsS3Bucket: string;awsDomain: string;};class FileAdapter {readonly awsRegion: string;readonly awsAccessKeyId: string;readonly awsSecretAccessKey: string;readonly awsS3Bucket: string;readonly awsDomain: string;constructor(props: ConstructorProps) {this.awsRegion = props.awsRegion;this.awsAccessKeyId = props.awsAccessKeyId;this.awsSecretAccessKey = props.awsSecretAccessKey;this.awsS3Bucket = props.awsS3Bucket;this.awsDomain = props.awsDomain;}async uploadFile(file: FileUpload): Promise<string> {const fileSize = file.size;const contentType = file.type;const webStream = file.stream();const fileStream = Readable.fromWeb(webStream as any);const uploadParams = {Bucket: this.awsS3Bucket,Key: `uploads/${generateId("text", "v4")}`,Body: fileStream,ContentType: contentType,ContentLength: fileSize,};const s3Client = new S3Client({region: this.awsRegion,requestStreamBufferSize: 65_536,credentials: {accessKeyId: this.awsAccessKeyId,secretAccessKey: this.awsSecretAccessKey,},});const command = new PutObjectCommand(uploadParams);await s3Client.send(command);return `${this.awsDomain}/${uploadParams.Key}`;}}export { FileAdapter };
FileUpload behaves like a normal field. The difference is that the hidden field receives the uploaded file URL after the upload finishes.tsx
<FileUploadname="avatarUrl"action="/api/file-upload"label="Profile picture"selectFileButtonText="Choose image"changeFileButtonText="Replace image"dropFileText="Drop your image here"acceptFile="image/*"onChange={(url) => {if (url) {console.log("Uploaded file URL:", url);}}}/>
avatarUrl receives the returned URL value.acceptFile only helps the browser file picker filter options. It does not replace server-side validation.onChange receives the uploaded URL after a successful response.disabled prevents both file selection and re-upload actions.method exists for backend compatibility, but most upload endpoints use POST.