Search code examples
reactjsuse-effect

How to cleanup useRef in useEffect?


I have this component, so I want to clean up in useEffect. I googled the issue and there is no helpful information.

const LoadableImg = ({src, alt}) => {
const [isLoaded, setIsLoaded] = useState(false);

let imageRef = useRef(null);


useEffect(() => {
    if (isLoaded) return;
    if (imageRef.current) {
        imageRef.current.onload = () => setIsLoaded(true);
    }
    return () => {
        imageRef.current = null;
    };
}, [isLoaded]);

return (
    <div className={isLoaded ? 'l_container_loaded' : 'l_container'}>
        <img ref={imageRef} className={isLoaded ? "l_image_loaded" : 'l_image'}
             src={src}
             alt={alt}/>
    </div>
) };

I can't figure out how to clean up in useEffect.

UPDATE added another useEffect, according to Arcanus answer.

const LoadableImg = ({src, alt}) => {
const [isLoaded, setIsLoaded] = useState(false);

let imageRef = useRef(null);


useEffect(() => {
    if (isLoaded) return;
    if (imageRef.current) {
        imageRef.current.onload = () => setIsLoaded(true);
    }

}, [isLoaded]);

useEffect(() => {
    return () => {
        imageRef.current = null;
    };
},[])


return (
    <div className={isLoaded ? 'l_container_loaded' : 'l_container'}>
        <img ref={imageRef} className={isLoaded ? "l_image_loaded" : 'l_image'}
             src={src}
             alt={alt}/>
    </div>
)};

Solution

  • If you want to do this with a ref, then you will need to remove the onload function, but you do not need to null out imageRef.current:

    useEffect(() => {
        if (isLoaded) return;
        const element = imageRef.current;
        if (element) {
            element.onload = () => setIsLoaded(true);
            return () => {
                element.onload = null;
            }
        }
    }, [isLoaded]);
    

    That said, i recommend you do not use a ref for this. A standard onLoad prop will work just as well, without the need for all the extra logic for adding and removing the event listener:

    const LoadableImg = ({ src, alt }) => {
      const [isLoaded, setIsLoaded] = useState(false);
    
      return (
        <div className={isLoaded ? "l_container_loaded" : "l_container"}>
          <img
            className={isLoaded ? "l_image_loaded" : "l_image"}
            src={src}
            alt={alt}
            onLoad={() => setIsLoaded(true)}
          />
        </div>
      );
    };