yahoo/react-stickynode

Sticky element with changing height

Closed this issue · 6 comments

Does this handle a case where there is a sticky sidebar that

  • Might be taller or shorter than the scrollable content area next to it
  • Might change in height due to expandable menus in the sidebar
  • The content area may also change in height

?

I ask because an easy problem to end up with other libraries or self made code with seems to be that the following happens:

  1. The sidebar becomes sticky
  2. The sidebar becomes larger when a menu is expanded
  3. The sidebar is no longer in the document flow, so it fails to force the div that contains the sidebar and the content to become larger, instead overflowing.

I've had a similar problem with this where the sticky content is dynamic and if I'm scrolled all the way to the bottom of the window and then sticky becomes taller than the window, I have to scroll back up for it to re-adjust. The trouble appears to be that https://github.com/yahoo/react-stickynode/blob/master/src/Sticky.jsx#L297 needs to also check if prevProps.children is equal to this.props.children or something since updateInitialDimension and update need to be called again if the spacial size of the children changes.

Actually, it would need to do a deep compare on the children so it maybe better to just remove that check.

One possible way to address this is by getting a ref to the react-stickynode component, and then doing

requestAnimationFrame(() => {
  if (this.state.stickyNode) {
    this.state.stickyNode.updateInitialDimension();
    this.state.stickyNode.update();
  }
});

Whenever I know the size of the sticky element has changed (In my case its a collapsible react-collapse element, so the children don't change). This doesn't exactly fill me with joy but it seems to work...

@hedgepigdaniel Thanks, I actually used that as a work around for now. Still bothersome that something like that has to be added though. Maybe if I get the time, I'll make a PR to fix this problem.

I too am encountering this issue. Seems the StickyNode doesn't correctly determine the height on the first pass, resulting in the element not pinning correctly. It corrects itself after scrolling up 1 pixel.

Hello, we solved that issue with the code from #102 (comment). We did some changes to make it work with hooks and added resize observers to detect height changes of the sticky element.

Simplified code:

const Sidebar = () => {
  const sidebarDomRef = useRef(null) // needed for resize observer
  const stickyRef = useRef(null) // needed to access sticky instance
  const sidebarResizeObserverRef = useRef({}) // needed to remove observer when component is unloaded

  //  update sticky instance when height changes
  const updateSticky = () => {
    const stickyInstance = stickyRef.current

    if (stickyInstance === null) {
      return
    }

    stickyInstance.updateInitialDimension()
    stickyInstance.update()
  }

  // observe sidebar height changes
  useEffect(() => {
    const sidebarDomElement = sidebarDomRef.current

    sidebarResizeObserverRef.current = new ResizeObserver(entries => {
      // ignore other sidebar observers (not strictly neccessary)
      const sidebarObserverResults = entries.find(({ target }) => target === sidebarDomElement)

      if (typeof sidebarObserverResults === 'undefined') {
        return
      }

      updateSticky()
    })

    if (sidebarDomElement === null) {
      return
    }

    sidebarResizeObserverRef.current.observe(sidebarDomElement)

    // cleanup when components is unloaded
    return () => {
      sidebarResizeObserverRef.current.disconnect()
    }
  }, [])

  return (
    <Sticky top={0} ref={stickyRef}>
        <Sidebar ref={sidebarDomRef}>
            ....
        </Sidebar>
    </Sticky>
  )
}