0

I have seen that checking if a flex container has wrapped/overflowed with JS is a thing.

stackoverflow

lib

codepen

Q1. Is this a practice used in this way or are there better ways to do that? The implementation seemed very simple and you can sort of not care about sizes and simply check if the container has wrapped/overflowed to rearrange the content or hide/show elements.

fluent ui has the ResizeGroup component which sort of does that but I just wanted to have a simpler implementation for learning.

Q2. I was able to do it with ResizeObserver but I am wondering if there are easier ways to do that? In my example there are some nav-links and a bunch of menu-buttons first we check for the overflow in the nav-links, once that happens we add/show an extra button to the end of the menu-links, which makes the size of it bigger and we then account for that in the math, when then, the menu-links overflow we only show the search button and the hamburger button.

I put in a pic to try to illustrate better image ex

Here is what I have done, anything that easier than that to follow will be very much appreciated.

const refNavLinks = React.useRef<HTMLDivElement>();
const refNavMenu = React.useRef<HTMLDivElement>();
const ref = React.useRef<HTMLDivElement>();
const imgRef = React.useRef<HTMLDivElement>();
const refW = React.useRef<any>(null);

const [wrappedNav, setWrappedNav] = React.useState({r:null,s:false, w:0});
const [wrappedMenu, setWrappedMenu] = React.useState({r:null,s:false, w:0})

React.useLayoutEffect(() => {
  refW.current = {npw: refNavLinks.current.scrollWidth,
    mpw:refNavMenu.current.scrollWidth}
  return () => {
    console.log('no cleanup')
  };
}, []);

React.useEffect(() => {
  const el = ref.current;
  const resizeObserver = new ResizeObserver(debounce(() => {
    const isNavOverflowing = refNavLinks.current.offsetWidth < refNavLinks.current.scrollWidth
      || refNavLinks.current.parentElement.clientWidth 
          - (imgRef.current.scrollWidth + Math.max(refW.current.mpw, refNavMenu.current.scrollWidth)) 
          < refW.current.npw;
    const isMenuOverflowing = refNavMenu.current.offsetWidth < refNavMenu.current.scrollWidth
      || refNavMenu.current.parentElement.clientWidth 
      - (imgRef.current.scrollWidth) < refW.current.mpw;
    if (isNavOverflowing) {
      refW.current = {...refW.current, 
        mpw:refNavMenu.current.scrollWidth > refW.current.mpw 
          ? refNavMenu.current.scrollWidth
          : refW.current.mpw}
      setWrappedNav({...wrappedNav,s:true})
    } else {
      refW.current = {...refW.current, 
        mpw:refNavMenu.current.scrollWidth > refW.current.mpw 
          ? refNavMenu.current.scrollWidth
          : refW.current.mpw}
      setWrappedNav({...wrappedNav,s:false})
    }
    if (isNavOverflowing && isMenuOverflowing) {
      setWrappedMenu({...wrappedMenu,s:true})
    } else {
      setWrappedMenu({...wrappedMenu,s:false})
    }
  },500))
  resizeObserver.observe(ref.current);
  return () => {
    resizeObserver.unobserve(el);
    resizeObserver.disconnect();
  };
}, [ref.current])
Ricardo Silva
  • 1,221
  • 9
  • 19

1 Answers1

0

For anyone wondering I was able to isolate the code to be reused in any given container:


import useResizeObserver from '@react-hook/resize-observer'
//we need ResizerObserver
const useSize = (target) => {
  const [size, setSize] = React.useState()

  React.useLayoutEffect(() => {
    
    setSize(target.current && target.current.getBoundingClientRect())
  }, [target])

  // Where the magic happens
  useResizeObserver(target, (entry) => setSize((entry as any).contentRect))
  return size
}
//this part goes in the component
//we need one ref to the container and some states to track the size of the individual items
const menuRef = React.useRef(null)
  const menuRefSizes = useSize(menuRef)
  const [navLinkIsHidden, setNavLinkIsHidden] = React.useState(false)
  const [neededContainerWidth, setNeededContainerWidth] = React.useState(null)
  const [navMenuIsHidden, setNavMenuIsHidden] = React.useState(false)
  const [neededNavMenuContainerWidth, setNeededNavMenuContainerWidth] = React.useState(null)
  React.useLayoutEffect(()=>{
    CheckResponsive(
      setNeededContainerWidth,setNavLinkIsHidden,neededContainerWidth, 'nav-links')
      CheckResponsive(
        setNeededNavMenuContainerWidth,setNavMenuIsHidden,neededNavMenuContainerWidth,'nav-menu-buttons')
    return () => {
      // no cleanup
    };
  }, [menuRefSizes])

//and the function we use applies to all items we are tracking.
//I am using direct dom references here, I am sure this can be simplified further but this is the more general I could done.

const CheckResponsive = (setContainer, setHide, neededContainerWidth, contId) => {
  const navLinkNode = window.document.getElementById(contId)
  const menuContainer = window.document.getElementById('menu')
  const scrollWidth = navLinkNode ? navLinkNode.scrollWidth : 0
  const offsetWidth = navLinkNode ? navLinkNode.offsetWidth : 0
  const NavLinkIsOverflown = navLinkNode ? scrollWidth !== offsetWidth : false
  const DoesNavLinkFitInContainer = menuContainer.scrollWidth >= neededContainerWidth
  const navLinkWillBeHidden = NavLinkIsOverflown || !DoesNavLinkFitInContainer
  
  navLinkWillBeHidden?setHide(true):setHide(false)
  if (navLinkNode && navLinkWillBeHidden) {
    setContainer(menuContainer.scrollWidth 
      + window.document.getElementById(contId).scrollWidth 
      - window.document.getElementById(contId).offsetWidth)
  }
  return () => {
    // no cleanup
  };
}

the result is that all items that I am tracking wont be rendered when they overflow, when the container then have enough space again the items will be rendered.

Ricardo Silva
  • 1,221
  • 9
  • 19