11import * as React from 'react' ;
22
3- import { createRoot } from 'react-dom/client' ;
4- import type { Root } from 'react-dom/client' ;
5-
63import { cellDefaultWidth , headerDefaultWidth } from '../constants' ;
4+ import { createMeasureRoot } from '../utils/createMeasureRoot' ;
5+ import type { MeasureRoot } from '../utils/createMeasureRoot' ;
76import { renderElementForMeasure as defaultRenderElementForMeasure } from '../utils/renderElementForMeasure' ;
87
98export type UseMeasureCellWidthProps = {
@@ -13,29 +12,61 @@ export type UseMeasureCellWidthProps = {
1312export function useMeasureCellWidth ( {
1413 renderElementForMeasure = defaultRenderElementForMeasure ,
1514} : UseMeasureCellWidthProps ) {
16- const rootRef = React . useRef < Root | null > ( null ) ;
15+ const rootRef = React . useRef < MeasureRoot | null > ( null ) ;
16+ const rootPromiseRef = React . useRef < Promise < MeasureRoot > | null > ( null ) ;
17+ const isUnmountedRef = React . useRef ( false ) ;
1718 const measureContainerRef = React . useRef < HTMLDivElement | null > ( null ) ;
1819 const lastMeasuredElementRef = React . useRef < {
1920 element : React . ReactNode ;
2021 width : number ;
2122 } | null > ( null ) ;
2223
2324 React . useEffect ( ( ) => {
25+ isUnmountedRef . current = false ;
26+
2427 return ( ) => {
28+ isUnmountedRef . current = true ;
29+
2530 if ( rootRef . current ) {
2631 rootRef . current . unmount ( ) ;
2732 rootRef . current = null ;
2833 }
2934
35+ rootPromiseRef . current = null ;
36+
3037 if ( measureContainerRef . current ) {
3138 document . body . removeChild ( measureContainerRef . current ) ;
3239 measureContainerRef . current = null ;
3340 }
3441 } ;
3542 } , [ ] ) ;
3643
44+ const ensureRoot = React . useCallback ( ( container : HTMLElement ) => {
45+ if ( rootRef . current ) {
46+ return Promise . resolve ( rootRef . current ) ;
47+ }
48+
49+ if ( ! rootPromiseRef . current ) {
50+ rootPromiseRef . current = createMeasureRoot ( container ) . then ( ( root ) => {
51+ // The hook may have unmounted while the React 18 client entry
52+ // was being resolved; tear the orphan root down immediately.
53+ if ( isUnmountedRef . current ) {
54+ root . unmount ( ) ;
55+
56+ return root ;
57+ }
58+
59+ rootRef . current = root ;
60+
61+ return root ;
62+ } ) ;
63+ }
64+
65+ return rootPromiseRef . current ;
66+ } , [ ] ) ;
67+
3768 return React . useCallback (
38- ( element : React . ReactNode , cellType : 'header' | 'cell' = 'cell' ) => {
69+ async ( element : React . ReactNode , cellType : 'header' | 'cell' = 'cell' ) => {
3970 if ( element === null || element === undefined ) {
4071 return 0 ;
4172 }
@@ -52,7 +83,6 @@ export function useMeasureCellWidth({
5283
5384 document . body . appendChild ( container ) ;
5485 measureContainerRef . current = container ;
55- rootRef . current = createRoot ( container ) ;
5686 }
5787
5888 if (
@@ -94,7 +124,9 @@ export function useMeasureCellWidth({
94124 }
95125
96126 try {
97- rootRef . current ! . render ( renderElementForMeasure ( element ) ) ;
127+ const root = await ensureRoot ( measureContainerRef . current ) ;
128+
129+ root . render ( renderElementForMeasure ( element ) ) ;
98130
99131 return new Promise < number > ( ( resolve ) => {
100132 setTimeout ( ( ) => {
@@ -129,6 +161,6 @@ export function useMeasureCellWidth({
129161 return defaultWidth ;
130162 }
131163 } ,
132- [ renderElementForMeasure ] ,
164+ [ ensureRoot , renderElementForMeasure ] ,
133165 ) ;
134166}
0 commit comments