Bradley Juma
Bradley Juma

Reputation: 130

Problem with using setTimeout function together with an arrow function that sets a particular state

I am trying to replicate how when you hover over a particular movie tile on Netflix, after sometime the movie tile expands to display more information.

Currently I have succeeded on expanding the tile to display more information on hover but setting a timeout such that the information is only displayed after the mouse has hovered for over 1000ms is proving to produce unwanted results.

So far i have tried this but the problem is that when i hover the state is changed for all other movie tiles instead of only the one that was hovered

function RowPoster({ movie, isTrending }) {
  const [isHovered, setisHovered] = useState(false);
  const trailerUrl = movie.trailer_url;

  return (
    <div
      className={`RowPoster ${isTrending && "isTrending"}`}
      onMouseEnter={setTimeout(() => setisHovered(true), 1000)}
      onMouseLeave={() => setisHovered(false)}
    >
      <img src={movie.cover_image} alt={movie.titles.en} />

      {isHovered && (
        <>
          {
            <ReactPlayer
              className="video"
              width="100%"
              height="160px"
              loop={true}
              playing={false}
              url={trailerUrl}
            />
          }

          <div className="item__info">
            <h4>{movie.titles.en}</h4>

            <div className="icons">
              <PlayArrow className="icon" />
              <Add />
              <ThumbUpAltOutlined className="icon" />
              <ThumbDownOutlined className="icon" />
              <KeyboardArrowDown className="icon" />
            </div>

            <div className="stats">
              <span className="stats_score">{`Score ${movie.score}%`}</span>
            </div>

            <div className="genre">
              <ul className="genre_items">
                <li>{movie.genres[0]}</li>
                <li>{movie.genres[1]}</li>
                <li>{movie.genres[2]}</li>
                <li>{movie.genres[3]}</li>
              </ul>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

export default RowPoster;

Upvotes: 0

Views: 341

Answers (1)

Denwakeup
Denwakeup

Reputation: 266

In the code you provided, a timeout is called when rendering. For this reason, the state changes for each movie tiles that is rendered. To call setTimeout when an event is fired, you need to wrap it in a function:

...
onMouseEnter={() => setTimeout(() => setisHovered(true), 1000)}
...

For behavior "display information after the mouse has hovered for more than 1000 ms", you will need a little more code:

function RowPoster({ movie, isTrending }) {
  const [isHovered, setisHovered] = useState(false);
  const trailerUrl = movie.trailer_url;

  const hoverTimerRef = useRef();

  const handleCancelHover = useCallback(() => {
    if (hoverTimerRef.current) {
      clearTimeout(hoverTimerRef.current);
    }
  }, []);

  const handleMouseEnter = useCallback(() => {
    // save the timer id in the hoverTimerRef
    hoverTimerRef.current = setTimeout(() => setisHovered(true), 1000);
  }, []);

  const handleMouseLeave = useCallback(() => {
    // cancel the scheduled hover if the mouseLeave event is fired before the timer is triggered
    handleCancelHover();

    setisHovered(false);
  }, [handleCancelHover]);

  useEffect(() => {
    return () => {
      // cancel the scheduled hover when unmounting the component
      handleCancelHover();
    };
  }, [handleCancelHover]);

  return (
    <div
      className={`RowPoster ${isTrending && "isTrending"}`}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
    >
...

Upvotes: 1

Related Questions