Search code examples
reactjsmern

Cannot set properties of null (setting 'src')


I have a MERN social media app that allows users to upload profile and cover pictures on their profile pages. Everything works but when the user uploads the image, the cover image being displayed is supposed to be set to the image that was just uploaded. I got this code from a tutorial and it works perfectly, but I have to refresh the page to get the new image to show. I commented out the setError(error.response.data.error) line and console.logged the error and I get this:

TypeError: Cannot set properties of null (setting 'src')
    at updateCoverPicture (Cover.js:103:1)

Line 103 is in the updateCoverPicture function and it is:

coverRef.current.src = res[0].url;

And the image I am trying to reference is:

{cover && !coverPicture && (
   <img src={cover} className="cover" alt="" ref={coverRef} />
)}

The ideas is that if I am not in the process of uploading a new image, the current cover image will be displayed but if there is a coverPicture (the one being uploaded) it is displayed there instead. When the function runs the image is uploaded with the cloudinary url being saved to the user's record in the DB, and a post is created and then coverPIcture is supposed be be set to "" and then the line above is run to display the newly uploaded image.

Everything works except I get that error instead of the image being displayed.

If I remove !coverPicture && from the line {cover && !coverPicture && ( it works but the current image is pushed down and the image I'm trying to upload displays above it. Once I save the coverPicture goes away and the image is updated but it doesn't look good having them both there.

Here is the whole page:

import { useCallback, useEffect, useRef, useState } from 'react';
import Cropper from 'react-easy-crop';
import getCroppedImg from '../../helpers/getCroppedImg';
import { uploadImages } from '../../functions/uploadImages';
import { updateCover } from '../../functions/user';
import { createPost } from '../../functions/post';
import PulseLoader from 'react-spinners/PulseLoader';

const Cover = ({ cover, visitor, user }) => {
  const [showCoverMenu, setShowCoverMenu] = useState(false);
  const [coverPicture, setCoverPicture] = useState('');
  const [loading, setLoading] = useState('');
  const [error, setError] = useState('');
  const [crop, setCrop] = useState({ x: 0, y: 0 });
  const [zoom, setZoom] = useState(1);
  const [croppedAreaPixels, seetCroppedAreaPixels] = useState(null);
  const [width, setWidth] = useState();
  const refInput = useRef(null);
  const menuRef = useRef(null);
  const coverWidthRef = useRef(null);
  const coverRef = useRef(null);

  const onCropComplete = useCallback(
    (croppedArea, croppedAreaPixels, picModalOpen) => {
      seetCroppedAreaPixels(croppedAreaPixels);
    },
    []
  );

  const handleImage = (e) => {
    let file = e.target.files[0];
    if (
      file.type !== 'image/jpeg' &&
      file.type !== 'image/jpg' &&
      file.type !== 'image/png' &&
      file.type !== 'image/webp' &&
      file.type !== 'image/gif'
    ) {
      setError(`${file.name} format is not supported.`);
      return;
    } else if (file.size > 1024 * 1024 * 5) {
      setError(`${file.name} is too large max 5mb allowed.`);
      return;
    }

    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = (event) => {
      setCoverPicture(event.target.result);
    };
  };

  const getCroppedImage = useCallback(
    async (show) => {
      try {
        const img = await getCroppedImg(coverPicture, croppedAreaPixels);
        if (show) {
          setZoom(1);
          setCrop({ x: 0, y: 0 });
          setCoverPicture(img);
        } else {
          return img;
        }
      } catch (error) {
        console.log(error);
      }
    },
    [croppedAreaPixels, coverPicture, setCoverPicture]
  );

  useEffect(() => {
    setWidth(coverWidthRef.current.clientWidth);
  }, [window.innerWidth]);

  const updateCoverPicture = async () => {
    try {
      setLoading(true);
      let img = await getCroppedImage();
      let blob = await fetch(img).then((b) => b.blob());
      const path = `tyt/${user.user.username}/cover_pictures`;
      let formData = new FormData();
      formData.append('file', blob);
      formData.append('path', path);
      const res = await uploadImages(formData, path, user.token);

      const updated_Picture = await updateCover(
        res[0].url,
        user.user._id,
        user.token
      );

      if (updated_Picture === 'ok') {
        const new_post = await createPost(
          'cover',
          null,
          res,
          user.user._id,
          user.token
        );

        if (new_post === 'ok') {
          setLoading(false);
          setCoverPicture('');
          coverRef.current.src = res[0].url;
        } else {
          setLoading(false);
          setError(new_post);
        }
      } else {
        setLoading(false);
        setError(updated_Picture);
      }
    } catch (error) {
      setLoading(false);
      //setError(error.response.data.error);
      console.log(error);
    }
  };
  return (
    <div className="profile_cover" ref={coverWidthRef}>
      {coverPicture && (
        <div className="save_changes_cover">
          <div className="save_changes_left">
            <i className="public_icon"></i>
            Your cover photo is public
          </div>
          <div className="save_changes_right">
            <button
              className="blue_btn opacity_btn"
              onClick={() => setCoverPicture('')}
            >
              Cancel
            </button>
            <button className="blue_btn" onClick={() => updateCoverPicture()}>
              {loading ? <PulseLoader color="#fff" size={5} /> : 'Save Changes'}
            </button>
          </div>
        </div>
      )}
      <input
        type="file"
        ref={refInput}
        hidden
        accept="image/jpg,image/jpeg,image/png,image/webp,image/gif"
        onChange={handleImage}
      />
      {error && (
        <div className="postError comment_error">
          <div className="postError_error">{error}</div>
          <button className="blue_btn" onClick={() => setError('')}>
            Try again
          </button>
        </div>
      )}
      {coverPicture && (
        <div className="cover_cropper">
          <Cropper
            image={coverPicture}
            crop={crop}
            zoom={zoom}
            aspect={width / 350}
            onCropChange={setCrop}
            onCropComplete={onCropComplete}
            onZoomChange={setZoom}
            showGrid={true}
            objectFit="horizontal-cover"
          />
        </div>
      )}
      {cover && !coverPicture && (
        <img src={cover} className="cover" alt="" ref={coverRef} />
      )}
      {!visitor && (
        <div className="update_cover_wrapper">
          <div
            className="open_cover_update"
            onClick={() => setShowCoverMenu((prev) => !prev)}
          >
            <i className="camera_filled_icon"></i>
            Add cover photo
          </div>
          {showCoverMenu && (
            <div className="open_cover_menu" ref={menuRef}>
              <div className="open_cover_menu_item hover1">
                <i className="photo_icon"></i>
                Select Photo
              </div>
              <div
                className="open_cover_menu_item hover1"
                onClick={() => refInput.current.click()}
              >
                <i className="upload_icon"></i>
                Upload Photo
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
};

export default Cover;

Solution

  • After search for several days I posted this question then of course found the solution a day later. The solution is from the anwser in this question How to use Refs to change image src in React js

    Rather than useRef to reset the cover image I used useEffect and useState. I set the cover to the imported cover it it exists using useState:

    const [image, setImage] = useState(cover ? cover : '');
    

    Then I changed

    cRef.current.src = res[0].url;
    

    to

    setImage(res[0].url);
    

    Then added a useEffect to set the image to the newly uploaded image

    useEffect(() => {
        if (loading) {
          setTimeout(() => {
            if (loading) {
              setLoading(false);
              setImage(cover);
            }
          }, 100);
        }
      }, [loading, setLoading, setImage, cover]);
    

    Then I change:

    <img src={cover} className="cover" alt="" ref={coverRef} />
    

    to

    <img src={image} className="cover" alt="" ref={coverRef} />