Szamanm
Szamanm

Reputation: 783

React: get DOM position for one of the children of component

I have a very annoying issue, and I need help with my component. It is used in such context:

<table>
  <thead>/* ... */</thead>
  <tbody>
    <COMPONENT>
      <ChildComponent>/* ... */</ChildComponent>
      <ChildComponent>/* ... */</ChildComponent>
      <ChildComponent>/* ... */</ChildComponent>
    </COMPONENT>
  </tbody>
</table>

ChildComponent is a component that includes other components but eventually renders simple HTML's <tr>

inside component.tsx, I need to get DOM values (offsetTop and clientHeight) for n-th child.

I've tried many things:

argument appears to not be a ReactComponent. Keys: $$typeof, type, key, ref, props, _owner, _store

children.map((child, index) =>
    index === n ? (
        <div style={{display: contents}} key={index} ref={ref}>
    ) : child
)

gives me warning (but works!):

index.js:2178 Warning: validateDOMNesting(...): cannot appear as a child of . in tr (created by FoldControlContainer) ...

is there a better solution? I've tried to use <>, or other "transparent" to DOM components instead of div, but it didn't work.

Upvotes: 2

Views: 7070

Answers (2)

Estus Flask
Estus Flask

Reputation: 222309

findDOMNode expects an instance of React.Component. Components should be rendered with refs in order to get instances. Since refs aren't used in <ChildComponent>/* ... */</ChildComponent> children, they should be cloned with a ref.

If children are supposed to be class component elements and their amount doesn't change during parent component lifespan, the case can be simplified to:

childRefs = [...Array(React.Children.count(this.props.children))]
.map(() => React.createRef());

render() {
  return React.Children.map(this.props.children, ((child, i) => 
    React.cloneElement(child, { ref: this.childRefs[i] });
  )
}

DOM element is available as ReactDOM.findDOMNode(this.childRefs[n].current) when the component is mounted.

Upvotes: 2

SlimSim
SlimSim

Reputation: 616

use React.cloneElement to set the refs for the children: https://reactjs.org/docs/react-api.html#cloneelement ex:

React.cloneElement(child, {ref: this.setChildRef})}
export default class Test extends React.Component {

  childrenRefs = {}

  setChildRef = index => el => this.childrenRefs[index] = el;

  showInfo = () => {
    console.log('children', Object.keys(this.childrenRefs).map(key => this.childrenRefs[key].offsetTop))
  }

  render() {

    return (
      <div>
        { React.Children.toArray(this.props.children).map((c, index) => React.cloneElement(
          c,
          {ref: this.setChildRef(index)},
        ))}
        <button onClick={this.showInfo} >test</button>
      </div>
    );
  }
} 

here is a link to a full working example: https://stackblitz.com/edit/react-cf33ge Open the console at the bottom right to see the output.

Upvotes: 4

Related Questions