Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
parachvte opened this issue Jan 28, 2019 · 6 comments

Comments

@parachvte
Copy link

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 parachvte changed the title Unable to get clientWidth always equals to 0 after upgrading to 2.3.0 Unable to get clientWidth (always equals to 0) after upgrading to 2.3.0 Jan 28, 2019
@cheton
Copy link
Member

cheton commented Jan 28, 2019

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}
        />
    );
}

@parachvte
Copy link
Author

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.

@cheton
Copy link
Member

cheton commented Jan 31, 2019

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
Copy link
Member

cheton commented Jan 31, 2019

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.

@parachvte
Copy link
Author

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.

@luislobo14rap
Copy link

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>
    )
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants