question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

bug: client rects of any Ionic component in React is not measurable

See original GitHub issue

I am trying to calculate the height of a Ionic react component, but it is not possible after the initial render is done. The common React techniques in use and all the common libraries that return measures of a component do not work with ionic and return a 0x0 size.

My suspect is that the Ionic components have an internal lifecycle that decides whether to render the component or not based on the hydration status and the stack position of the IonPage hosting the component.

However this makes it impossible to measure the size of a component, unless the ‘measure tool’ constantly polls for a size, which might destroy performances.

In the examples below, I have three different implementations:

  • the first one is called useMeasure and is very similar to what is described in https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
  • the second one is called useMeasureAlt and gets the height of the component using useLayoutEffect
  • the third one id called useMeasureDelay and is a variant of the previous, where if the height is 0, it schedules a redraw (changing a state variable) until height > 0.

The first two methods do not return any height. The third returns a height after some re-renders.

IMHO the issue could be solved in two ways:

  1. implement on a onIonRendered(e) event, which triggers when the element is really rendered - and therefore the clientRect is available -
  2. when the component is rendered, invoke the ref function.

Thanks, Marco

https://codesandbox.io/embed/proud-glade-lrpc9?fontsize=14



function useMeasure<T extends HTMLElement>() : [(node: T) => void, number | null]{

  const rect = React.useRef<ClientRect | DOMRect | null>(null) 
  const refCallback = React.useCallback ( (node: T) => {
    rect.current = node.getBoundingClientRect();
    console.log(`Measured: ${JSON.stringify(rect.current)}`)
  },[]);
  return [refCallback, rect.current && rect.current.height]
}


function useMeasureAlt<T extends HTMLElement>() : [React.RefObject<T>, number | null]{

  const [rect, setRect] = React.useState<ClientRect | DOMRect | null>(null)
  const ref = React.useRef<T>(null);
  React.useLayoutEffect( () => {
    if (ref.current) {
      const newRect = ref.current.getBoundingClientRect();
      if (!rect || rect.height !== newRect.height)
        setRect(newRect);
      console.log(`Measured in LayoutEffect: ${JSON.stringify(newRect)}`)
    }
  },) 

  return [ref, rect && rect.height]
}

function useMeasureWithDelay<T extends HTMLElement> () : [React.RefObject<T>, number | null] {
  const ref = React.useRef<T>(null)
  const [forceRedraw, setForceRedraw] = React.useState(1);
  const rect = React.useRef<ClientRect | DOMRect | null>(null)
  
  //this effect is hacky trick to force a re-render until IonGrid has done its duty...
  React.useLayoutEffect( () => {
    rect.current = ref.current && ref.current.getBoundingClientRect()
    console.log(`Measured with delay: ${JSON.stringify(rect.current)}. forceRedraw: ${forceRedraw}`)
    if (!ref.current) return;
    if (!rect.current!.height && forceRedraw>0) {
        setTimeout( ()=>setForceRedraw(v => v+1),1);
    }
    if (rect.current!.height) {
        setForceRedraw(0);
    }
  },[forceRedraw, rect])

  return [ref,rect.current && rect.current.height];
}


const MeasureTest : React.FC = () => {
  const [ref, height] = useMeasure<HTMLIonRowElement>();
  return  <IonRow ref={ref}>
    <IonCol><IonText>Measure test. Hello world. My height is {height}px</IonText></IonCol>
  </IonRow>
}

const MeasureWithLayoutTest : React.FC = () => {
  const [ref, height] = useMeasureAlt<HTMLIonRowElement>();
  return  <IonRow ref={ref}>
    <IonCol><IonText>Layout test. Hello world. My height is {height}px</IonText></IonCol>
  </IonRow>
}

const MeasureWithDelayTest : React.FC = () => {
  const [ref, height] = useMeasureWithDelay<HTMLIonRowElement>();
  return  <IonRow ref={ref}>
    <IonCol><IonText>Layout test. Hello world. My height is {height}px</IonText></IonCol>
  </IonRow>
}



const App: React.FunctionComponent = () => {

  return (
  <IonApp>
    <IonReactRouter>
      <IonPage id="main">
        <IonGrid>
          <MeasureTest/>
          <MeasureWithLayoutTest/>
          <MeasureWithDelayTest/>
        </IonGrid>
      </IonPage>
    </IonReactRouter>
  </IonApp>
)}

export default App;

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:5
  • Comments:7

github_iconTop GitHub Comments

1reaction
marcoromagcommented, Oct 28, 2019

Currently, I found a workaround that “works” using element-resize-detector It works like a charm, with the added bonus that if the size changes, you’ll get the new size, but I still think it would be nice if stencil components behave nicely 😄

import * as React from 'react'

import {default as elementSizeDetectorMarker} from 'element-resize-detector'

const erd = elementSizeDetectorMarker({
  strategy: "scroll" //<- For ultra performance.
});
export function useSize<T extends HTMLElement>(ref: React.RefObject<T>) {

  const [size, setSize] = React.useState<ClientRect | DOMRect | null> (null);

  React.useEffect( () => {
    const currentEl = ref.current;

    if (!currentEl) 
      return;

    const listener: (element: HTMLElement) => void = (element) => {
      setSize (element.getBoundingClientRect())
    }

    erd.listenTo(currentEl, listener);
    return () => {
      erd.removeListener(currentEl, listener);
    }
  },[ref.current])

  return size;
}
0reactions
ionitron-bot[bot]commented, Apr 27, 2022

Thanks for the issue! This issue is being closed due to inactivity. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

Thank you for using Ionic!

Read more comments on GitHub >

github_iconTop Results From Across the Web

client rects of any Ionic component in React is not measurable ...
I am trying to calculate the height of a Ionic react component, but it is not possible after the initial render is done....
Read more >
Is this an Array of Object BUG? Ionic v6 React - Stack Overflow
I'm trying to figure out the solution, but happens that i`m more than 24hours trying to figure out and nothing. could someone help?...
Read more >
Comparing Cross-Platform Mobile Development Frameworks
View our comparison of cross-platform mobile app development frameworks to see how Ionic matches up against React Native and other alternatives.
Read more >
isopropylbenzylamine in bleach - Feudo 43
NaOH reacts with acid to produce a water and an ionic compound. Name matches: benzylamine n-isopropylbenzylamine.
Read more >
Ionic vs. React Native: Which is the Best for Mobile App ...
In this blog, we explain the best mobile app development platforms Ionic vs React Native. Why do developers use Ionic or React Native...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found