@@ -22,6 +22,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
22
22
// Modified to be compatible with webpack 4 / Next.js
23
23
24
24
import React from 'react'
25
+ import { useSubscription } from 'use-subscription'
25
26
import { LoadableContext } from './loadable-context'
26
27
27
28
const ALL_INITIALIZERS = [ ]
@@ -151,113 +152,129 @@ function createLoadableComponent (loadFn, options) {
151
152
} )
152
153
}
153
154
154
- return class LoadableComponent extends React . Component {
155
- constructor ( props ) {
156
- super ( props )
157
- init ( )
158
-
159
- this . state = {
160
- error : res . error ,
161
- pastDelay : false ,
162
- timedOut : false ,
163
- loading : res . loading ,
164
- loaded : res . loaded
155
+ const LoadableComponent = ( props , ref ) => {
156
+ init ( )
157
+
158
+ const context = React . useContext ( LoadableContext )
159
+ const [ , setDirty ] = React . useState ( null )
160
+ const subscription = React . useMemo ( ( ) => {
161
+ const subscription = new LoadableSubscription ( res , opts )
162
+ return {
163
+ getCurrentValue : ( ) => subscription . getCurrentValue ( ) ,
164
+ subscribe : ( ) => subscription . subscribe ( )
165
165
}
166
+ } , [ res ] )
167
+ const state = useSubscription ( subscription )
168
+
169
+ const retry = React . useMemo (
170
+ ( ) => ( ) => {
171
+ res = loadFn ( opts . loader )
172
+ setDirty ( ( ) => ( { } ) )
173
+ } ,
174
+ [ ]
175
+ )
176
+
177
+ React . useImperativeHandle ( ref , ( ) => ( {
178
+ retry
179
+ } ) )
180
+
181
+ if ( context && Array . isArray ( opts . modules ) ) {
182
+ opts . modules . forEach ( moduleName => {
183
+ context ( moduleName )
184
+ } )
166
185
}
167
186
168
- static preload ( ) {
169
- return init ( )
187
+ if ( state . loading || state . error ) {
188
+ return React . createElement ( opts . loading , {
189
+ isLoading : state . loading ,
190
+ pastDelay : state . pastDelay ,
191
+ timedOut : state . timedOut ,
192
+ error : state . error ,
193
+ retry : retry
194
+ } )
195
+ } else if ( state . loaded ) {
196
+ return opts . render ( state . loaded , props )
197
+ } else {
198
+ return null
170
199
}
200
+ }
171
201
172
- static contextType = LoadableContext
173
- // TODO: change it before next major React release
174
- // eslint-disable-next-line
175
- UNSAFE_componentWillMount ( ) {
176
- this . _mounted = true
177
- this . _loadModule ( )
178
- }
202
+ LoadableComponent . preload = ( ) => init ( )
203
+ LoadableComponent . displayName = 'LoadableComponent'
179
204
180
- _loadModule ( ) {
181
- if ( this . context && Array . isArray ( opts . modules ) ) {
182
- opts . modules . forEach ( moduleName => {
183
- this . context ( moduleName )
184
- } )
185
- }
205
+ return React . forwardRef ( LoadableComponent )
206
+ }
186
207
187
- if ( ! res . loading ) {
188
- return
189
- }
208
+ class LoadableSubscription {
209
+ constructor ( res , opts ) {
210
+ this . _res = res
211
+ this . _delay = null
212
+ this . _timeout = null
213
+ this . _callbacks = new Set ( )
190
214
215
+ this . _state = {
216
+ pastDelay : false ,
217
+ timedOut : false
218
+ }
219
+
220
+ if ( res . loading ) {
191
221
if ( typeof opts . delay === 'number' ) {
192
222
if ( opts . delay === 0 ) {
193
- this . setState ( { pastDelay : true } )
223
+ this . _state . pastDelay = true
194
224
} else {
195
225
this . _delay = setTimeout ( ( ) => {
196
- this . setState ( { pastDelay : true } )
226
+ this . _update ( {
227
+ pastDelay : true
228
+ } )
197
229
} , opts . delay )
198
230
}
199
231
}
200
232
201
233
if ( typeof opts . timeout === 'number' ) {
202
234
this . _timeout = setTimeout ( ( ) => {
203
- this . setState ( { timedOut : true } )
235
+ this . _update ( { timedOut : true } )
204
236
} , opts . timeout )
205
237
}
238
+ }
206
239
207
- let update = ( ) => {
208
- if ( ! this . _mounted ) {
209
- return
210
- }
211
-
212
- this . setState ( {
213
- error : res . error ,
214
- loaded : res . loaded ,
215
- loading : res . loading
216
- } )
217
-
240
+ this . _res . promise
241
+ . then ( ( ) => {
242
+ this . _update ( )
218
243
this . _clearTimeouts ( )
219
- }
220
-
221
- res . promise
222
- . then ( ( ) => {
223
- update ( )
224
- } )
225
- // eslint-disable-next-line handle-callback-err
226
- . catch ( err => {
227
- update ( )
228
- } )
229
- }
244
+ } )
245
+ // eslint-disable-next-line handle-callback-err
246
+ . catch ( err => {
247
+ this . _update ( )
248
+ this . _clearTimeouts ( )
249
+ } )
250
+ }
230
251
231
- componentWillUnmount ( ) {
232
- this . _mounted = false
233
- this . _clearTimeouts ( )
252
+ _update ( partial ) {
253
+ this . _state = {
254
+ ...this . _state ,
255
+ ...partial
234
256
}
257
+ this . _callbacks . forEach ( callback => callback ( ) )
258
+ }
235
259
236
- _clearTimeouts ( ) {
237
- clearTimeout ( this . _delay )
238
- clearTimeout ( this . _timeout )
239
- }
260
+ _clearTimeouts ( ) {
261
+ clearTimeout ( this . _delay )
262
+ clearTimeout ( this . _timeout )
263
+ }
240
264
241
- retry = ( ) => {
242
- this . setState ( { error : null , loading : true , timedOut : false } )
243
- res = loadFn ( opts . loader )
244
- this . _loadModule ( )
265
+ getCurrentValue ( ) {
266
+ return {
267
+ ...this . _state ,
268
+ error : this . _res . error ,
269
+ loaded : this . _res . loaded ,
270
+ loading : this . _res . loading
245
271
}
272
+ }
246
273
247
- render ( ) {
248
- if ( this . state . loading || this . state . error ) {
249
- return React . createElement ( opts . loading , {
250
- isLoading : this . state . loading ,
251
- pastDelay : this . state . pastDelay ,
252
- timedOut : this . state . timedOut ,
253
- error : this . state . error ,
254
- retry : this . retry
255
- } )
256
- } else if ( this . state . loaded ) {
257
- return opts . render ( this . state . loaded , this . props )
258
- } else {
259
- return null
260
- }
274
+ subscribe ( callback ) {
275
+ this . _callbacks . add ( callback )
276
+ return ( ) => {
277
+ this . _callbacks . delete ( callback )
261
278
}
262
279
}
263
280
}
0 commit comments