skip to Main Content

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?

2

Answers


  1. You are still attempting to upload and overwrite the default-avatar.png file in storage with an empty file

    let userAvatar = formData.avatar
      ? md5(`${formData.email}-${date}`) + '.' + formData.avatar.name.split(".")[1]
      : "default-avatar.png";
    
    Login or Signup to reply.
  2. 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 set userAvatar to the default value when no avatar is provided.

    Your code:

    let userAvatar = formData.avatar ? md5(`${formData.email}-${date}`) + '.' + formData.avatar.name.split(".")[1]
      : "default-avatar.png";

    Solution Code:

    let userAvatar = formData.avatar ? md5(`${formData.email}-${date}`) + '.' + formData.avatar.name.split(".")[1] : formData.avatar || "default-avatar.png";

    Explanation:

    If formData.avatar is provided, a unique avatar name based on email and date is generated.
    If no avatar is uploaded (formData.avatar is falsy), it checks for formData.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

    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');
    
          let userAvatar = "default-avatar.png"; // Default avatar image
    
          // Check if the user uploaded an avatar
          if (formData.avatar) {
            // Create avatar filename
            const meta = { contentType: 'image/jpeg, image/png' };
            const date = new Date().getTime();
            userAvatar = md5(`${formData.email}-${date}`) + '.' + formData.avatar.name.split(".")[1];
    
            const storageRef = ref(storage, userAvatar);
    
            // Upload image
            await uploadBytesResumable(storageRef, formData.avatar, meta);
          }
    
          // Get download URL (either uploaded avatar or default avatar)
          const avatarURL = await getDownloadURL(ref(storage, userAvatar));
    
          try {
            // Update profile
            await updateProfile(response.user, {
              avatar: avatarURL,
            });
    
            // 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([]);
    };
    

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search