React hooks hover effect
Asked Answered
S

3

7

I am trying to make hover effect with react hooks I wrote function to hover based on some tutorials

function useHover() {
  const [hovered, setHovered] = useState(false);

  const ref = useRef(null);

  const handleMouseOver = () => setHovered(true);
  const handleMouseOut = () => setHovered(false);

  useEffect(() => {
    const node = ref.current;
    if (node) {
      node.addEventListener("mouseover", handleMouseOver);
      node.addEventListener("mouseout", handleMouseOut);

      return () => {
        node.removeEventListener("mouseover", handleMouseOver);
        node.removeEventListener("mouseout", handleMouseOut);
      };
    }
  }, [ref]);

  return [ref, hovered];
}

but how to make it work in my App function

export default function App() {
  const [ref, isHovered] = useHover();

  const reactionItems = myObject.map(([key, value]) => (
    <li key={key} ref={ref}>
      {isHovered ? `${key} ${value.length > 1 ? "x " + value.length : ""}` : `${key} ${value.length > 1 ? "x " + value.length : ""} ${value}`}
    </li>
  ));


return (
    <div className="App">
      <h1>{string}</h1>
      <h2>Reactions</h2>
      <ul>{reactionItems}</ul>
    </div>
  );

} 

I can see it only in state false so second option and no hover effect

Scrip answered 2/5, 2020 at 16:24 Comment(0)
B
9

Use React's events' system, and not the DOM's. In addition, each item should have it's own event handlers, and state.

Create a hook that returns the hovered state, and the events' listeners of an item. Create an Item component, and use the hook in it's definition. Render the items.

const { useState, useMemo } = React;

const useHover = () => {
  const [hovered, setHovered] = useState();
  
  const eventHandlers = useMemo(() => ({
    onMouseOver() { setHovered(true); },
    onMouseOut() { setHovered(false); }
  }), []);
  
  return [hovered, eventHandlers];
}

const Item = ({ children }) => {
  const [hovered, eventHandlers] = useHover();

  return (
    <li {...eventHandlers}>Item: {hovered && children}</li>
  );
};

const myObject = {
  a: 'A1',
  b: 'B2',
  c: 'C3',
}

function App() {
  const reactionItems = Object.entries(myObject)
    .map(([key, value]) => (
      <Item key={key}>{value}</Item>
    ));

  return (
    <div className="App">
      <h2>Reactions</h2>
      <ul>{reactionItems}</ul>
    </div>
  );
}

ReactDOM.render(<App />, root);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
Borodino answered 2/5, 2020 at 16:38 Comment(3)
thank you yes it is working! but what if instead of adding hovered string I need to use values from the object inside the app function is it possible or I have to have object in global scope?Scrip
I've updated the answer to show the value on hover.Borodino
Sure. You can replace children, and pass whatever properties you want, and change the Item to whatever works for you.Borodino
C
0

A way to do this is to use React's events, and just make sure you let it be more generic.

One of the issues you were running into is that ref can only refer to a single node at a time. And ref never changes dependencies, so the useEffect only ever ran once.

const { useState, useRef, useEffect, useCallback } = React;

function useHover() {
  const [hovered, setHovered] = useState({});

  const mouseOver = useCallback((event) => {
    const target = event.target;
    const key = target.getAttribute('data-key');
    setHovered((curState) => ({ ...curState, [key]: true }));
  }, []);

  const mouseOut = useCallback((event) => {
    const target = event.target;
    const key = target.getAttribute('data-key');
    setHovered((curState) => ({ ...curState, [key]: false }));
  }, []);

  return { mouseOver, mouseOut, hovered };
}

const object = { key1: 'test', key2: 'test2', key3: 'test3' };
const myObject = Object.entries(object);
const string = 'Header';
function App() {
  const { mouseOver, mouseOut, hovered } = useHover();

  const reactionItems = myObject.map(([key, value]) => (
    <li key={key} data-key={key} onMouseOver={mouseOver} onMouseOut={mouseOut}>
      {hovered[key]
        ? `${key} ${value.length > 1 ? 'x ' + value.length : ''}`
        : `${key} ${value.length > 1 ? 'x ' + value.length : ''} ${value}`}
    </li>
  ));

  return (
    <div className="App">
      <h1>{string}</h1>
      <h2>Reactions</h2>
      <ul>{reactionItems}</ul>
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>

You can also separate the list items out to their own component which would enable you to work with the useHover more closely to how you had it.

Campney answered 2/5, 2020 at 16:42 Comment(0)
E
0

You was near to the solution: the hook must get the 'ref' in input from your 'App' component and return the state 'hovered'

The useHover hook:

export function useHover(ref: React.RefObject<HTMLElement>): { hovered: boolean } {
const [hovered, setHovered] = useState(false);

const handleMouseOver = () => setHovered(true);
const handleMouseOut = () => setHovered(false);

useEffect(() => {
    const node = ref.current;
    if (node) {
        node.addEventListener('mouseover', handleMouseOver);
        node.addEventListener('mouseout', handleMouseOut);

        return () => {
            node.removeEventListener('mouseover', handleMouseOver);
            node.removeEventListener('mouseout', handleMouseOut);
        };
    }
}, [ref]);

return { hovered };

}

In your app:

const divRef = useRef<HTMLElement>(null);
const { hovered } = useHover(divRef);
useEffect(() => {
    if (divRef.current) {
        console.log('hovered', hovered);
    }
}, [hovered]);



<div ref={divRef} style={{opacity: hovered ? 1: 0.5 }}> .... </div>
Electroform answered 14/9 at 16:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.