File upload with React and Firebase


Créer un projet
Créer un projet

Créer un projet

Créer un projet

Créer un projet

Cliquez sur projet web
Créer un projet

Créer un projet

Créer un projet

mettre la config dans un fichier : firebase/config.ts
Installer firebase

npm i firebase --save
Importer firebase

import * as firebase from 'firebase/app';
Importer storage et firestore
import 'firebase/storage';
import 'firebase/firestore';

Inititaliser storage et firestore
const projectStorage = firebase.storage();
const projectFirestore = firebase.firestore();
export { projectStorage, projectFirestore };

Code
import * as firebase from 'firebase/app';
import 'firebase/storage';
import 'firebase/firestore';
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: 'AIzaSyDAmgRgchDRU0du9-WPfy7XFnkgk7r2mVw',
authDomain: 'image-upload-d2866.firebaseapp.com',
databaseURL: 'https://image-upload-d2866.firebaseio.com',
projectId: 'image-upload-d2866',
storageBucket: 'image-upload-d2866.appspot.com',
messagingSenderId: '993562178799',
appId: '1:993562178799:web:fdf294227df4f043eb571a',
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const projectStorage = firebase.storage();
const projectFirestore = firebase.firestore();
export { projectStorage, projectFirestore };
Créer une base de données

Créer une base de données

Créer une base de données

Créer une base de données

Créer un service storage

Créer un service storage

Créer un service storage

Créer un service storage

Changement des règles

allow read, write: if request.auth != null;
devient allow read, write
Changement des règles

Création de notre uploader
import React from 'react';
export default function Uploader() {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
console.log(event.currentTarget.files);
}
return (
<form>
<input type="file" onChange={handleChange}/>
</form>
);
}
Création de notre uploader

Création de notre uploader

Gestion du state de l'uploader
import React, { useState } from 'react';
export default function Uploader() {
const [file, setFile] = useState<any>(undefined)
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
if(event.currentTarget.files === null){
return;
}
const selectedFile = event.currentTarget.files[0];
if(selectedFile){
setFile(selectedFile);
}
}
return (
<form>
<input type="file" onChange={handleChange}/>
</form>
);
}
Gestion des types
import React, { useState } from 'react';
export default function Uploader() {
const [file, setFile] = useState<any>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const types = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'];
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.currentTarget.files === null) {
return;
}
const selectedFile = event.currentTarget.files[0];
if (selectedFile && types.includes(selectedFile.type)) {
setFile(selectedFile);
setError(undefined);
} else {
setFile(undefined);
setError('Le format du fichier est incorrect');
}
};
return (
<form>
<input type="file" onChange={handleChange} />
{error ? <p>{error}</p> : null}
</form>
);
}Test gestion des types

Création d'un hook useStorage
import { useState, useEffect } from 'react';
import { projectStorage } from '../../../firebase/config';
const useStorage = (file: any) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState<any | undefined>(undefined);
const [url, setUrl] = useState<any | undefined>(undefined);
useEffect(() => {
const storageRef = projectStorage.ref(file.name);
storageRef.put(file).on(
'state_changed',
(snap) => {
let percentage = Math.round((snap.bytesTransferred / snap.totalBytes) * 100);
setProgress(percentage);
},
(err) => {
setError(err);
},
async () => {
const url = await storageRef.getDownloadURL();
setUrl(url);
}
);
}, [file]);
return { progress, url, error };
};
export default useStorage;Création d'une progress bar
import React, { useEffect } from 'react';
import useStorage from './hooks/useStorage';
export default function ProgressBar({
file,
onFileUploadCompleted,
}: {
file: any;
onFileUploadCompleted: Function;
}) {
const { url, progress } = useStorage(file);
useEffect(() => {
if(url) {
onFileUploadCompleted(url);
}
}, [url, onFileUploadCompleted]);
return (
<div>
<p>{progress} %</p>
<div style={{ width: `${progress}%` }}></div>
</div>
);
}
Style de la progress bar
.progressBar {
height: 5px;
background: #2ecc71;
}style.module.scss
import styles from './styles.module.scss';
return (
<div>
<p>{progress} %</p>
<div className={styles.progressBar} style={{ width: `${progress}%` }}></div>
</div>
);progressBar.tsx
Test de notre composant

Le storage Firebase

Faire le lien entre le storage et la base de données
const getTimestamp = firebase.firestore.FieldValue.serverTimestamp;
export { projectStorage, projectFirestore, getTimestamp };
Nous avons besoin de cela dans notre fichier de configuration afin d'enregistrer la date de création de l'image dans la base de données
Faire le lien entre le storage et la base de données
import { useState, useEffect } from 'react';
import { projectStorage, projectFirestore, getTimestamp } from '../../../firebase/config';
const useStorage = (file: any) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState<any | undefined>(undefined);
const [url, setUrl] = useState<any | undefined>(undefined);
useEffect(() => {
const storageRef = projectStorage.ref(file.name);
const collectionRef = projectFirestore.collection('image');
storageRef.put(file).on(
'state_changed',
(snap) => {
let percentage = Math.round((snap.bytesTransferred / snap.totalBytes) * 100);
setProgress(percentage);
},
(err) => {
setError(err);
},
async () => {
const url = await storageRef.getDownloadURL();
collectionRef.add({ url, createdAt: getTimestamp() })
setUrl(url);
}
);
}, [file]);
return { progress, url, error };
};
export default useStorage;
Code uploader
import React, { useState } from 'react';
import ProgressBar from './ProgressBar';
export default function Uploader() {
const [file, setFile] = useState<any>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const types = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'];
const onFileUploadCompleted = (url: string) => {
setFile(undefined);
setError(undefined);
}
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.currentTarget.files === null) {
return;
}
const selectedFile = event.currentTarget.files[0];
if (selectedFile && types.includes(selectedFile.type)) {
setFile(selectedFile);
setError(undefined);
} else {
setFile(undefined);
setError('Le format du fichier est incorrect');
}
};
return (
<form>
<input type="file" onChange={handleChange} />
{error ? <p>{error}</p> : null}
{file && <ProgressBar file={file} onFileUploadCompleted={onFileUploadCompleted}/>}
</form>
);
}
Image grid component
import React from 'react';
import styles from './styles.module.scss';
export default function ImageGrid() {
return (
<div className={styles.imgGrid}>
images
</div>
)
}.imgGrid {}Créer useFirestore hook
import { useState, useEffect } from 'react';
import { projectFirestore } from '../../../firebase/config';
export default function useFirestore(collection: any) {
const [docs, setDocs] = useState<Array<any>>([]);
useEffect(() => {
const unsuscribe = projectFirestore
.collection(collection)
.orderBy('createdAt', 'desc')
.onSnapshot((snap) => {
let documents: Array<any> = [];
snap.forEach((doc) => documents.push({ ...doc.data(), id: doc.id }));
setDocs(documents);
});
return () => unsuscribe();
}, [collection]);
return { docs };
}
Utilisation du hook dans Image grid
import React from 'react';
import styles from './styles.module.scss';
import useFirestore from '../../components/Uploader/hooks/useFirestore';
export default function ImageGrid({ onSelectedImage}: {onSelectedImage: Function}) {
const { docs } = useFirestore('image');
return (
<div className={styles.imgGrid}>
{docs &&
docs.map((doc) => {
return (
<div key={doc.id} className={styles.imgWrapper}>
<img src={doc.url} onClick={() => onSelectedImage(doc.url)} />
</div>
);
})}
</div>
);
}
Style du composant image grid
.imgGrid {
margin: 20px auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 25px;
}
.imgWrapper {
overflow: hidden;
height: 0;
padding: 50% 0;
position: relative;
img {
min-width: 100%;
min-height: 100%;
position: absolute;
top: 0;
left: 0;
max-width: 150%;
}
}
Resultat

Création d'une modal
import React from 'react';
import styles from './styles.module.scss';
export default function Modal({
image,
onBackdropClick,
}: {
image: string | undefined;
onBackdropClick: any;
}) {
return (
<div className={styles.backdrop} onClick={onBackdropClick}>
<img src={image} />
</div>
);
}
Style de la modal
.backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(30, 30, 30, 0.5);
z-index: 9999;
img {
margin: 60px auto;
display: block;
max-width: 70%;
max-height: 70%;
}
}
Interaction entre nos composants
import React, { useState } from 'react';
import styles from './styles.module.scss';
import Topbar from '../../components/Topbar';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
import { Link } from 'react-router-dom';
import Uploader from '../../components/Uploader';
import ImageGrid from '../../components/ImageGrid';
import Modal from '../../components/Modal';
export default function Dashboard() {
const [selectedImage, setSelectedImage] = useState<string | undefined>(
undefined
);
const handleSelectedImage = (image: string) => {
setSelectedImage(image);
};
const handleOnCloseBackdrop = () => {
setSelectedImage(undefined);
}
return (
<div>
<Topbar>
<Link to="/" className={styles.backButton}>
<FontAwesomeIcon icon={faChevronLeft} />
</Link>
</Topbar>
<div className="mt-4 p-4">
<Uploader />
</div>
<div className="mt-4 p-4">
<ImageGrid onSelectedImage={handleSelectedImage} />
</div>
{selectedImage ? <Modal image={selectedImage} onBackdropClick={handleOnCloseBackdrop} /> : null}
</div>
);
}
Résultat

File upload with React and Firebase
By NicoHash
File upload with React and Firebase
- 240