Media uploads on firebase
11:09 17 Mar 2026

Can I please get some assistance?

In my component, I am trying to upload/edit media. When I submit the form, in Firestore, it sets the previous media to null and just updates the new media. See snippets below, the handleRemoveMedia and the handleAddMedia are in the component, and the handleEditMedia is in my hook

 function handleRemoveMedia(type, index) {
    const currentMedia = getValues('media') || {};
    if (type === 'galleryPhotos') {
      const currentPhotos = currentMedia.galleryPhotos || [];
      const newPhotos = [...currentPhotos];
      newPhotos.splice(index, 1);
      setValue('media', { ...currentMedia, galleryPhotos: newPhotos });
    }
    if (type === 'additionalVideos') {
      const currentVideos = currentMedia.additionalVideos || [];
      const newVideos = [...currentVideos];
      newVideos.splice(index, 1);
      setValue('media', { ...currentMedia, additionalVideos: newVideos });
    }
    if (type === 'coverMedia') {
      setValue('media', { ...currentMedia, coverMedia: null });
    }
  }

  function handleAddMedia(type, event) {
    let files = [];
    if (event?.target?.files) {
      files = Array.from(event.target.files);
    } else if (Array.isArray(event)) {
      files = event;
    } else {
      files = [event];
    }
    const currentMedia = getValues('media') || {};

    if (type === 'coverMedia') {
      const file = files[0];
      if (!file) return;
      const maxSize = 50 * 1024 * 1024;
      if (file.size > maxSize) {
        alert('Cover media file must not exceed 50MB.');
        return;
      }
      const previewUrl = URL.createObjectURL(file);
      const coverMediaObj = {
        file,
        previewUrl,
        type: file.type.startsWith('image/') ? 'image' : 'video',
      };
      setValue('media', {
        ...currentMedia,
        coverMedia: coverMediaObj,
      });
    }

    if (type === 'galleryPhotos') {
      const validFiles = files.filter(
        (file) => file.type && ['image/jpeg', 'image/png', 'image/jpg'].includes(file.type)
      );
      const currentPhotos = currentMedia.galleryPhotos || [];
      if (validFiles.length + currentPhotos.length > 6) {
        alert('You can only upload a maximum of 6 images.');
        return;
      }
      const newPhotos = validFiles.map((file) => {
        const previewUrl = URL.createObjectURL(file);
        return { file, previewUrl };
      });
      setValue('media', {
        ...currentMedia,
        galleryPhotos: [...currentPhotos, ...newPhotos],
      });
    }

    if (type === 'additionalVideos') {
      const maxSize = 50 * 1024 * 1024;
      const validFiles = files.filter((file) => file.type && file.type.startsWith('video/'));
      const oversized = validFiles.find((file) => file.size > maxSize);
      if (oversized) {
        alert('Each video file must not exceed 50MB.');
        return;
      }
      const currentVideos = currentMedia.additionalVideos || [];
      if (validFiles.length + currentVideos.length > 6) {
        alert('You can only upload a maximum of 6 videos.');
        return;
      }
      const newVideos = validFiles.map((file) => {
        const previewUrl = URL.createObjectURL(file);
        return { file, previewUrl };
      });
      setValue('media', {
        ...currentMedia,
        additionalVideos: [...currentVideos, ...newVideos],
      });
    }
  }

// Here is the JSX: 

Media
          
            Cover Media
            {!watch('media')?.coverMedia && (
              
            )}
            {watch('media')?.coverMedia && (
              
                {watch('media').coverMedia?.type === 'image' ? (
                  Cover
                ) : (
                  
                )}
                 handleRemoveMedia('coverMedia')}
                >
                  
                
              
            )}

            Gallery Photos
            {(watch('media')?.galleryPhotos?.length ?? 0) < 6 && (
              
            )}
            
              {watch('media')?.galleryPhotos?.length > 0 &&
                watch('media').galleryPhotos.map((photo, index) => (
                  
                    {`Gallery
                     handleRemoveMedia('galleryPhotos', index)}
                    >
                      
                    
                  
                ))}
            

            Additional Videos
            {(watch('media')?.additionalVideos?.length ?? 0) < 6 && (
              
            )}
            
              {watch('media')?.additionalVideos?.length > 0 &&
                watch('media').additionalVideos.map((video, index) => (
                  
                    
                      
                    
                     handleRemoveMedia('additionalVideos', index)}
                    >
                      
                    
                  
                ))}
            
          


// handleEditMedia in hook: 
const handleEditListing = async (listing, formData) => {
    try {
      const coverMediaToDelete =
        listing.media.coverMedia && formData.media.coverMedia?.url !== listing.media.coverMedia.url
          ? listing.media.coverMedia
          : null;

      const galleryImagesToDelete = [];

      for (let i = 0; i < listing.media.galleryPhotos.length; i += 1) {
        const oldImage = listing.media.galleryPhotos[i];
        let imageFound = false;

        for (let j = 0; j < formData.media.galleryPhotos.length; j += 1) {
          const newImg = formData.media.galleryPhotos[j];
          if (newImg.url === oldImage.url) {
            imageFound = true;
            break;
          }
        }
        if (!imageFound) {
          galleryImagesToDelete.push(oldImage);
        }
      }

      const galleryVideosToDelete = [];

      for (let i = 0; i < listing.media.additionalVideos.length; i += 1) {
        const oldVideo = listing.media.additionalVideos[i];
        let videoFound = false;

        for (let j = 0; j < formData.media.additionalVideos.length; j += 1) {
          const newVid = formData.media.additionalVideos[j];
          if (newVid.url === oldVideo.url) {
            videoFound = true;
            break;
          }
        }
        if (!videoFound) {
          galleryVideosToDelete.push(oldVideo);
        }
      }

      if (coverMediaToDelete) {
        await deleteCoverMedia(listing.id, coverMediaToDelete.name);
      }

      if (galleryImagesToDelete.length > 0) {
        await Promise.all(
          galleryImagesToDelete.map(async (url) => {
            try {
              await deleteGalleryMedia(listing.id, url);
            } catch (error) {
              console.error('Error deleting gallery image:', error);
            }
          })
        );
      }

      if (galleryVideosToDelete.length > 0) {
        await Promise.all(
          galleryVideosToDelete.map(async (url) => {
            try {
              await deleteVideoMedia(listing.id, url);
            } catch (error) {
              console.error('Error deleting gallery video:', error);
            }
          })
        );
      }

      const galleryImageUrls = await Promise.all(
        formData.media.galleryPhotos.map(async (photo) => {
          try {
            if (photo.file instanceof File) {
              return await uploadGalleryMedia(listing.id, photo.file);
            }
            if (photo.url) {
              return photo; 
            }
            return null;
          } catch (error) {
            console.error('Error uploading gallery image:', error);
            return null;
          }
        })
      );

      const galleryVideoUrls = await Promise.all(
        formData.media.additionalVideos.map(async (video) => {
          try {
            if (video.file instanceof File) {
              return await uploadVideoMedia(listing.id, video.file);
            }
            if (video.url) {
              return video; 
            }
            return null;
          } catch (error) {
            console.error('Error uploading gallery video:', error);
            return null;
          }
        })
      );

      let coverMediaUrl;
      if (formData.media.coverMedia?.file instanceof File) {
        coverMediaUrl = await uploadCoverMedia(listing.id, formData.media.coverMedia.file);
      } else if (formData.media.coverMedia?.url) {
        coverMediaUrl = formData.media.coverMedia; 
      } else {
        coverMediaUrl = formData.media.coverMedia || null;
      }

      const listingData = {
        ...listing,
        basicDetails: {
          ...listing.basicDetails,
          experienceName: formData.experienceName,
          category: formData.category,
          secondaryCategory: formData.secondaryCategory,
          location: formData.location,
          shortDescription: formData.shortDescription,
        },
        experienceDetails: {
          ...listing.experienceDetails,
          whatGuestsWillDo: formData.whatGuestsWillDo,
          whatMakesItSpecial: formData.whatMakesItSpecial,
          whoThisIsFor: formData.whoThisIsFor,
          privateBooking: formData.privateBooking,
          minimumSeatThreshold: formData.minimumSeatThreshold,
          maximumGroupSize: formData.maximumGroupSize,
          perPersonPrice: formData.perPersonPrice,
          duration: {
            type: formData.durationType,
            hours: formData.durationHours,
            minutes: formData.durationMinutes,
          },
          whatsIncluded: formData.whatsIncluded,
          whatsNotIncluded: formData.whatsNotIncluded,
          languagesOffered: formData.languagesOffered,
          guestRequirements: formData.guestRequirements,
          whatToBring: formData.whatToBring,
          specialNotes: formData.specialNotes,
        },
        schedule: {
          ...listing.schedule,
          sameScheduleEveryday: formData.sameScheduleEveryday,
          operatingDays: formData.operatingDays,
          timeSlots: formData.timeSlots,
          maxBookingsPerSlot: formData.maxBookingsPerSlot,
        },
        pricings: {
          ...listing.pricings,
          currency: formData.currency,
          basePrice: formData.basePrice,
          additionalFees: formData.additionalFees,
          offerDiscount: formData.offerDiscount,
          discountPercentage: formData.discountPercentage,
          minGuests: formData.minGuests,
          maxGuests: formData.maxGuests,
          cancellationPolicy: formData.cancellationPolicy,
        },
        media: {
          ...listing.media,
          galleryPhotos: galleryImageUrls,
          additionalVideos: galleryVideoUrls,
          coverMedia: coverMediaUrl,
        },
        status: listingStatuses.PENDING,
        verification: {
          ...listing.verification,
          safetySecurity: {
            ...listing.verification.safetySecurity,
            securityPresence: formData.securityPresence,
            fireSafetyCompliance: formData.fireSafetyCompliance,
            licensedGuide: formData.licensedGuide,
            maxTravellersPerGuide: formData.maxTravellersPerGuide,
            safetyEquipmentProvided: formData.safetyEquipmentProvided,
          },
          healthEmergency: {
            ...listing.verification.healthEmergency,
            firstAidStaff: formData.firstAidStaff,
            emergencyContactNumber: formData.emergencyContactNumber,
            healthProtocols: formData.healthProtocols,
          },
          connectivity: {
            ...listing.verification.connectivity,
            mobileSignal: formData.mobileSignal,
            wifiAvailable: formData.wifiAvailable,
            gpsTracking: formData.gpsTracking,
          },
          continuityAccess: {
            ...listing.verification.continuityAccess,
            backupVehicle: formData.backupVehicle,
            roadAccessType: formData.roadAccessType,
            backupPower: formData.backupPower,
          },
          sustainabilityHygiene: {
            ...listing.verification.sustainabilityHygiene,
            wttcSafeTravelBadge: formData.wttcSafeTravelBadge,
            wttcBusinessName: formData.wttcBusinessName,
            gstcEcoCertified: formData.gstcEcoCertified,
            gstcBusinessName: formData.gstcBusinessName,
            hasCommunityPartnership: formData.hasCommunityPartnership,
            communityPartnerships: formData.communityPartnerships,
          },
          inclusion: {
            ...listing.verification.inclusion,
            minimumAgeAccepted: formData.minimumAgeAccepted,
            womenSafetyMeasures: formData.womenSafetyMeasures,
            wheelchairAccessible: formData.wheelchairAccessible,
            mobilityAssistanceAvailable: formData.mobilityAssistanceAvailable,
            visualHearingSupport: formData.visualHearingSupport,
          },
        },
      };

      await updateListing(listing.id, listingData);
      showSuccess('Listing updated successfully!');
    } catch (error) {
      showError('Failed to update listing!');
    }
  };
javascript reactjs firebase