How to get rendered JSX in React?

Viewed 305

Imagine a Container component that renders a div with the specified height, e.g.:

<Container height="80">
  Hello World
</Container>

enter image description here

and MyHeader component that renders a Container with a certain height, e.g.:

function MyHeader() {
  return (
    <Container height="100">
      Header content goes here
    </Container>
  );
}

Now, I'd like to implement a Fixed component that looks like this:

<Fixed>
  <Fixed.Item>
    <MyHeader />
  </Fixed.Item>
  <Fixed.Content>
    Some content goes here
  </Fixed.Content>
</Fixed>

When rendering Fixed.Content I'd like to automatically set its offset to 100px (since MyHeader is 100px high).

Is there a way for the Fixed component to get MyHeader's height so it could pass it to Fixed.Content?

Is there a better way to automate this?

Note: Using useEffect (or componentDidMount) is not an option because I'd like it to work in server rendered environments.

5 Answers

You can use refs for that.

To solve your specific problem, first turn your <Container> component into a class component to be able to set a ref to it.

Then use React.forwardRef to forward the ref from the MyHeader component to the <Container> component:

const MyHeader = React.forwardRef((props, ref) => {
  return (
    <Container ref={ref} height={100}>
      Header content goes here
    </Container>
  );
});

Finally, create a ref hook in your component that renders the <Fixed> component and pass the ref to the <MyHeader> component. You can then also use the ref to set the height of the <Fixed.Content> component (or whatever you want to set), as follows:

function App () {
  const headerRef = React.useRef(null)
  return (
      <Fixed>
        <Fixed.Item>
          <MyHeader ref={headerRef} />
        </Fixed.Item>
        <Fixed.Content height={headerRef.current && headerRef.current.props.height}>
          Some content goes here
        </Fixed.Content>
      </Fixed>
  )
}

This seems to only render the <App> component once, so it should also work for server-side rendering. See the following code snippet as an example: https://codesandbox.io/s/get-height-from-header-gvvlo

You can use React Test Renderer which renders React Node to an inspectable Object tree.

import TestRenderer from 'react-test-renderer';

const height = TestRenderer.create(<MyHeader />).toTree().rendered.props.height

This way, you can get its height without second render.

function Container(props) {
  return (
    <div style={{height:props.height}}>
       your content
    </div>
  );
}

you need to pass height to Container as props which can varied dynamically.

function MyHeader() {
  return (
    <Container height="100px">
      Header content goes here
    </Container>
  );
}

for fixed also you need to pass it as props by having the height in a variable,e.g.:

function TopBar() {
  let height = '100px'
  return (
     <>
       <MyHeader height={height} />
       <FixedComponent height={height} />
     </> 
  );
}

I think you need to pass height prop from MyHeader to Fixed. And then Fixed to Fixed.Content. For example you can do similar to the following:

MyHeader.jsx

class MyHeader extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            height: 100
        }
        this.props.setHeaderOffset(this.state.height)
    }
    render() {
        return (
            <Container height={this.state.height}>
            </Container>
        )
    }
}

Fixed.jsx

class Fixed extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            headerOffset: 0
        }
        this.setHeaderOffset = this.setHeaderOffset.bind(this)
    }
    setHeaderOffset(headerOffset) {
        this.setState({
            headerOffset
        })
    }
    render() {
        return (
            <>
                <Item>
                    <MyHeader setHeaderOffset={this.setHeaderOffset} />
                </Item>
                <Content headerOffset={this.state.headerOffset}>
                    Some content goes here
                </Content>
            </>
        )
    }
}
Related