@@ -128,35 +128,38 @@ export const Spinner: FC<SpinnerProps> = ({
128
128
129
129
// Not a big fan of one-time custom hooks, but it helps insulate the main
130
130
// component from the chaos of handling all these state syncs, when the ultimate
131
- // result is a simple boolean. V8 will be able to inline the function definition
132
- // in some cases anyway
131
+ // result is a simple boolean. A lot of browsers will be able to inline the
132
+ // function definition in some cases anyway
133
133
function useShowSpinner (
134
134
loading : boolean ,
135
135
spinnerStartDelayMs : number ,
136
136
) : boolean {
137
137
// Doing a bunch of mid-render state syncs to minimize risks of
138
138
// contradictory states during re-renders. It's ugly, but it's what the
139
- // React team officially recommends
140
- const noDelay = spinnerStartDelayMs === 0 ;
141
- const [ mountSpinner , setMountSpinner ] = useState ( noDelay ) ;
142
- const unmountImmediatelyOnRerender = mountSpinner && ! loading ;
139
+ // React team officially recommends. Be very careful with this logic; React
140
+ // only bails out of redundant state updates if they happen outside of a
141
+ // render. Inside a render, if you keep calling a state dispatch, you will
142
+ // get an infinite render loop, no matter what the state value is.
143
+ const [ spinnerActive , setSpinnerActive ] = useState ( spinnerStartDelayMs === 0 ) ;
144
+ const unmountImmediatelyOnRerender = spinnerActive && ! loading ;
143
145
if ( unmountImmediatelyOnRerender ) {
144
- setMountSpinner ( false ) ;
146
+ setSpinnerActive ( false ) ;
145
147
}
146
- const mountImmediatelyOnRerender = ! mountSpinner && noDelay ;
148
+ const mountImmediatelyOnRerender =
149
+ ! spinnerActive && loading && spinnerStartDelayMs === 0 ;
147
150
if ( mountImmediatelyOnRerender ) {
148
- setMountSpinner ( true ) ;
151
+ setSpinnerActive ( true ) ;
149
152
}
150
153
useEffect ( ( ) => {
151
154
if ( spinnerStartDelayMs === 0 ) {
152
155
return ;
153
156
}
154
157
155
158
const delayId = window . setTimeout ( ( ) => {
156
- setMountSpinner ( true ) ;
159
+ setSpinnerActive ( true ) ;
157
160
} , spinnerStartDelayMs ) ;
158
161
return ( ) => window . clearTimeout ( delayId ) ;
159
162
} , [ spinnerStartDelayMs ] ) ;
160
163
161
- return loading && mountSpinner ;
164
+ return spinnerActive && loading ;
162
165
}
0 commit comments