react-modal
react-modal copied to clipboard
Unable to get clientWidth (always equals to 0) after upgrading to 2.3.0
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
}
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}
/>
);
}
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.
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.
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.
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.
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>
)
}