How to properly write a custom react hook which depends on a value?

Viewed 77

The goal of my hook is to determine the "open direction" of the menu. When there is not enough height, the open direction will switch from "bottom" to "top". I simplified my code to a dummy example to be able to demonstrate it. You can see it in the animated gif I added.

So, what is my problem? In my real code, the logic of the "open direction" depends on the last "openDirection" from the state. With classes which I'm used to, it works well, I've added the code example using classes. In the handleResize I'm writing to the console the current openDirection, to simulate the use of it in my real code.

BUT in my hooks code example, you can see that each time I am writing to the console from the handleResize, the openDirection is always the first initial value of the state which is bottom.

I guess it works this way because I'm giving the [] dependency, so useLayoutEffect works on the first time only. But what should I do? if I pass openDirection as a dependency, it will work, but then it means that each time addEventListener will be added and removeEventListener will be removed, for every resize event!

I'm really confused about it, what should I do?

import { useLayoutEffect, useState } from 'react';

const useOpenDirection = () => {
  const [openDirection, setOpenDirection] = useState("bottom");

  useLayoutEffect(() => {
    const handleResize = () => {
      console.log('openDirection', openDirection);
      if (window.innerHeight < 622) {
        setOpenDirection("top");
      } else {
        setOpenDirection("bottom");
      }
    }

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return openDirection;
};

export default useOpenDirection;

The complete code for live editing:

classes code example

hooks code example

enter image description here

1 Answers

Your openDirection has become a stale closure, because of empty array as dependencies. You can access current value of openDirection if you use functional update:

useLayoutEffect(() => {
    const handleResize = () => {

        setOpenDirection(current => {
            // If you need you can also use current to compute return value of this function
            if (window.innerHeight < 622) {
                console.log('openDirection', current);
                return "top";
            } else {
                console.log('openDirection', current);
                return "bottom";
            }

        });

    }

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
}, []);
Related