@@ -73,15 +73,15 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
7373 ...otherProps
7474 } = props ;
7575
76- let stateRef = useRef ( {
76+ let state = useRef ( {
7777 scrollTop : 0 ,
7878 scrollLeft : 0 ,
7979 scrollEndTime : 0 ,
8080 scrollTimeout : null as ReturnType < typeof setTimeout > | null ,
8181 width : 0 ,
8282 height : 0 ,
8383 isScrolling : false
84- } ) ;
84+ } ) . current ;
8585 let { direction} = useLocale ( ) ;
8686
8787 let [ isScrolling , setScrolling ] = useState ( false ) ;
@@ -98,7 +98,6 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
9898 flushSync ( ( ) => {
9999 let scrollTop = e . currentTarget . scrollTop ;
100100 let scrollLeft = getScrollLeft ( e . currentTarget , direction ) ;
101- let state = stateRef . current ;
102101
103102 // Prevent rubber band scrolling from shaking when scrolling out of bounds
104103 state . scrollTop = Math . max ( 0 , Math . min ( scrollTop , contentSize . height - state . height ) ) ;
@@ -139,13 +138,12 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
139138 } , 300 ) ;
140139 }
141140 } ) ;
142- } , [ props , direction , stateRef , contentSize , onVisibleRectChange , onScrollStart , onScrollEnd ] ) ;
141+ } , [ props , direction , state , contentSize , onVisibleRectChange , onScrollStart , onScrollEnd ] ) ;
143142
144143 // Attach event directly to ref so RAC Virtualizer doesn't need to send props upward.
145144 useEvent ( ref , 'scroll' , onScroll ) ;
146145
147146 useEffect ( ( ) => {
148- let state = stateRef . current ;
149147 return ( ) => {
150148 if ( state . scrollTimeout != null ) {
151149 clearTimeout ( state . scrollTimeout ) ;
@@ -155,7 +153,8 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
155153 window . dispatchEvent ( new Event ( 'tk.connect-observer' ) ) ;
156154 }
157155 } ;
158- } , [ stateRef ] ) ;
156+ // eslint-disable-next-line react-hooks/exhaustive-deps
157+ } , [ ] ) ;
159158
160159 let isUpdatingSize = useRef ( false ) ;
161160 let updateSize = useCallback ( ( flush : typeof flushSync ) => {
@@ -176,7 +175,6 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
176175 let w = isTestEnv && ! isClientWidthMocked ? Infinity : clientWidth ;
177176 let h = isTestEnv && ! isClientHeightMocked ? Infinity : clientHeight ;
178177
179- let state = stateRef . current ;
180178 if ( state . width !== w || state . height !== h ) {
181179 state . width = w ;
182180 state . height = h ;
@@ -199,11 +197,36 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
199197 }
200198
201199 isUpdatingSize . current = false ;
202- } , [ ref , stateRef , onVisibleRectChange ] ) ;
203- let [ update , setUpdate ] = useState ( { } ) ;
200+ } , [ ref , state , onVisibleRectChange ] ) ;
204201 let updateSizeEvent = useEffectEvent ( updateSize ) ;
205202
206- useScrollViewContentSizeChange ( { updateSize, contentSize, isUpdatingSize, setUpdate} ) ;
203+ // Update visible rect when the content size changes, in case scrollbars need to appear or disappear.
204+ let lastContentSize = useRef < Size | null > ( null ) ;
205+ let [ update , setUpdate ] = useState ( { } ) ;
206+ // We only contain a call to setState in here for testing environments.
207+ // eslint-disable-next-line react-hooks/exhaustive-deps
208+ useLayoutEffect ( ( ) => {
209+ if ( ! isUpdatingSize . current && ( lastContentSize . current == null || ! contentSize . equals ( lastContentSize . current ) ) ) {
210+ // React doesn't allow flushSync inside effects, so queue a microtask.
211+ // We also need to wait until all refs are set (e.g. when passing a ref down from a parent).
212+ // If we are in an `act` environment, update immediately without a microtask so you don't need
213+ // to mock timers in tests. In this case, the update is synchronous already.
214+ // IS_REACT_ACT_ENVIRONMENT is used by React 18. Previous versions checked for the `jest` global.
215+ // https://github.com/reactwg/react-18/discussions/102
216+ // @ts -ignore
217+ if ( typeof IS_REACT_ACT_ENVIRONMENT === 'boolean' ? IS_REACT_ACT_ENVIRONMENT : typeof jest !== 'undefined' ) {
218+ // This is so we update size in a separate render but within the same act. Needs to be setState instead of refs
219+ // due to strict mode.
220+ setUpdate ( { } ) ;
221+ lastContentSize . current = contentSize ;
222+ return ;
223+ } else {
224+ queueMicrotask ( ( ) => updateSizeEvent ( flushSync ) ) ;
225+ }
226+ }
227+
228+ lastContentSize . current = contentSize ;
229+ } ) ;
207230
208231 // Will only run in tests, needs to be in separate effect so it is properly run in the next render in strict mode.
209232 useLayoutEffect ( ( ) => {
@@ -227,7 +250,7 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
227250 if ( scrollDirection === 'horizontal' ) {
228251 style . overflowX = 'auto' ;
229252 style . overflowY = 'hidden' ;
230- } else if ( scrollDirection === 'vertical' || contentSize . width === stateRef . current . width ) {
253+ } else if ( scrollDirection === 'vertical' || contentSize . width === state . width ) {
231254 // Set overflow-x: hidden if content size is equal to the width of the scroll view.
232255 // This prevents horizontal scrollbars from flickering during resizing due to resize observer
233256 // firing slower than the frame rate, which may cause an infinite re-render loop.
@@ -257,33 +280,3 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
257280 }
258281 } ;
259282}
260-
261- function useScrollViewContentSizeChange ( { updateSize, contentSize, isUpdatingSize, setUpdate} : { updateSize : ( flush : typeof flushSync ) => void , contentSize : Size , isUpdatingSize : RefObject < boolean > , setUpdate : ( update : { } ) => void } ) {
262- let updateSizeEvent = useEffectEvent ( updateSize ) ;
263-
264- // Update visible rect when the content size changes, in case scrollbars need to appear or disappear.
265- let lastContentSize = useRef < Size | null > ( null ) ;
266- // We only contain a call to setState in here for testing environments.
267- useLayoutEffect ( ( ) => {
268- if ( ! isUpdatingSize . current && ( lastContentSize . current == null || ! contentSize . equals ( lastContentSize . current ) ) ) {
269- // React doesn't allow flushSync inside effects, so queue a microtask.
270- // We also need to wait until all refs are set (e.g. when passing a ref down from a parent).
271- // If we are in an `act` environment, update immediately without a microtask so you don't need
272- // to mock timers in tests. In this case, the update is synchronous already.
273- // IS_REACT_ACT_ENVIRONMENT is used by React 18. Previous versions checked for the `jest` global.
274- // https://github.com/reactwg/react-18/discussions/102
275- // @ts -ignore
276- if ( typeof IS_REACT_ACT_ENVIRONMENT === 'boolean' ? IS_REACT_ACT_ENVIRONMENT : typeof jest !== 'undefined' ) {
277- // This is so we update size in a separate render but within the same act. Needs to be setState instead of refs
278- // due to strict mode.
279- setUpdate ( { } ) ;
280- lastContentSize . current = contentSize ;
281- return ;
282- } else {
283- queueMicrotask ( ( ) => updateSizeEvent ( flushSync ) ) ;
284- }
285- }
286-
287- lastContentSize . current = contentSize ;
288- } ) ;
289- }
0 commit comments