I am making a chat app with React 18 and Firebase 9.
In the Register form, I have an input of type file, for uploading user's avatar.
The avatar
form field is optional and there is a default-avatar.png
file that I uploaded manually in Storage and is intended for as default user image.
In Register.jsx
I have:
import React, { useState } from "react";
import md5 from "md5";
import FormCard from "../components/FormCard/FormCard";
import Success from "../components/Alerts/Success";
import Error from "../components/Alerts/Error";
import { make, register } 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";
// Custom validation rule
register('image-only', function (value) {
let allowedFormats = ["jpg", "jpeg", "png"];
let format = value.name.split('.')[1].toLowerCase();
return allowedFormats.includes(format);
}, function() {
return "Only JPG, JPEG, and PNG files are allowed.";
});
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: ["image-only"],
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 handleChange = (event) => {
const { name, value, files } = event.target;
setFormData((prevFormData) => {
const newValue = files?.length > 0 ? files[0] : value;
const newFormData = { ...prevFormData, [name]: newValue };
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 meta = { contentType: 'image/jpeg, image/png' };
const date = new Date().getTime();
let userAvatar = formData.avatar
? md5(`${formData.email}-${date}`) + '.' + formData.avatar.name.split(".")[1]
: "default-avatar.png";
const storageRef = ref(storage, userAvatar);
// Upload image
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.log(error)
}
})
})
} catch (error) {
setError(true);
errContainer.append(error.message);
errContainer.classList.remove('d-none');
}
}
setPristineFields([]);
};
return (
<FormCard title="Register">
{/* Alerts */}
<Error dismissible={true} error={error} />
<Success dismissible={true} message="You have registered successfully. You can sign in!" />
<form onSubmit={handleSubmit}>
<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={handleChange}
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"
onChange={handleChange}
className="form-control form-control-sm"
/>
{errors.has("avatar") ? (
<p className="invalid-feedback">{errors.first("avatar")}</p>
) : null}
</div>
<div className="pt-1">
<button type="submit" className="btn btn-sm btn-success fw-bold">
Submit
</button>
</div>
</form>
</FormCard>
);
}
The problem
If the user does not upload an avatar, the image I uploaded manually in Storage (default-avatar.png
) is overwritten by an empty image.
Questions
- What am I doing wrong?
- What is the most reliable way to fix the issue?
from How can I prevent the overwriting of an implicit user image in this Firebase 9 app?
No comments:
Post a Comment