Friday 29 September 2023

How can I prevent the overwriting of an implicit user image in this Firebase 9 app?

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

  1. What am I doing wrong?
  2. 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