react-modal icon indicating copy to clipboard operation
react-modal copied to clipboard

Unable to get clientWidth (always equals to 0) after upgrading to 2.3.0

Open parachvte opened this issue 5 years ago • 6 comments

Hi @cheton ,

After upgrading to 2.3.0, child components in <Modal.Body> are unable to get correct clientWidth on componentDidMount(). It works good on version 2.2.2.

componentDidMount() {
    const element = ReactDOM.findDOMNode(this.node);
    const clientWidth = element.clientWidth; // => 0
}

parachvte avatar Jan 28 '19 08:01 parachvte

Hi @parachvte

With React v16.3, you can try using React.createRef() rather than ReactDOM.findDOMNode(node):

node = React.createRef();

componentDidMount() {
    // your parent element should have a pre-defined width and height
    const { clientWidth, clientHeight } = this.node.current.parentElement;
}

render() {
    return (
        <div
            ref={this.node}
        />
    );
}

cheton avatar Jan 28 '19 13:01 cheton

It didn't help, using ReactDOM.findDOMNode() is regard as an advanced (but not recommended) option to get that DOM element.

There is my code:

getVisibleWidth() {
    return this.node.current.parentElement.clientWidth;
}

componentDidMount() {
    const width = this.getVisibleWidth();
    console.log('width', width);

    setTimeout(() => {
        const width = this.getVisibleWidth();
        console.log('width', width);
    });
}

Output:

width 0
width 402

I think there must be some render procedure changed that delayed the real render of my Component.

parachvte avatar Jan 31 '19 08:01 parachvte

The difference between 2.2.2 and 2.3.0 is the dependency update of React Portal component.

In React 16, it will use ReactDOM.createPortal() API to wrap the children, not rendering elements to a new DOM node.

import ReactDOM from 'react-dom';
import Portal from './Portal';
import LegacyPortal from './LegacyPortal';

export default !!(ReactDOM.createPortal) ? Portal : LegacyPortal;

Portal.jsx

https://github.com/trendmicro-frontend/react-portal/blob/master/src/Portal.jsx#L34-L39

    render() {
        return ReactDOM.createPortal(
            this.props.children,
            this.node
        );
    }

LegacyPortal.jsx

https://github.com/trendmicro-frontend/react-portal/blob/master/src/LegacyPortal.jsx#L37-L42

    componentDidUpdate() {
        ReactDOM.render(
            this.props.children,
            this.node
        );
    }

There might be some differences after the change, but I think that is our call to fix deprecated code.

cheton avatar Jan 31 '19 10:01 cheton

I just took a look at your code and found some potential issues. You can see the article https://stackoverflow.com/questions/48323746/order-of-componentdidmount-in-react-components-hierarchy, it describes the order of componentDidMount in React components hierarchy.

Let get back to your code when you finished the reading:

class ExtractBgImg extends Component {
  render() {
    return (
      <div style={{ height: '550px', width: '404px' }}>
        <ExtractingPreview />
      </div>
    )
  }
}

ExtractBgImg's componentDidMount() will fire after ExtractingPreview is mounted. If you tried to access parent DOM node in ExtractingPreview's componentDidMount(), you will get zero width and height because ExtractBgImg does not finish its first render. That's why you need to use a setTimeout to workaround it.

For a better approach, I suggest you use flexbox to control the layout:

display: flex;
flex-direction: column;

or pass down width and height through style or props to ExtractingPreview rather than accessing parent DOM node. That will make your code clean and elegant.

cheton avatar Jan 31 '19 10:01 cheton

Thanks @cheton I'll try flexbox later.

I do know the render of parentNode comes after ExtractingPreview itself. I'm just wondering the difference between react-modal 2.2.2 and 2.3.0, because in 2.2.2, I can still get parentNode's width & height even ExtractBgImg's componentDidMount is not fired. And I didn't find the answer in your recent commits.

parachvte avatar Feb 01 '19 04:02 parachvte

You can get the size of the element with clientWidth or getBoundingClientRect().width. In some cases, I'm not quite sure why, clientWidth didn't work, but getBoundingClientRect did. So I get the width of the element in that order.

I added a setTimeout because during rendering, the element may be adjusting to the layout, so it's important to be aware that its size may change depending on its CSS (or display of the scroll bar).

React 18.2.0

import { useEffect, useRef } from "react"
import "./Component.scss"

function Component() {
    const elementRef = useRef(null)

    useEffect(() => {
        const element = elementRef.current
        let width = element.clientWidth || element.getBoundingClientRect().width
        console.log(width)

        setTimeout(() => {
            width = element.clientWidth || element.getBoundingClientRect().width
            console.log(width)
        }, 1000)
    }, [])

    return (
        <div id="component">
            <div className="element" ref={elementRef}>element</div>
        </div>
    )
}

luislobo14rap avatar Apr 16 '23 23:04 luislobo14rap