Я создаю приложение для чата с помощью React 18 и Firebase 9.
У меня возникла проблема при разработке функции загрузки изображений для формы «Регистрация».
В src\firebaseConfig.js у меня есть:
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getStorage } from "firebase/storage";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID
};
// Initialize Firebase
export const app = initializeApp(firebaseConfig);
export const auth = getAuth();
export const storage = getStorage();
export const db = getFirestore();
В компоненте src\pages\Register.jsx у меня есть:
import React, { useState } from "react";
import md5 from "md5";
import FormCard from "../components/FormCard/FormCard";
import { make } from "simple-body-validator";
import { createUserWithEmailAndPassword, updateProfile } from "firebase/auth";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import { doc, setDoc } from "firebase/firestore";
import { auth, db, storage } from "../firebaseConfig";
export default function Register() {
const initialFormData = {
firstName: "",
lastName: "",
email: "",
avatar: "",
password: "",
password_confirmation: ""
};
const validationRules = {
firstName: ["required", "string", "min:3", "max:255"],
lastName: ["required", "string", "min:3", "max:255"],
email: ["required", "email"],
avatar: ["required"],
password: ["required", "min:6", "confirmed"],
password_confirmation: ["required"]
};
const validator = make(initialFormData, validationRules);
const [formData, setFormData] = useState(initialFormData);
// Form validation errors
const [errors, setErrors] = useState(validator.errors());
// Firebase errors
const [error, setError] = useState(false);
const [pristineFields, setPristineFields] = useState(() =>
Object.keys(initialFormData)
);
const handleChangeEvent = (event) => {
const { name, value } = event.target;
setFormData((prevFormData) => {
const newFormData = { ...prevFormData, [name]: value };
const newPristineFields = pristineFields.filter((f) => f !== name);
validator.setData(newFormData).validate();
const validationErrors = validator.errors();
newPristineFields.forEach((f) => validationErrors.forget(f));
setErrors(validationErrors);
setPristineFields(newPristineFields);
return newFormData;
});
};
const handleSubmit = async (event) => {
event.preventDefault();
if (!validator.setData(formData).validate()) {
setErrors(validator.errors());
} else {
const email = formData.email;
const password = formData.password;
const successContainer = document.getElementById('successAlert');
const errContainer = document.getElementById('errorAlert');
try {
const response = await createUserWithEmailAndPassword(auth, email, password);
// Hide error
errContainer.classList.add('d-none');
// Show success
successContainer.classList.remove('d-none');
// Create avatar filename
const date = new Date().getTime();
let userAvatar = formData.avatar ? md5(`${formData.email}-${date}`) + `.${formData.avatar.split('.')[1]}` : 'default-avatar.png';
const storageRef = ref(storage, userAvatar);
// Upload image
await uploadBytesResumable(storageRef, formData.avatar).then(() => {
getDownloadURL(storageRef).then(async () => {
try {
// Update profile
await updateProfile(response.user, {
avatar: userAvatar,
});
// Store user
await setDoc(doc(db, "users", response.user.uid), {
uid: response.user.uid,
firstName: formData.firstName,
lastName: formData.lastName,
email: formData.email,
avatar: userAvatar,
});
} catch (error) {
setError(true);
console.info(error)
}
})
})
} catch (error) {
setError(true);
errContainer.append(error.message);
errContainer.classList.remove('d-none');
}
}
setPristineFields([]);
};
return (
<FormCard title = "Register">
<div id = "errorAlert" class = "d-none text-center alert alert-danger alert-dismissible">
<button type = "button" class = "btn-close" data-bs-dismiss = "alert"></button>
{error}
</div>
<div id = "successAlert" class = "d-none text-center alert alert-success alert-dismissible">
<button type = "button" class = "btn-close" data-bs-dismiss = "alert"></button>
You have registered successfully!
</div>
<form onSubmit = {handleSubmit}>
<div
className = {`mb-2 form-element ${errors.has("firstName") ? "has-error" : null
}`}
>
<label for = "firstName" className = "form-label">
First name
</label>
<input
type = "text"
id = "firstName"
name = "firstName"
value = {formData.firstName}
onChange = {handleChangeEvent}
className = "form-control form-control-sm"
/>
{errors.has("firstName") ? (
<p className = "invalid-feedback">{errors.first("firstName")}</p>
) : null}
</div>
<div
className = {`mb-2 form-element ${errors.has("lastName") ? "has-error" : null
}`}
>
<label for = "lastName" className = "form-label">
Last name
</label>
<input
type = "text"
id = "lastName"
name = "lastName"
value = {formData.lastName}
onChange = {handleChangeEvent}
className = "form-control form-control-sm"
/>
{errors.has("lastName") ? (
<p className = "invalid-feedback">{errors.first("lastName")}</p>
) : null}
</div>
<div
className = {`mb-2 form-element ${errors.has("email") ? "has-error" : null
}`}
>
<label for = "email" className = "form-label">
Email address
</label>
<input
type = "email"
id = "email"
name = "email"
value = {formData.email}
onChange = {handleChangeEvent}
className = "form-control form-control-sm"
/>
{errors.has("email") ? (
<p className = "invalid-feedback">{errors.first("email")}</p>
) : null}
</div>
<div
className = {`mb-2 form-element ${errors.has("avatar") ? "has-error" : null
}`}
>
<label for = "avatar" className = "form-label">
Avatar
</label>
<input
type = "file"
id = "avatar"
name = "avatar"
value = {formData.avatar}
onChange = {handleChangeEvent}
className = "form-control form-control-sm"
/>
{errors.has("avatar") ? (
<p className = "invalid-feedback">{errors.first("avatar")}</p>
) : null}
</div>
<div
className = {`mb-2 form-element ${errors.has("password") ? "has-error" : null
}`}
>
<label for = "password" className = "form-label">
Password
</label>
<input
type = "password"
id = "password"
name = "password"
value = {formData.password}
onChange = {handleChangeEvent}
className = "form-control form-control-sm"
/>
{errors.has("password") ? (
<p className = "invalid-feedback">{errors.first("password")}</p>
) : null}
</div>
<div
className = {`mb-2 form-element ${errors.has("password_confirmation") ? "has-error" : null
}`}
>
<label for = "password_repeat" className = "form-label">
Confirm Password
</label>
<input
type = "password"
id = "password_repeat"
name = "password_confirmation"
value = {formData.password_confirmation}
onChange = {handleChangeEvent}
className = "form-control form-control-sm"
/>
{errors.has("password_confirmation") ? (
<p className = "invalid-feedback">
{errors.first("password_confirmation")}
</p>
) : null}
</div>
<div className = "pt-1">
<button type = "submit" className = "btn btn-sm btn-success fw-bold">
Submit
</button>
</div>
</form>
</FormCard>
);
}
Данные формы успешно отправлены, как показано ниже.
Изображение, которое должно быть загружено в хранилище Firebase, имеет тип «application/octet-stream» вместо «image/jpeg» и имеет размер менее 10 байт.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Как упоминалось в документации,
Если метаданные contentType не указаны и файл не имеет расширения, Cloud Storage по умолчанию использует тип application/octet-stream.
const meta = { contentType: 'image/jpeg' };
await uploadBytesResumable(storageRef, formData.avatar, meta).then(() => {
getDownloadURL(storageRef).then(async (url) => {
try {
// Update profile
await updateProfile(response.user, {
avatar: url,
});
// Store user
await setDoc(doc(db, "users", response.user.uid), {
uid: response.user.uid,
firstName: formData.firstName,
lastName: formData.lastName,
email: formData.email,
avatar: userAvatar,
});
} catch (error) {
setError(true);
console.info(error)
}
})
})
С другой стороны, похоже, что в Firebase загружается только строка пути к файлу, а не весь файл.
Исправленный:
<input
type = "file"
id = "avatar"
name = "avatar"
onChange = {handleChangeEvent}
className = "form-control form-control-sm"
/>;
const handleChangeEvent = (event) => {
const { name, value, files } = event.target;
setFormData((prevFormData) => {
const newValue = files?.length > 0 ? files[0] : value;
const newFormData = { ...prevFormData, [name]: newValue };
let userAvatar = formData.avatar
? md5(`${formData.email}-${date}`) + '.' + formData.avatar.name.split(".")[1]
: "default-avatar.png";
Пожалуйста, попробуйте ответить и на этот вопрос. Спасибо!
Отредактировал ответ. Взгляни.