Skip to content

Commit 16e9c55

Browse files
committed
first commit
1 parent 8c075d9 commit 16e9c55

12 files changed

+280
-56
lines changed

.env

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PORT=8081

README.md

+58-26
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,78 @@
1-
# Getting Started with Create React App
1+
# React Multiple File Upload example (using Typescript)
2+
React Multiple File Upload example with Typescript, Hooks, Progress Bars using Axios, Bootstrap, Multipart File, FormData.
23

3-
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4+
![react-multiple-file-upload-typescript](react-multiple-file-upload-typescript.png)
45

5-
## Available Scripts
6+
For instruction, please visit:
7+
> [React Typescript Multiple File Upload example](https://www.bezkoder.com/react-multiple-file-upload-typescript/)
68
7-
In the project directory, you can run:
9+
More Practice:
10+
> [React Typescript Multiple Image Upload example (with Preview)](https://www.bezkoder.com/react-typescript-multiple-image-upload/)
11+
12+
> [React Typescript CRUD example](https://www.bezkoder.com/react-typescript-api-call/)
13+
14+
> [React Typescript Custom Hook](https://www.bezkoder.com/react-custom-hook-typescript/)
15+
16+
> [React Hook Form Typescript example with Validation](https://www.bezkoder.com/react-hook-form-typescript/)
17+
18+
> [React Table example: CRUD App with react-table v7](https://www.bezkoder.com/react-table-example-hooks-crud/)
19+
20+
> [React Pagination using Hooks example](https://www.bezkoder.com/react-pagination-hooks/)
821
9-
### `yarn start`
22+
> [React Typescript JWT Authentication & Authorization example](https://www.bezkoder.com/react-typescript-authentication-example/)
1023
11-
Runs the app in the development mode.\
12-
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
24+
> [React + Redux + Hooks: JWT Authentication & Authorization example](https://www.bezkoder.com/react-hooks-redux-login-registration-example/)
1325
14-
The page will reload if you make edits.\
15-
You will also see any lint errors in the console.
26+
Fullstack with Node Express:
27+
> [React + Node Express + MySQL](https://www.bezkoder.com/react-node-express-mysql/)
1628
17-
### `yarn test`
29+
> [React + Node Express + PostgreSQL](https://www.bezkoder.com/react-node-express-postgresql/)
1830
19-
Launches the test runner in the interactive watch mode.\
20-
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
31+
> [React + Node Express + MongoDB](https://www.bezkoder.com/react-node-express-mongodb-mern-stack/)
2132
22-
### `yarn build`
33+
Fullstack with Spring Boot:
34+
> [React + Spring Boot + MySQL](https://www.bezkoder.com/react-spring-boot-crud/)
2335
24-
Builds the app for production to the `build` folder.\
25-
It correctly bundles React in production mode and optimizes the build for the best performance.
36+
> [React + Spring Boot + PostgreSQL](https://www.bezkoder.com/spring-boot-react-postgresql/)
2637
27-
The build is minified and the filenames include the hashes.\
28-
Your app is ready to be deployed!
38+
> [React + Spring Boot + MongoDB](https://www.bezkoder.com/react-spring-boot-mongodb/)
2939
30-
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
40+
Fullstack with Django:
41+
> [React Hooks + Django Rest Framework](https://www.bezkoder.com/django-react-hooks/)
3142
32-
### `yarn eject`
43+
Serverless with Firebase:
44+
> [React Typescript Firebase example: CRUD App](https://www.bezkoder.com/firebase-typescript-react/)
3345
34-
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
46+
> [React Typescript Firestore example: CRUD App](https://www.bezkoder.com/react-typescript-firestore/)
47+
48+
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
49+
50+
### Set port
51+
.env
52+
```
53+
PORT=8081
54+
```
55+
56+
## Project setup
57+
58+
In the project directory, you can run:
3559

36-
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
60+
```
61+
npm install
62+
# or
63+
yarn install
64+
```
3765

38-
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
66+
or
3967

40-
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
68+
### Compiles and hot-reloads for development
4169

42-
## Learn More
70+
```
71+
npm start
72+
# or
73+
yarn start
74+
```
4375

44-
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
76+
Open [http://localhost:8081](http://localhost:8081) to view it in the browser.
4577

46-
To learn React, check out the [React documentation](https://reactjs.org/).
78+
The page will reload if you make edits.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"@types/node": "^16.7.13",
1111
"@types/react": "^18.0.0",
1212
"@types/react-dom": "^18.0.0",
13+
"axios": "0.27.2",
14+
"bootstrap": "4.6.2",
1315
"react": "^18.2.0",
1416
"react-dom": "^18.2.0",
1517
"react-scripts": "5.0.1",
17.8 KB
Loading

src/App.test.tsx

-9
This file was deleted.

src/App.tsx

+12-19
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
1-
import React from 'react';
2-
import logo from './logo.svg';
3-
import './App.css';
1+
import "bootstrap/dist/css/bootstrap.min.css";
2+
import "./App.css";
43

5-
function App() {
4+
import FilesUpload from "./components/FilesUpload";
5+
6+
const App: React.FC = () => {
67
return (
7-
<div className="App">
8-
<header className="App-header">
9-
<img src={logo} className="App-logo" alt="logo" />
10-
<p>
11-
Edit <code>src/App.tsx</code> and save to reload.
12-
</p>
13-
<a
14-
className="App-link"
15-
href="https://reactjs.org"
16-
target="_blank"
17-
rel="noopener noreferrer"
18-
>
19-
Learn React
20-
</a>
21-
</header>
8+
<div className="container" style={{ width: "600px" }}>
9+
<div className="my-3">
10+
<h3>bezkoder.com</h3>
11+
<h4>React Typescript Multiple Files Upload</h4>
12+
</div>
13+
14+
<FilesUpload />
2215
</div>
2316
);
2417
}

src/components/FilesUpload.tsx

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { useState, useEffect, useRef } from "react";
2+
import UploadService from "../services/FileUploadService";
3+
import IFile from "../types/File";
4+
5+
interface ProgressInfo {
6+
fileName: string;
7+
percentage: number;
8+
}
9+
10+
const FilesUpload: React.FC = () => {
11+
const [selectedFiles, setSelectedFiles] = useState<FileList | null>(null);
12+
const [progressInfos, setProgressInfos] = useState<Array<ProgressInfo>>([]);
13+
const [message, setMessage] = useState<Array<string>>([]);
14+
const [fileInfos, setFileInfos] = useState<Array<IFile>>([]);
15+
const progressInfosRef = useRef<any>(null);
16+
17+
useEffect(() => {
18+
UploadService.getFiles().then((response) => {
19+
setFileInfos(response.data);
20+
});
21+
}, []);
22+
23+
const selectFiles = (event: React.ChangeEvent<HTMLInputElement>) => {
24+
setSelectedFiles(event.target.files);
25+
setProgressInfos([]);
26+
setMessage([]);
27+
};
28+
29+
const upload = (idx: number, file: File) => {
30+
let _progressInfos = [...progressInfosRef.current];
31+
return UploadService.upload(file, (event) => {
32+
_progressInfos[idx].percentage = Math.round(
33+
(100 * event.loaded) / event.total
34+
);
35+
setProgressInfos(_progressInfos);
36+
})
37+
.then(() => {
38+
setMessage((prevMessage) => [
39+
...prevMessage,
40+
file.name + ": Successful!"
41+
]);
42+
})
43+
.catch((err: any) => {
44+
_progressInfos[idx].percentage = 0;
45+
setProgressInfos(_progressInfos);
46+
47+
let msg = file.name + ": Failed!";
48+
if (err.response && err.response.data && err.response.data.message) {
49+
msg += " " + err.response.data.message;
50+
}
51+
52+
setMessage((prevMessage) => [
53+
...prevMessage,
54+
msg
55+
]);
56+
});
57+
};
58+
59+
const uploadFiles = () => {
60+
if (selectedFiles != null) {
61+
const files = Array.from(selectedFiles);
62+
63+
let _progressInfos = files.map((file) => ({
64+
percentage: 0,
65+
fileName: file.name
66+
}));
67+
68+
progressInfosRef.current = _progressInfos;
69+
70+
const uploadPromises = files.map((file, i) => upload(i, file));
71+
72+
Promise.all(uploadPromises)
73+
.then(() => UploadService.getFiles())
74+
.then((files) => {
75+
setFileInfos(files.data);
76+
});
77+
78+
setMessage([]);
79+
}
80+
};
81+
82+
return (
83+
<div>
84+
{progressInfos &&
85+
progressInfos.length > 0 &&
86+
progressInfos.map((progressInfo: ProgressInfo, index: number) => (
87+
<div className="mb-2" key={index}>
88+
<span>{progressInfo.fileName}</span>
89+
<div className="progress">
90+
<div
91+
className="progress-bar progress-bar-info"
92+
role="progressbar"
93+
aria-valuenow={progressInfo.percentage}
94+
aria-valuemin={0}
95+
aria-valuemax={100}
96+
style={{ width: progressInfo.percentage + "%" }}
97+
>
98+
{progressInfo.percentage}%
99+
</div>
100+
</div>
101+
</div>
102+
))}
103+
104+
<div className="row my-3">
105+
<div className="col-8">
106+
<label className="btn btn-default p-0">
107+
<input type="file" multiple onChange={selectFiles} />
108+
</label>
109+
</div>
110+
111+
<div className="col-4">
112+
<button
113+
className="btn btn-success btn-sm"
114+
disabled={!selectedFiles}
115+
onClick={uploadFiles}
116+
>
117+
Upload
118+
</button>
119+
</div>
120+
</div>
121+
122+
{message.length > 0 && (
123+
<div className="alert alert-secondary" role="alert">
124+
<ul>
125+
{message.map((item, i) => {
126+
return <li key={i}>{item}</li>;
127+
})}
128+
</ul>
129+
</div>
130+
)}
131+
132+
<div className="card">
133+
<div className="card-header">List of Files</div>
134+
<ul className="list-group list-group-flush">
135+
{fileInfos &&
136+
fileInfos.map((file, index) => (
137+
<li className="list-group-item" key={index}>
138+
<a href={file.url}>{file.name}</a>
139+
</li>
140+
))}
141+
</ul>
142+
</div>
143+
</div>
144+
);
145+
};
146+
147+
export default FilesUpload;

src/http-common.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import axios from "axios";
2+
3+
export default axios.create({
4+
baseURL: "http://localhost:8080",
5+
headers: {
6+
"Content-type": "application/json",
7+
},
8+
});

src/logo.svg

-1
This file was deleted.

src/services/FileUploadService.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import http from "../http-common";
2+
3+
const upload = (file: File, onUploadProgress: (progressEvent: any) => void): Promise<any> => {
4+
let formData = new FormData();
5+
6+
formData.append("file", file);
7+
8+
return http.post("/upload", formData, {
9+
headers: {
10+
"Content-Type": "multipart/form-data",
11+
},
12+
onUploadProgress,
13+
});
14+
};
15+
16+
const getFiles = () : Promise<any> => {
17+
return http.get("/files");
18+
};
19+
20+
const FileUploadService = {
21+
upload,
22+
getFiles,
23+
};
24+
25+
export default FileUploadService;

src/types/File.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default interface IFile {
2+
url: string,
3+
name: string,
4+
}

0 commit comments

Comments
 (0)