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?
2
Answers
You are still attempting to upload and overwrite the default-avatar.png file in storage with an empty file
Problem: When users do not upload an avatar during registration, the manually uploaded image (
default-avatar.png
) in Storage is overwritten by an empty image.Solution: To maintain
default-avatar.png
if no avatar is uploaded, modify the avatar assignment logic to setuserAvatar
to the default value when no avatar is provided.Your code:
Solution Code:
Explanation:
If
formData.avatar
is provided, a unique avatar name based on email and date is generated.If no avatar is uploaded (
formData.avatar
isfalsy
), it checks forformData.avatar
, and if that’s also falsy (no avatar provided), it defaults to"default-avatar.png"
to ensure the default avatar is used.This change ensures that
default-avatar.png
is used if no user avatar is uploaded.here is the perfect solution of your issue
Explanation:
In this modified code, we first set the
userAvatar
to the default avatar image. If the user uploads an avatar, we update userAvatar to the filename generated for the uploaded avatar. Then, we proceed to upload the avatar (if provided) and store the user information with the correct avatar filename.