From df2359ce38be7e961412259726d41b4361c72f16 Mon Sep 17 00:00:00 2001
From: GSt4r <11350542+GSt4r@users.noreply.github.com>
Date: Thu, 9 Jun 2022 23:10:28 +0200
Subject: [PATCH 1/5] S3 file upload (#1)
* Implemented S3 upload
* Updated README
* Cleaned up dependencies
---
.../file-and-cloudinary-upload/package.json | 1 -
examples/file-and-s3-upload/.env.sample | 4 ++
examples/file-and-s3-upload/.eslintrc.js | 3 +
examples/file-and-s3-upload/.gitignore | 6 ++
examples/file-and-s3-upload/README.md | 35 +++++++++++
.../file-and-s3-upload/app/entry.client.tsx | 4 ++
.../file-and-s3-upload/app/entry.server.tsx | 21 +++++++
examples/file-and-s3-upload/app/root.tsx | 32 ++++++++++
.../app/routes/s3-upload.tsx | 59 ++++++++++++++++++
.../file-and-s3-upload/app/utils/s3.server.ts | 46 ++++++++++++++
examples/file-and-s3-upload/package.json | 29 +++++++++
.../file-and-s3-upload/public/favicon.ico | Bin 0 -> 16958 bytes
examples/file-and-s3-upload/remix.config.js | 10 +++
examples/file-and-s3-upload/remix.env.d.ts | 2 +
.../file-and-s3-upload/sandbox.config.json | 6 ++
examples/file-and-s3-upload/tsconfig.json | 22 +++++++
16 files changed, 279 insertions(+), 1 deletion(-)
create mode 100644 examples/file-and-s3-upload/.env.sample
create mode 100644 examples/file-and-s3-upload/.eslintrc.js
create mode 100644 examples/file-and-s3-upload/.gitignore
create mode 100644 examples/file-and-s3-upload/README.md
create mode 100644 examples/file-and-s3-upload/app/entry.client.tsx
create mode 100644 examples/file-and-s3-upload/app/entry.server.tsx
create mode 100644 examples/file-and-s3-upload/app/root.tsx
create mode 100644 examples/file-and-s3-upload/app/routes/s3-upload.tsx
create mode 100644 examples/file-and-s3-upload/app/utils/s3.server.ts
create mode 100644 examples/file-and-s3-upload/package.json
create mode 100644 examples/file-and-s3-upload/public/favicon.ico
create mode 100644 examples/file-and-s3-upload/remix.config.js
create mode 100644 examples/file-and-s3-upload/remix.env.d.ts
create mode 100644 examples/file-and-s3-upload/sandbox.config.json
create mode 100644 examples/file-and-s3-upload/tsconfig.json
diff --git a/examples/file-and-cloudinary-upload/package.json b/examples/file-and-cloudinary-upload/package.json
index 2420b7c20c6..2a1c19fcdfc 100644
--- a/examples/file-and-cloudinary-upload/package.json
+++ b/examples/file-and-cloudinary-upload/package.json
@@ -10,7 +10,6 @@
"@remix-run/node": "1.5.1",
"@remix-run/react": "1.5.1",
"@remix-run/serve": "1.5.1",
- "cloudinary": "^1.28.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
diff --git a/examples/file-and-s3-upload/.env.sample b/examples/file-and-s3-upload/.env.sample
new file mode 100644
index 00000000000..17b368d399e
--- /dev/null
+++ b/examples/file-and-s3-upload/.env.sample
@@ -0,0 +1,4 @@
+STORAGE_ACCESS_KEY=
+STORAGE_SECRET=
+STORAGE_REGION=
+STORAGE_BUCKET=
\ No newline at end of file
diff --git a/examples/file-and-s3-upload/.eslintrc.js b/examples/file-and-s3-upload/.eslintrc.js
new file mode 100644
index 00000000000..ced78085f86
--- /dev/null
+++ b/examples/file-and-s3-upload/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
+};
diff --git a/examples/file-and-s3-upload/.gitignore b/examples/file-and-s3-upload/.gitignore
new file mode 100644
index 00000000000..3f7bf98da3e
--- /dev/null
+++ b/examples/file-and-s3-upload/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+
+/.cache
+/build
+/public/build
+.env
diff --git a/examples/file-and-s3-upload/README.md b/examples/file-and-s3-upload/README.md
new file mode 100644
index 00000000000..36249dcd670
--- /dev/null
+++ b/examples/file-and-s3-upload/README.md
@@ -0,0 +1,35 @@
+# Upload images to S3
+
+This is a simple example of using the remix built-in [uploadHandler](https://remix.run/docs/en/v1/api/remix#uploadhandler) and Form with multipart data to upload a file with the built-in local uploader and upload an image file to S3 with a custom uploader and display it. You can test it locally by running the dev server and opening the path `/s3-upload` in your browser.
+
+The relevent files are:
+
+```
+├── app
+│ ├── routes
+│ │ ├── s3-upload.tsx // upload to S3
+│ └── utils
+│ └── s3.server.ts // init S3 client on server side
+|── .env // holds AWS S3 credentails
+```
+
+## Steps to set up an S3 bucket
+
+- Sign up for an [AWS account](https://portal.aws.amazon.com/billing/signup) - this will require a credit card
+- Create an S3 bucket in your desired region
+- Create an access key pair for an IAM user that has access to the bucket
+- Copy the .env.sample to .env and fill in the S3 bucket, the region as well as the access key and secret key from the IAM user
+
+Note: in order for the image to be displayed after being uploaded to your S3 bucket in this example, the bucket needs to have public access enabled, which is potentially dangerous.
+
+> :warning: Lambda imposes a [limit of 6MB](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html) on the invocation payload size. If you use this example with Remix running on Lambda, you can only update files with a size smaller than 6MB.
+
+Open this example on [CodeSandbox](https://codesandbox.com):
+
+[![Open in CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/remix-run/remix/tree/main/examples/file-and-s3-upload)
+
+## Related Links
+
+- [Handle Multiple Part Forms (File Uploads)](https://remix.run/docs/en/v1/api/remix#unstable_parsemultipartformdata-node)
+- [Upload Handler](https://remix.run/docs/en/v1/api/remix#uploadhandler)
+- [Custom Uploader](https://remix.run/docs/en/v1/api/remix#custom-uploadhandler)
diff --git a/examples/file-and-s3-upload/app/entry.client.tsx b/examples/file-and-s3-upload/app/entry.client.tsx
new file mode 100644
index 00000000000..3eec1fd0a02
--- /dev/null
+++ b/examples/file-and-s3-upload/app/entry.client.tsx
@@ -0,0 +1,4 @@
+import { RemixBrowser } from "@remix-run/react";
+import { hydrate } from "react-dom";
+
+hydrate(, document);
diff --git a/examples/file-and-s3-upload/app/entry.server.tsx b/examples/file-and-s3-upload/app/entry.server.tsx
new file mode 100644
index 00000000000..5afa18235cc
--- /dev/null
+++ b/examples/file-and-s3-upload/app/entry.server.tsx
@@ -0,0 +1,21 @@
+import type { EntryContext } from "@remix-run/node";
+import { RemixServer } from "@remix-run/react";
+import { renderToString } from "react-dom/server";
+
+export default function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext
+) {
+ const markup = renderToString(
+
+ );
+
+ responseHeaders.set("Content-Type", "text/html");
+
+ return new Response("" + markup, {
+ status: responseStatusCode,
+ headers: responseHeaders,
+ });
+}
diff --git a/examples/file-and-s3-upload/app/root.tsx b/examples/file-and-s3-upload/app/root.tsx
new file mode 100644
index 00000000000..927a0f745df
--- /dev/null
+++ b/examples/file-and-s3-upload/app/root.tsx
@@ -0,0 +1,32 @@
+import type { MetaFunction } from "@remix-run/node";
+import {
+ Links,
+ LiveReload,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from "@remix-run/react";
+
+export const meta: MetaFunction = () => ({
+ charset: "utf-8",
+ title: "New Remix App",
+ viewport: "width=device-width,initial-scale=1",
+});
+
+export default function App() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/file-and-s3-upload/app/routes/s3-upload.tsx b/examples/file-and-s3-upload/app/routes/s3-upload.tsx
new file mode 100644
index 00000000000..c9bedadfc3f
--- /dev/null
+++ b/examples/file-and-s3-upload/app/routes/s3-upload.tsx
@@ -0,0 +1,59 @@
+import type { ActionFunction, UploadHandler } from "@remix-run/node";
+import {
+ json,
+ unstable_composeUploadHandlers as composeUploadHandlers,
+ unstable_createMemoryUploadHandler as createMemoryUploadHandler,
+ unstable_parseMultipartFormData as parseMultipartFormData,
+} from "@remix-run/node";
+import { Form, useActionData } from "@remix-run/react";
+import { s3UploadHandler } from "~/utils/s3.server";
+
+type ActionData = {
+ errorMsg?: string;
+ imgSrc?: string;
+ imgDesc?: string;
+};
+
+export const action: ActionFunction = async ({ request }) => {
+ const uploadHandler: UploadHandler = composeUploadHandlers(
+ s3UploadHandler,
+ createMemoryUploadHandler()
+ );
+ const formData = await parseMultipartFormData(request, uploadHandler);
+ const imgSrc = formData.get("img");
+ const imgDesc = formData.get("desc");
+ console.log(imgDesc)
+ if (!imgSrc) {
+ return json({
+ error: "Something went wrong while uploading",
+ });
+ }
+ return json({
+ imgSrc,
+ imgDesc,
+ });
+};
+
+export default function Index() {
+ const data = useActionData();
+ return (
+ <>
+
+ {data?.errorMsg && {data.errorMsg}
}
+ {data?.imgSrc && (
+ <>
+ File has been uploaded to S3 and is available under the following URL (if the bucket has public access enabled):
+ {data.imgSrc}
+
+
+ >
+ )}
+ >
+ );
+}
diff --git a/examples/file-and-s3-upload/app/utils/s3.server.ts b/examples/file-and-s3-upload/app/utils/s3.server.ts
new file mode 100644
index 00000000000..5f755c83fff
--- /dev/null
+++ b/examples/file-and-s3-upload/app/utils/s3.server.ts
@@ -0,0 +1,46 @@
+import AWS from "aws-sdk"
+import type { UploadHandler } from "@remix-run/node"
+import { writeAsyncIterableToWritable } from "@remix-run/node"
+import { PassThrough } from "stream"
+
+const { STORAGE_ACCESS_KEY, STORAGE_SECRET, STORAGE_REGION, STORAGE_BUCKET } = process.env
+
+if (!(STORAGE_ACCESS_KEY && STORAGE_SECRET && STORAGE_REGION && STORAGE_BUCKET)) {
+ throw new Error(`Storage is missing required configuration.`)
+}
+
+const uploadStream = ({ Key }: Pick) => {
+ const s3 = new AWS.S3({
+ credentials: {
+ accessKeyId: STORAGE_ACCESS_KEY,
+ secretAccessKey: STORAGE_SECRET,
+ },
+ region: STORAGE_REGION,
+ })
+ const pass = new PassThrough()
+ return {
+ writeStream: pass,
+ promise: s3.upload({ Bucket: STORAGE_BUCKET, Key, Body: pass }).promise(),
+ }
+}
+
+export async function uploadStreamToS3(data: any, filename: string) {
+ const stream = uploadStream({
+ Key: filename,
+ })
+ await writeAsyncIterableToWritable(data, stream.writeStream)
+ const file = await stream.promise
+ return file.Location
+}
+
+export const s3UploadHandler: UploadHandler = async ({
+ name,
+ filename,
+ data,
+}) => {
+ if (name !== "img") {
+ return undefined;
+ }
+ const uploadedFileLocation = await uploadStreamToS3(data, filename!)
+ return uploadedFileLocation
+}
diff --git a/examples/file-and-s3-upload/package.json b/examples/file-and-s3-upload/package.json
new file mode 100644
index 00000000000..e54e9a1b766
--- /dev/null
+++ b/examples/file-and-s3-upload/package.json
@@ -0,0 +1,29 @@
+{
+ "private": true,
+ "sideEffects": false,
+ "scripts": {
+ "build": "remix build",
+ "dev": "remix dev",
+ "start": "remix-serve build"
+ },
+ "dependencies": {
+ "@remix-run/node": "1.5.1",
+ "@remix-run/react": "1.5.1",
+ "@remix-run/serve": "1.5.1",
+ "aws-sdk": "^2.1152.0",
+ "cloudinary": "^1.28.1",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ },
+ "devDependencies": {
+ "@remix-run/dev": "1.5.1",
+ "@remix-run/eslint-config": "1.5.1",
+ "@types/react": "^17.0.39",
+ "@types/react-dom": "^17.0.13",
+ "eslint": "^8.10.0",
+ "typescript": "^4.6.2"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+}
diff --git a/examples/file-and-s3-upload/public/favicon.ico b/examples/file-and-s3-upload/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..8830cf6821b354114848e6354889b8ecf6d2bc61
GIT binary patch
literal 16958
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
EKbET!0{{R3
literal 0
HcmV?d00001
diff --git a/examples/file-and-s3-upload/remix.config.js b/examples/file-and-s3-upload/remix.config.js
new file mode 100644
index 00000000000..260b82c7cb1
--- /dev/null
+++ b/examples/file-and-s3-upload/remix.config.js
@@ -0,0 +1,10 @@
+/**
+ * @type {import('@remix-run/dev').AppConfig}
+ */
+module.exports = {
+ ignoredRouteFiles: ["**/.*"],
+ // appDirectory: "app",
+ // assetsBuildDirectory: "public/build",
+ // serverBuildPath: "build/index.js",
+ // publicPath: "/build/",
+};
diff --git a/examples/file-and-s3-upload/remix.env.d.ts b/examples/file-and-s3-upload/remix.env.d.ts
new file mode 100644
index 00000000000..72e2affe311
--- /dev/null
+++ b/examples/file-and-s3-upload/remix.env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/examples/file-and-s3-upload/sandbox.config.json b/examples/file-and-s3-upload/sandbox.config.json
new file mode 100644
index 00000000000..4363d87a30d
--- /dev/null
+++ b/examples/file-and-s3-upload/sandbox.config.json
@@ -0,0 +1,6 @@
+{
+ "hardReloadOnChange": true,
+ "container": {
+ "port": 3000
+ }
+}
diff --git a/examples/file-and-s3-upload/tsconfig.json b/examples/file-and-s3-upload/tsconfig.json
new file mode 100644
index 00000000000..20f8a386a6c
--- /dev/null
+++ b/examples/file-and-s3-upload/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "target": "ES2019",
+ "strict": true,
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+
+ // Remix takes care of building everything in `remix build`.
+ "noEmit": true
+ }
+}
From 37b99767e8416b8f666e76d216cdc517191ae472 Mon Sep 17 00:00:00 2001
From: GSt4r <11350542+GSt4r@users.noreply.github.com>
Date: Thu, 9 Jun 2022 23:11:37 +0200
Subject: [PATCH 2/5] Revert "S3 file upload (#1)" (#2)
This reverts commit df2359ce38be7e961412259726d41b4361c72f16.
---
.../file-and-cloudinary-upload/package.json | 1 +
examples/file-and-s3-upload/.env.sample | 4 --
examples/file-and-s3-upload/.eslintrc.js | 3 -
examples/file-and-s3-upload/.gitignore | 6 --
examples/file-and-s3-upload/README.md | 35 -----------
.../file-and-s3-upload/app/entry.client.tsx | 4 --
.../file-and-s3-upload/app/entry.server.tsx | 21 -------
examples/file-and-s3-upload/app/root.tsx | 32 ----------
.../app/routes/s3-upload.tsx | 59 ------------------
.../file-and-s3-upload/app/utils/s3.server.ts | 46 --------------
examples/file-and-s3-upload/package.json | 29 ---------
.../file-and-s3-upload/public/favicon.ico | Bin 16958 -> 0 bytes
examples/file-and-s3-upload/remix.config.js | 10 ---
examples/file-and-s3-upload/remix.env.d.ts | 2 -
.../file-and-s3-upload/sandbox.config.json | 6 --
examples/file-and-s3-upload/tsconfig.json | 22 -------
16 files changed, 1 insertion(+), 279 deletions(-)
delete mode 100644 examples/file-and-s3-upload/.env.sample
delete mode 100644 examples/file-and-s3-upload/.eslintrc.js
delete mode 100644 examples/file-and-s3-upload/.gitignore
delete mode 100644 examples/file-and-s3-upload/README.md
delete mode 100644 examples/file-and-s3-upload/app/entry.client.tsx
delete mode 100644 examples/file-and-s3-upload/app/entry.server.tsx
delete mode 100644 examples/file-and-s3-upload/app/root.tsx
delete mode 100644 examples/file-and-s3-upload/app/routes/s3-upload.tsx
delete mode 100644 examples/file-and-s3-upload/app/utils/s3.server.ts
delete mode 100644 examples/file-and-s3-upload/package.json
delete mode 100644 examples/file-and-s3-upload/public/favicon.ico
delete mode 100644 examples/file-and-s3-upload/remix.config.js
delete mode 100644 examples/file-and-s3-upload/remix.env.d.ts
delete mode 100644 examples/file-and-s3-upload/sandbox.config.json
delete mode 100644 examples/file-and-s3-upload/tsconfig.json
diff --git a/examples/file-and-cloudinary-upload/package.json b/examples/file-and-cloudinary-upload/package.json
index 2a1c19fcdfc..2420b7c20c6 100644
--- a/examples/file-and-cloudinary-upload/package.json
+++ b/examples/file-and-cloudinary-upload/package.json
@@ -10,6 +10,7 @@
"@remix-run/node": "1.5.1",
"@remix-run/react": "1.5.1",
"@remix-run/serve": "1.5.1",
+ "cloudinary": "^1.28.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
diff --git a/examples/file-and-s3-upload/.env.sample b/examples/file-and-s3-upload/.env.sample
deleted file mode 100644
index 17b368d399e..00000000000
--- a/examples/file-and-s3-upload/.env.sample
+++ /dev/null
@@ -1,4 +0,0 @@
-STORAGE_ACCESS_KEY=
-STORAGE_SECRET=
-STORAGE_REGION=
-STORAGE_BUCKET=
\ No newline at end of file
diff --git a/examples/file-and-s3-upload/.eslintrc.js b/examples/file-and-s3-upload/.eslintrc.js
deleted file mode 100644
index ced78085f86..00000000000
--- a/examples/file-and-s3-upload/.eslintrc.js
+++ /dev/null
@@ -1,3 +0,0 @@
-module.exports = {
- extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
-};
diff --git a/examples/file-and-s3-upload/.gitignore b/examples/file-and-s3-upload/.gitignore
deleted file mode 100644
index 3f7bf98da3e..00000000000
--- a/examples/file-and-s3-upload/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-node_modules
-
-/.cache
-/build
-/public/build
-.env
diff --git a/examples/file-and-s3-upload/README.md b/examples/file-and-s3-upload/README.md
deleted file mode 100644
index 36249dcd670..00000000000
--- a/examples/file-and-s3-upload/README.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Upload images to S3
-
-This is a simple example of using the remix built-in [uploadHandler](https://remix.run/docs/en/v1/api/remix#uploadhandler) and Form with multipart data to upload a file with the built-in local uploader and upload an image file to S3 with a custom uploader and display it. You can test it locally by running the dev server and opening the path `/s3-upload` in your browser.
-
-The relevent files are:
-
-```
-├── app
-│ ├── routes
-│ │ ├── s3-upload.tsx // upload to S3
-│ └── utils
-│ └── s3.server.ts // init S3 client on server side
-|── .env // holds AWS S3 credentails
-```
-
-## Steps to set up an S3 bucket
-
-- Sign up for an [AWS account](https://portal.aws.amazon.com/billing/signup) - this will require a credit card
-- Create an S3 bucket in your desired region
-- Create an access key pair for an IAM user that has access to the bucket
-- Copy the .env.sample to .env and fill in the S3 bucket, the region as well as the access key and secret key from the IAM user
-
-Note: in order for the image to be displayed after being uploaded to your S3 bucket in this example, the bucket needs to have public access enabled, which is potentially dangerous.
-
-> :warning: Lambda imposes a [limit of 6MB](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html) on the invocation payload size. If you use this example with Remix running on Lambda, you can only update files with a size smaller than 6MB.
-
-Open this example on [CodeSandbox](https://codesandbox.com):
-
-[![Open in CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/remix-run/remix/tree/main/examples/file-and-s3-upload)
-
-## Related Links
-
-- [Handle Multiple Part Forms (File Uploads)](https://remix.run/docs/en/v1/api/remix#unstable_parsemultipartformdata-node)
-- [Upload Handler](https://remix.run/docs/en/v1/api/remix#uploadhandler)
-- [Custom Uploader](https://remix.run/docs/en/v1/api/remix#custom-uploadhandler)
diff --git a/examples/file-and-s3-upload/app/entry.client.tsx b/examples/file-and-s3-upload/app/entry.client.tsx
deleted file mode 100644
index 3eec1fd0a02..00000000000
--- a/examples/file-and-s3-upload/app/entry.client.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-import { RemixBrowser } from "@remix-run/react";
-import { hydrate } from "react-dom";
-
-hydrate(, document);
diff --git a/examples/file-and-s3-upload/app/entry.server.tsx b/examples/file-and-s3-upload/app/entry.server.tsx
deleted file mode 100644
index 5afa18235cc..00000000000
--- a/examples/file-and-s3-upload/app/entry.server.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { EntryContext } from "@remix-run/node";
-import { RemixServer } from "@remix-run/react";
-import { renderToString } from "react-dom/server";
-
-export default function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext
-) {
- const markup = renderToString(
-
- );
-
- responseHeaders.set("Content-Type", "text/html");
-
- return new Response("" + markup, {
- status: responseStatusCode,
- headers: responseHeaders,
- });
-}
diff --git a/examples/file-and-s3-upload/app/root.tsx b/examples/file-and-s3-upload/app/root.tsx
deleted file mode 100644
index 927a0f745df..00000000000
--- a/examples/file-and-s3-upload/app/root.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { MetaFunction } from "@remix-run/node";
-import {
- Links,
- LiveReload,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
-} from "@remix-run/react";
-
-export const meta: MetaFunction = () => ({
- charset: "utf-8",
- title: "New Remix App",
- viewport: "width=device-width,initial-scale=1",
-});
-
-export default function App() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/examples/file-and-s3-upload/app/routes/s3-upload.tsx b/examples/file-and-s3-upload/app/routes/s3-upload.tsx
deleted file mode 100644
index c9bedadfc3f..00000000000
--- a/examples/file-and-s3-upload/app/routes/s3-upload.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { ActionFunction, UploadHandler } from "@remix-run/node";
-import {
- json,
- unstable_composeUploadHandlers as composeUploadHandlers,
- unstable_createMemoryUploadHandler as createMemoryUploadHandler,
- unstable_parseMultipartFormData as parseMultipartFormData,
-} from "@remix-run/node";
-import { Form, useActionData } from "@remix-run/react";
-import { s3UploadHandler } from "~/utils/s3.server";
-
-type ActionData = {
- errorMsg?: string;
- imgSrc?: string;
- imgDesc?: string;
-};
-
-export const action: ActionFunction = async ({ request }) => {
- const uploadHandler: UploadHandler = composeUploadHandlers(
- s3UploadHandler,
- createMemoryUploadHandler()
- );
- const formData = await parseMultipartFormData(request, uploadHandler);
- const imgSrc = formData.get("img");
- const imgDesc = formData.get("desc");
- console.log(imgDesc)
- if (!imgSrc) {
- return json({
- error: "Something went wrong while uploading",
- });
- }
- return json({
- imgSrc,
- imgDesc,
- });
-};
-
-export default function Index() {
- const data = useActionData();
- return (
- <>
-
- {data?.errorMsg && {data.errorMsg}
}
- {data?.imgSrc && (
- <>
- File has been uploaded to S3 and is available under the following URL (if the bucket has public access enabled):
- {data.imgSrc}
-
-
- >
- )}
- >
- );
-}
diff --git a/examples/file-and-s3-upload/app/utils/s3.server.ts b/examples/file-and-s3-upload/app/utils/s3.server.ts
deleted file mode 100644
index 5f755c83fff..00000000000
--- a/examples/file-and-s3-upload/app/utils/s3.server.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import AWS from "aws-sdk"
-import type { UploadHandler } from "@remix-run/node"
-import { writeAsyncIterableToWritable } from "@remix-run/node"
-import { PassThrough } from "stream"
-
-const { STORAGE_ACCESS_KEY, STORAGE_SECRET, STORAGE_REGION, STORAGE_BUCKET } = process.env
-
-if (!(STORAGE_ACCESS_KEY && STORAGE_SECRET && STORAGE_REGION && STORAGE_BUCKET)) {
- throw new Error(`Storage is missing required configuration.`)
-}
-
-const uploadStream = ({ Key }: Pick) => {
- const s3 = new AWS.S3({
- credentials: {
- accessKeyId: STORAGE_ACCESS_KEY,
- secretAccessKey: STORAGE_SECRET,
- },
- region: STORAGE_REGION,
- })
- const pass = new PassThrough()
- return {
- writeStream: pass,
- promise: s3.upload({ Bucket: STORAGE_BUCKET, Key, Body: pass }).promise(),
- }
-}
-
-export async function uploadStreamToS3(data: any, filename: string) {
- const stream = uploadStream({
- Key: filename,
- })
- await writeAsyncIterableToWritable(data, stream.writeStream)
- const file = await stream.promise
- return file.Location
-}
-
-export const s3UploadHandler: UploadHandler = async ({
- name,
- filename,
- data,
-}) => {
- if (name !== "img") {
- return undefined;
- }
- const uploadedFileLocation = await uploadStreamToS3(data, filename!)
- return uploadedFileLocation
-}
diff --git a/examples/file-and-s3-upload/package.json b/examples/file-and-s3-upload/package.json
deleted file mode 100644
index e54e9a1b766..00000000000
--- a/examples/file-and-s3-upload/package.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "private": true,
- "sideEffects": false,
- "scripts": {
- "build": "remix build",
- "dev": "remix dev",
- "start": "remix-serve build"
- },
- "dependencies": {
- "@remix-run/node": "1.5.1",
- "@remix-run/react": "1.5.1",
- "@remix-run/serve": "1.5.1",
- "aws-sdk": "^2.1152.0",
- "cloudinary": "^1.28.1",
- "react": "^17.0.2",
- "react-dom": "^17.0.2"
- },
- "devDependencies": {
- "@remix-run/dev": "1.5.1",
- "@remix-run/eslint-config": "1.5.1",
- "@types/react": "^17.0.39",
- "@types/react-dom": "^17.0.13",
- "eslint": "^8.10.0",
- "typescript": "^4.6.2"
- },
- "engines": {
- "node": ">=14"
- }
-}
diff --git a/examples/file-and-s3-upload/public/favicon.ico b/examples/file-and-s3-upload/public/favicon.ico
deleted file mode 100644
index 8830cf6821b354114848e6354889b8ecf6d2bc61..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 16958
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
EKbET!0{{R3
diff --git a/examples/file-and-s3-upload/remix.config.js b/examples/file-and-s3-upload/remix.config.js
deleted file mode 100644
index 260b82c7cb1..00000000000
--- a/examples/file-and-s3-upload/remix.config.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- * @type {import('@remix-run/dev').AppConfig}
- */
-module.exports = {
- ignoredRouteFiles: ["**/.*"],
- // appDirectory: "app",
- // assetsBuildDirectory: "public/build",
- // serverBuildPath: "build/index.js",
- // publicPath: "/build/",
-};
diff --git a/examples/file-and-s3-upload/remix.env.d.ts b/examples/file-and-s3-upload/remix.env.d.ts
deleted file mode 100644
index 72e2affe311..00000000000
--- a/examples/file-and-s3-upload/remix.env.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-///
-///
diff --git a/examples/file-and-s3-upload/sandbox.config.json b/examples/file-and-s3-upload/sandbox.config.json
deleted file mode 100644
index 4363d87a30d..00000000000
--- a/examples/file-and-s3-upload/sandbox.config.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "hardReloadOnChange": true,
- "container": {
- "port": 3000
- }
-}
diff --git a/examples/file-and-s3-upload/tsconfig.json b/examples/file-and-s3-upload/tsconfig.json
deleted file mode 100644
index 20f8a386a6c..00000000000
--- a/examples/file-and-s3-upload/tsconfig.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
- "compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2019"],
- "isolatedModules": true,
- "esModuleInterop": true,
- "jsx": "react-jsx",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "target": "ES2019",
- "strict": true,
- "allowJs": true,
- "forceConsistentCasingInFileNames": true,
- "baseUrl": ".",
- "paths": {
- "~/*": ["./app/*"]
- },
-
- // Remix takes care of building everything in `remix build`.
- "noEmit": true
- }
-}
From 37d8ec8dd6d19ed51254eae2ec73b95fc10b5cb6 Mon Sep 17 00:00:00 2001
From: GSt4r <11350542+GSt4r@users.noreply.github.com>
Date: Thu, 9 Jun 2022 23:13:23 +0200
Subject: [PATCH 3/5] S3 file upload (#3)
* Implemented S3 upload
* Updated README
* Cleaned up dependencies
---
examples/file-and-s3-upload/.env.sample | 4 ++
examples/file-and-s3-upload/.eslintrc.js | 3 +
examples/file-and-s3-upload/.gitignore | 6 ++
examples/file-and-s3-upload/README.md | 35 +++++++++++
.../file-and-s3-upload/app/entry.client.tsx | 4 ++
.../file-and-s3-upload/app/entry.server.tsx | 21 +++++++
examples/file-and-s3-upload/app/root.tsx | 32 ++++++++++
.../app/routes/s3-upload.tsx | 59 ++++++++++++++++++
.../file-and-s3-upload/app/utils/s3.server.ts | 46 ++++++++++++++
examples/file-and-s3-upload/package.json | 28 +++++++++
.../file-and-s3-upload/public/favicon.ico | Bin 0 -> 16958 bytes
examples/file-and-s3-upload/remix.config.js | 10 +++
examples/file-and-s3-upload/remix.env.d.ts | 2 +
.../file-and-s3-upload/sandbox.config.json | 6 ++
examples/file-and-s3-upload/tsconfig.json | 22 +++++++
15 files changed, 278 insertions(+)
create mode 100644 examples/file-and-s3-upload/.env.sample
create mode 100644 examples/file-and-s3-upload/.eslintrc.js
create mode 100644 examples/file-and-s3-upload/.gitignore
create mode 100644 examples/file-and-s3-upload/README.md
create mode 100644 examples/file-and-s3-upload/app/entry.client.tsx
create mode 100644 examples/file-and-s3-upload/app/entry.server.tsx
create mode 100644 examples/file-and-s3-upload/app/root.tsx
create mode 100644 examples/file-and-s3-upload/app/routes/s3-upload.tsx
create mode 100644 examples/file-and-s3-upload/app/utils/s3.server.ts
create mode 100644 examples/file-and-s3-upload/package.json
create mode 100644 examples/file-and-s3-upload/public/favicon.ico
create mode 100644 examples/file-and-s3-upload/remix.config.js
create mode 100644 examples/file-and-s3-upload/remix.env.d.ts
create mode 100644 examples/file-and-s3-upload/sandbox.config.json
create mode 100644 examples/file-and-s3-upload/tsconfig.json
diff --git a/examples/file-and-s3-upload/.env.sample b/examples/file-and-s3-upload/.env.sample
new file mode 100644
index 00000000000..17b368d399e
--- /dev/null
+++ b/examples/file-and-s3-upload/.env.sample
@@ -0,0 +1,4 @@
+STORAGE_ACCESS_KEY=
+STORAGE_SECRET=
+STORAGE_REGION=
+STORAGE_BUCKET=
\ No newline at end of file
diff --git a/examples/file-and-s3-upload/.eslintrc.js b/examples/file-and-s3-upload/.eslintrc.js
new file mode 100644
index 00000000000..ced78085f86
--- /dev/null
+++ b/examples/file-and-s3-upload/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
+};
diff --git a/examples/file-and-s3-upload/.gitignore b/examples/file-and-s3-upload/.gitignore
new file mode 100644
index 00000000000..3f7bf98da3e
--- /dev/null
+++ b/examples/file-and-s3-upload/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+
+/.cache
+/build
+/public/build
+.env
diff --git a/examples/file-and-s3-upload/README.md b/examples/file-and-s3-upload/README.md
new file mode 100644
index 00000000000..36249dcd670
--- /dev/null
+++ b/examples/file-and-s3-upload/README.md
@@ -0,0 +1,35 @@
+# Upload images to S3
+
+This is a simple example of using the remix built-in [uploadHandler](https://remix.run/docs/en/v1/api/remix#uploadhandler) and Form with multipart data to upload a file with the built-in local uploader and upload an image file to S3 with a custom uploader and display it. You can test it locally by running the dev server and opening the path `/s3-upload` in your browser.
+
+The relevent files are:
+
+```
+├── app
+│ ├── routes
+│ │ ├── s3-upload.tsx // upload to S3
+│ └── utils
+│ └── s3.server.ts // init S3 client on server side
+|── .env // holds AWS S3 credentails
+```
+
+## Steps to set up an S3 bucket
+
+- Sign up for an [AWS account](https://portal.aws.amazon.com/billing/signup) - this will require a credit card
+- Create an S3 bucket in your desired region
+- Create an access key pair for an IAM user that has access to the bucket
+- Copy the .env.sample to .env and fill in the S3 bucket, the region as well as the access key and secret key from the IAM user
+
+Note: in order for the image to be displayed after being uploaded to your S3 bucket in this example, the bucket needs to have public access enabled, which is potentially dangerous.
+
+> :warning: Lambda imposes a [limit of 6MB](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html) on the invocation payload size. If you use this example with Remix running on Lambda, you can only update files with a size smaller than 6MB.
+
+Open this example on [CodeSandbox](https://codesandbox.com):
+
+[![Open in CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/remix-run/remix/tree/main/examples/file-and-s3-upload)
+
+## Related Links
+
+- [Handle Multiple Part Forms (File Uploads)](https://remix.run/docs/en/v1/api/remix#unstable_parsemultipartformdata-node)
+- [Upload Handler](https://remix.run/docs/en/v1/api/remix#uploadhandler)
+- [Custom Uploader](https://remix.run/docs/en/v1/api/remix#custom-uploadhandler)
diff --git a/examples/file-and-s3-upload/app/entry.client.tsx b/examples/file-and-s3-upload/app/entry.client.tsx
new file mode 100644
index 00000000000..3eec1fd0a02
--- /dev/null
+++ b/examples/file-and-s3-upload/app/entry.client.tsx
@@ -0,0 +1,4 @@
+import { RemixBrowser } from "@remix-run/react";
+import { hydrate } from "react-dom";
+
+hydrate(, document);
diff --git a/examples/file-and-s3-upload/app/entry.server.tsx b/examples/file-and-s3-upload/app/entry.server.tsx
new file mode 100644
index 00000000000..5afa18235cc
--- /dev/null
+++ b/examples/file-and-s3-upload/app/entry.server.tsx
@@ -0,0 +1,21 @@
+import type { EntryContext } from "@remix-run/node";
+import { RemixServer } from "@remix-run/react";
+import { renderToString } from "react-dom/server";
+
+export default function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ remixContext: EntryContext
+) {
+ const markup = renderToString(
+
+ );
+
+ responseHeaders.set("Content-Type", "text/html");
+
+ return new Response("" + markup, {
+ status: responseStatusCode,
+ headers: responseHeaders,
+ });
+}
diff --git a/examples/file-and-s3-upload/app/root.tsx b/examples/file-and-s3-upload/app/root.tsx
new file mode 100644
index 00000000000..927a0f745df
--- /dev/null
+++ b/examples/file-and-s3-upload/app/root.tsx
@@ -0,0 +1,32 @@
+import type { MetaFunction } from "@remix-run/node";
+import {
+ Links,
+ LiveReload,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from "@remix-run/react";
+
+export const meta: MetaFunction = () => ({
+ charset: "utf-8",
+ title: "New Remix App",
+ viewport: "width=device-width,initial-scale=1",
+});
+
+export default function App() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/file-and-s3-upload/app/routes/s3-upload.tsx b/examples/file-and-s3-upload/app/routes/s3-upload.tsx
new file mode 100644
index 00000000000..c9bedadfc3f
--- /dev/null
+++ b/examples/file-and-s3-upload/app/routes/s3-upload.tsx
@@ -0,0 +1,59 @@
+import type { ActionFunction, UploadHandler } from "@remix-run/node";
+import {
+ json,
+ unstable_composeUploadHandlers as composeUploadHandlers,
+ unstable_createMemoryUploadHandler as createMemoryUploadHandler,
+ unstable_parseMultipartFormData as parseMultipartFormData,
+} from "@remix-run/node";
+import { Form, useActionData } from "@remix-run/react";
+import { s3UploadHandler } from "~/utils/s3.server";
+
+type ActionData = {
+ errorMsg?: string;
+ imgSrc?: string;
+ imgDesc?: string;
+};
+
+export const action: ActionFunction = async ({ request }) => {
+ const uploadHandler: UploadHandler = composeUploadHandlers(
+ s3UploadHandler,
+ createMemoryUploadHandler()
+ );
+ const formData = await parseMultipartFormData(request, uploadHandler);
+ const imgSrc = formData.get("img");
+ const imgDesc = formData.get("desc");
+ console.log(imgDesc)
+ if (!imgSrc) {
+ return json({
+ error: "Something went wrong while uploading",
+ });
+ }
+ return json({
+ imgSrc,
+ imgDesc,
+ });
+};
+
+export default function Index() {
+ const data = useActionData();
+ return (
+ <>
+
+ {data?.errorMsg && {data.errorMsg}
}
+ {data?.imgSrc && (
+ <>
+ File has been uploaded to S3 and is available under the following URL (if the bucket has public access enabled):
+ {data.imgSrc}
+
+
+ >
+ )}
+ >
+ );
+}
diff --git a/examples/file-and-s3-upload/app/utils/s3.server.ts b/examples/file-and-s3-upload/app/utils/s3.server.ts
new file mode 100644
index 00000000000..5f755c83fff
--- /dev/null
+++ b/examples/file-and-s3-upload/app/utils/s3.server.ts
@@ -0,0 +1,46 @@
+import AWS from "aws-sdk"
+import type { UploadHandler } from "@remix-run/node"
+import { writeAsyncIterableToWritable } from "@remix-run/node"
+import { PassThrough } from "stream"
+
+const { STORAGE_ACCESS_KEY, STORAGE_SECRET, STORAGE_REGION, STORAGE_BUCKET } = process.env
+
+if (!(STORAGE_ACCESS_KEY && STORAGE_SECRET && STORAGE_REGION && STORAGE_BUCKET)) {
+ throw new Error(`Storage is missing required configuration.`)
+}
+
+const uploadStream = ({ Key }: Pick) => {
+ const s3 = new AWS.S3({
+ credentials: {
+ accessKeyId: STORAGE_ACCESS_KEY,
+ secretAccessKey: STORAGE_SECRET,
+ },
+ region: STORAGE_REGION,
+ })
+ const pass = new PassThrough()
+ return {
+ writeStream: pass,
+ promise: s3.upload({ Bucket: STORAGE_BUCKET, Key, Body: pass }).promise(),
+ }
+}
+
+export async function uploadStreamToS3(data: any, filename: string) {
+ const stream = uploadStream({
+ Key: filename,
+ })
+ await writeAsyncIterableToWritable(data, stream.writeStream)
+ const file = await stream.promise
+ return file.Location
+}
+
+export const s3UploadHandler: UploadHandler = async ({
+ name,
+ filename,
+ data,
+}) => {
+ if (name !== "img") {
+ return undefined;
+ }
+ const uploadedFileLocation = await uploadStreamToS3(data, filename!)
+ return uploadedFileLocation
+}
diff --git a/examples/file-and-s3-upload/package.json b/examples/file-and-s3-upload/package.json
new file mode 100644
index 00000000000..edd814eb782
--- /dev/null
+++ b/examples/file-and-s3-upload/package.json
@@ -0,0 +1,28 @@
+{
+ "private": true,
+ "sideEffects": false,
+ "scripts": {
+ "build": "remix build",
+ "dev": "remix dev",
+ "start": "remix-serve build"
+ },
+ "dependencies": {
+ "@remix-run/node": "1.5.1",
+ "@remix-run/react": "1.5.1",
+ "@remix-run/serve": "1.5.1",
+ "aws-sdk": "^2.1152.0",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ },
+ "devDependencies": {
+ "@remix-run/dev": "1.5.1",
+ "@remix-run/eslint-config": "1.5.1",
+ "@types/react": "^17.0.39",
+ "@types/react-dom": "^17.0.13",
+ "eslint": "^8.10.0",
+ "typescript": "^4.6.2"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+}
diff --git a/examples/file-and-s3-upload/public/favicon.ico b/examples/file-and-s3-upload/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..8830cf6821b354114848e6354889b8ecf6d2bc61
GIT binary patch
literal 16958
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
EKbET!0{{R3
literal 0
HcmV?d00001
diff --git a/examples/file-and-s3-upload/remix.config.js b/examples/file-and-s3-upload/remix.config.js
new file mode 100644
index 00000000000..260b82c7cb1
--- /dev/null
+++ b/examples/file-and-s3-upload/remix.config.js
@@ -0,0 +1,10 @@
+/**
+ * @type {import('@remix-run/dev').AppConfig}
+ */
+module.exports = {
+ ignoredRouteFiles: ["**/.*"],
+ // appDirectory: "app",
+ // assetsBuildDirectory: "public/build",
+ // serverBuildPath: "build/index.js",
+ // publicPath: "/build/",
+};
diff --git a/examples/file-and-s3-upload/remix.env.d.ts b/examples/file-and-s3-upload/remix.env.d.ts
new file mode 100644
index 00000000000..72e2affe311
--- /dev/null
+++ b/examples/file-and-s3-upload/remix.env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/examples/file-and-s3-upload/sandbox.config.json b/examples/file-and-s3-upload/sandbox.config.json
new file mode 100644
index 00000000000..4363d87a30d
--- /dev/null
+++ b/examples/file-and-s3-upload/sandbox.config.json
@@ -0,0 +1,6 @@
+{
+ "hardReloadOnChange": true,
+ "container": {
+ "port": 3000
+ }
+}
diff --git a/examples/file-and-s3-upload/tsconfig.json b/examples/file-and-s3-upload/tsconfig.json
new file mode 100644
index 00000000000..20f8a386a6c
--- /dev/null
+++ b/examples/file-and-s3-upload/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "target": "ES2019",
+ "strict": true,
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+
+ // Remix takes care of building everything in `remix build`.
+ "noEmit": true
+ }
+}
From f1b40fec0d19caf448a38bf37cf61be6a1bb490d Mon Sep 17 00:00:00 2001
From: GSt4r <11350542+GSt4r@users.noreply.github.com>
Date: Thu, 9 Jun 2022 23:17:53 +0200
Subject: [PATCH 4/5] Update contributors.yml
---
contributors.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/contributors.yml b/contributors.yml
index e3e34e35e24..851ec208dc1 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -123,6 +123,7 @@
- gonzoscript
- graham42
- GregBrimble
+- GSt4r
- guerra08
- gunners6518
- hadizz
From 19c1495493139d83ea4fc9918da77eafc10ad213 Mon Sep 17 00:00:00 2001
From: GSt4r <11350542+GSt4r@users.noreply.github.com>
Date: Thu, 21 Jul 2022 21:26:50 +0200
Subject: [PATCH 5/5] S3 file upload (#4)
* Implemented S3 upload
* Updated README
* Updated README
* Cleaned up dependencies
* Fixed dependencies
* Refactored from useActionData to useFetcher
---
.../app/routes/s3-upload.tsx | 25 ++++++++++---------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/examples/file-and-s3-upload/app/routes/s3-upload.tsx b/examples/file-and-s3-upload/app/routes/s3-upload.tsx
index c9bedadfc3f..e4dc02d8b1b 100644
--- a/examples/file-and-s3-upload/app/routes/s3-upload.tsx
+++ b/examples/file-and-s3-upload/app/routes/s3-upload.tsx
@@ -5,7 +5,7 @@ import {
unstable_createMemoryUploadHandler as createMemoryUploadHandler,
unstable_parseMultipartFormData as parseMultipartFormData,
} from "@remix-run/node";
-import { Form, useActionData } from "@remix-run/react";
+import { useFetcher } from "@remix-run/react";
import { s3UploadHandler } from "~/utils/s3.server";
type ActionData = {
@@ -25,7 +25,7 @@ export const action: ActionFunction = async ({ request }) => {
console.log(imgDesc)
if (!imgSrc) {
return json({
- error: "Something went wrong while uploading",
+ errorMsg: "Something went wrong while uploading",
});
}
return json({
@@ -35,25 +35,26 @@ export const action: ActionFunction = async ({ request }) => {
};
export default function Index() {
- const data = useActionData();
+ const fetcher = useFetcher();
return (
<>
-
- {data?.errorMsg && {data.errorMsg}
}
- {data?.imgSrc && (
- <>
+
+ {fetcher.type === "done" ? (
+ fetcher.data.errorMsg ? (
+ {fetcher.data.errorMsg}
+ ) : (
+ <>
File has been uploaded to S3 and is available under the following URL (if the bucket has public access enabled):
- {data.imgSrc}
-
-
+ {fetcher.data.imgSrc}
+
>
- )}
+ )) : null}
>
);
}