Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React DOM offsetHeight before rendering

I need to be able to position a React DOM element by its offsetHeight.

The problem is that I can't gather the offsetHeight of an element that hasn't been created yet (so the height cannot be passed to the render function as a parameter) and I also cannot calculate the height inside of the render function as noted in the React DOM refs docs:

Never access refs inside of any component's render method – or while any component's render method is even running anywhere in the call stack.

The DOM element should render relative to an icon that is clicked to display it.

The component tree:

|— FormInputGroup
  |— Label
    |— TooltipIcon
  |— Tooltip
  |— Input

The render function for the tooltip-icon:

const TooltipIcon = ({ attribute, setCurrentTooltip }) => (
  <Icon
    name="tooltip"
    style={{
      position: "relative",
      top: "1px",
      marginLeft: "10px",
      "cursor": "pointer",
    }}
    onClick={() => setCurrentTooltip(attribute)}
  />
)

The render function:

const Tooltip = ({ title, content, setCurrentTooltip }) => (
  <div className="popover"
    style={{
//      top: "-"+ ReactDOM.findDOMNode(this).offsetHeight +"px",
    }}
  >
    <h3 className="popover-title">{title}</h3>
    <div className="popover-close-button"
      onClick={() => setCurrentTooltip(null)}
    />
    <div className="popover-content">{content}</div>
  </div>
)

Here is the DOM element without the positioning: not positioned

Here is how I would like the position to be rendered (with top: -(offsetHeight)px: positioned

like image 899
cmwall Avatar asked May 17 '16 00:05

cmwall


2 Answers

Not sure it's the best solution, but one technique you can use is to keep track of your style through component state, initializing it to be empty at first:

this.state = { style: {} };

And then updating that state in a componentDidMount() function once you can successfully access the element you are looking for:

componentDidMount() {
  this.setState(
    {style: { top: "-" + (ReactDOM.findDOMNode(this).offsetHeight) + "px" } }
  );
} 

Passing it down as a prop if need be to any child component that needs it.

I put together a codepen with some comments to illustrate a little further: http://codepen.io/anon/pen/vGwyVj

like image 75
Brad Colthurst Avatar answered Oct 11 '22 08:10

Brad Colthurst


I had a similar problem myself, one work around is to use the lifecycle method componentDidMount, as such you will need to use a controlled or 'smart' component.

DEMO

class Tooltip extends React.component {
  constructor(props) {
    super(props);
    this.state = {
      mounted: false,
      ... other state stuff
    };
  }
  ....
}

in componentDidMount you set the state of mounted to true, here you will also have access to your component:

componentDidMount() {
  this.setState({mounted: true})
  // get your style here...
}

then in your render function you conditionally render the component based on that state:

render() {
   if (this.state.mounted) {
     // return your component with the offset style
     // which you can pass in as a prop...
   } else {
     // you can return a version of the component that is not offset here, 
     // I chose not to do this, React is pretty fast. 
     }
}
like image 30
omarjmh Avatar answered Oct 11 '22 08:10

omarjmh