mcclosa
mcclosa

Reputation: 1455

Update style of one Element from mouseOver on other Element React

I have a tab element where went the tab is the active tab, defined by props, there is an underline.

It is styled by the state value underlineStyle.

On initial load the state for sizes has not updated by the time it reaches getUnderlineStyle which is called afterwards?

The reason I am not doing this with CSS, is that there is a transistion that requires some calculation in getSizes() so it's got the correct left and right property values from an object sizes you can see referenced.

How can I call getUnderlineStyle() to apply correct styling after the sizes state has been updated?

Below is a snippet from my component.

const Tabs = (props) => {
   const [sizes, updateSizes] = useState({});
   const [underlineStyle, updateUnderlinStyle] = useState({});

   useEffect(() => {
      if (props.underline) {
         getSizes();
         // State for sizes does not seem to get updated here?
         getUnderlineStyle();
         window.addEventListener("resize", _.debounce(() => getSizes()));
      }
   },
      [props]
   );

   let els = {};

   const tabsRef = useRef(null);

   const transistionTime = 500;
   const transistionStyle = `left ${transistionTime}ms, right ${transistionTime}ms`;

   const getSizes = () => {
      const rootBounds = tabsRef.current.getBoundingClientRect();

      const size = {};

      Object.keys(els).forEach((key) => {
         const el = els[key];
         const bounds = el.getBoundingClientRect();
         const left = bounds.left - rootBounds.left;
         const right = rootBounds.right - bounds.right;

         size[key] = { left, right };
      });

      // This updates size state, but does not seem to carry across into getUnderlineStyle???
      updateSizes(size);

   }

   const getUnderlineStyle = (active) => {
      // the state, `size` is still set to it's default value of an empty object, hit first condition, should hit last on initial load?
      if (props.active == null || Object.keys(sizes).length === 0) {
         updateUnderlinStyle({ left: '0', right: '100%' });
      } else if (active) {
         updateUnderlinStyle({
            left: sizes[active].left,
            right: sizes[active].right,
            transition: transistionStyle
         })
      } else {
         updateUnderlinStyle({
            left: sizes[props.active].left,
            right: sizes[props.active].right,
            transition: transistionStyle
         });
      }
   }

   return (
      <div className="Tabs" ref={tabsRef}>
         {React.Children.map(props.children, (child, i) => {
            return (
               <div
                  className={(child.key === props.active ? 'Tabs-item Tabs-itemActive' : 'Tabs-item') + (i === 0 ? ' Tabs-first' : i === (props.children.length - 1) ? ' Tabs-last' : null)}
                  onClick={() => {
                     // Call onChange method in Parent Component
                     props.onChange(child.key);
                  }}
                  onMouseOver={() => getUnderlineStyle(child.key)}
                  ref={el => els[child.key] = el}
               >
                  {child}
               </div>
            )
         })}
         {props.underline ?
            <div key={"Tabs-underline"} className="Tabs-underline" style={underlineStyle}></div>
            : null
         }
      </div>
   );
};

export default Tabs;

Upvotes: 0

Views: 839

Answers (2)

SquallQL
SquallQL

Reputation: 98

You would first need to either convert your component into a Class or use hooks to implement a state in your component. Since Class are still more common than hooks, I'll go with that for this answer

You can add an event listener on the component you want to trigger an action on when there is an hover and in that function, modify the state of your component. Then you can have a conditional style/class that is applied only when that state is true. Also you'd need to make sure that your setState function is only called once to prevent having multiple unnecessary calls. Simple example:

Class Tabs extends React.Component{
constructor(props){
  super(props);

  this.state = {
    isHovered: false
  }

 // Needed to keep the reference to this
 this.onHover = this.onHover.bind(this);
 this.onHoverEnd = this.onHoverEnd.bind(this);
}

render(){
   const { isHovered } = this.state;

    // Classes
    // Ternary operator: acts like a If/else
    const hoverClass = isHovered? "yourClass" : "";

    return(
    <div id="root">
      <div onMouseEnter={this.onHover} onMouseLeave={this.onHoverEnd} />
      <div className={hoverClass} />
    </div>
  )
}

onHover(){
  const { isHovered } = this.state;

  if(!isHovered){
    this.setState({isHovered: true})
    }
  }

onHoverEnd(){
 const { isHovered } = this.state;

  if(isHovered){
    this.setState({isHovered: false})
    }
  }
 } 
}

Upvotes: 1

CrissCrossCrass
CrissCrossCrass

Reputation: 121

so your question in short

"Is it possible to update Tabs-underline style dynamically when a user hovers on a Tabs-item?"

answer: "yes" :)

in react you can define "states" and "mount"/"bind" them for your specific need! i can't write the code on mobile for you now but have a look here: https://reactjs.org/docs/state-and-lifecycle.html

Upvotes: 0

Related Questions