简体   繁体   中英

ReactJS : eventListener tranistionend not getting cleaned up in useEffect

I have an image carousel component which has a smooth transition between images using the eventListener transtionend .

This event listener even though I have a cleanup function in place it creates a memory leak. When I leave the page that has the image carousel the error does not appear yet. However, if I return to the page with the carousel and the transition completes one cycle (the image changes) then I get the error in the console.

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

I attached my code below:

/** @jsx jsx */
import { useState, useEffect, useRef } from "react";
import { css, jsx } from "@emotion/core";

import SliderContent from "./SliderContent";
import Slide from "./Slide";
import Arrow from "./Arrow";
import Dots from "./Dots";

export default function Slider({ autoPlay }) {
  const getWidth = () => window.innerWidth * 0.8;

  const slides = [
    "https://images.unsplash.com/photo-1449034446853-66c86144b0ad?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2100&q=80",
    "https://images.unsplash.com/photo-1470341223622-1019832be824?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2288&q=80",
    "https://images.unsplash.com/photo-1448630360428-65456885c650?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2094&q=80",
    "https://images.unsplash.com/photo-1534161308652-fdfcf10f62c4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2174&q=80",
  ];

  const firstSlide = slides[0];
  const secondSlide = slides[1];
  const lastSlide = slides[slides.length - 1];

  const [isTabFocused, setIsTabFocused] = useState(true);
  const [isButtonDisabled, setIsButtonDisabled] = useState(false);

  const [state, setState] = useState({
    translate: 0,
    transition: 0.9,
    activeSlide: 0,
    _slides: [firstSlide, secondSlide, lastSlide],
  });

  const { activeSlide, translate, _slides, transition } = state;

  const autoPlayRef = useRef();
  const transitionRef = useRef();
  const resizeRef = useRef();
  const focusedTabRef = useRef();
  const blurredTabRef = useRef();

  useEffect(() => {
    //eslint-disable-next-line react-hooks/exhaustive-deps
    if (transition === 0) setState({ ...state, transition: 0.9 });
  }, [transition]);

  useEffect(() => {
    transitionRef.current = smoothTransition;
    resizeRef.current = handleResize;
    focusedTabRef.current = handleFocus;
    blurredTabRef.current = handleBlur;
    autoPlayRef.current = handleAutoPlay;
  });

  useEffect(() => {
    const play = () => autoPlayRef.current();

    let interval = null;

    if (autoPlay) {
      interval = setInterval(play, autoPlay * 1000);
    }

    return () => {
      if (autoPlay) {
        clearInterval(interval);
      }
    };
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isButtonDisabled, autoPlay]);

  useEffect(() => {
    const smooth = (e) => {
      if (typeof e.target.className === "string" || e.target.className instanceof String) {
        if (e.target.className.includes("SliderContent")) {
          transitionRef.current();
        }
      }
    };
    const resize = () => resizeRef.current();
    const onFocusAction = () => focusedTabRef.current();
    const onBlurAction = () => blurredTabRef.current();

    const transitionEnd = window.addEventListener("transitionend", smooth);
    const onResize = window.addEventListener("resize", resize);
    const onFocus = window.addEventListener("focus", onFocusAction);
    const onBlur = window.addEventListener("blur", onBlurAction);

    return () => {
      window.removeEventListener("resize", onResize);
      window.removeEventListener("focus", onFocus);
      window.removeEventListener("blur", onBlur);
      window.removeEventListener("transitionend", transitionEnd);
    };
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    if (isButtonDisabled) {
      const buttonTimeout = setTimeout(() => {
        setIsButtonDisabled(false);
      }, 1000);

      return () => clearTimeout(buttonTimeout);
    }
    //eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isButtonDisabled]);

  const handleFocus = () => setIsTabFocused(true);
  const handleBlur = () => setIsTabFocused(false);
  const handleAutoPlay = () => isTabFocused && nextSlide();
  const handleResize = () => setState({ ...state, translate: getWidth(), transition: 0 });

  const nextSlide = () => {
    if (!isButtonDisabled) {
      setState({
        ...state,
        translate: translate + getWidth(),
        activeSlide: activeSlide === slides.length - 1 ? 0 : activeSlide + 1,
      });
    }

    setIsButtonDisabled(true);
  };

  const prevSlide = () => {
    if (!isButtonDisabled) {
      setState({
        ...state,
        translate: 0,
        activeSlide: activeSlide === 0 ? slides.length - 1 : activeSlide - 1,
      });
    }

    setIsButtonDisabled(true);
  };

  const smoothTransition = () => {
    let _slides = [];

    // We're at the last slide.
    if (activeSlide === slides.length - 1)
      _slides = [slides[slides.length - 2], lastSlide, firstSlide];
    // We're back at the first slide. Just reset to how it was on initial render
    else if (activeSlide === 0) _slides = [lastSlide, firstSlide, secondSlide];
    // Create an array of the previous last slide, and the next two slides that follow it.
    else _slides = slides.slice(activeSlide - 1, activeSlide + 2);

    setState({
      ...state,
      _slides,
      transition: 0,
      translate: getWidth(),
    });
  };

  return (
    <div css={SliderCSS}>
      <SliderContent
        translate={translate}
        transition={transition}
        width={getWidth() * _slides.length}
      >
        {_slides.map((slide, i) => (
          <Slide width={getWidth()} key={slide + i} content={slide} />
        ))}
      </SliderContent>

      <Arrow direction="left" handleClick={prevSlide} isDisabled={isButtonDisabled} />
      <Arrow direction="right" handleClick={nextSlide} isDisabled={isButtonDisabled} />

      <Dots slides={slides} activeIndex={activeSlide} />
    </div>
  );
}

const SliderCSS = css`
  position: relative;
  height: 600px;
  width: 80%;
  margin: 40px auto 0px auto;
  overflow: hidden;
`;

The window listener is getting removed at the end of the useEffect but I don't know why it still creates the memory leak.

Hmm. It seems you're removing event listeners incorrectly. DOM addEventListener returns nothing (undefined).

Wrong:

const onResize = window.addEventListener("resize", resize);
window.removeEventListener("resize", onResize);

Should be:

window.addEventListener("resize", resize);
window.removeEventListener("resize", resize);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM