@@ -22,6 +22,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
2222// Modified to be compatible with webpack 4 / Next.js
2323
2424import React from 'react'
25+ import { useSubscription } from 'use-subscription'
2526import { LoadableContext } from './loadable-context'
2627
2728const ALL_INITIALIZERS = [ ]
@@ -151,113 +152,129 @@ function createLoadableComponent (loadFn, options) {
151152 } )
152153 }
153154
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 : callback => subscription . subscribe ( callback )
165165 }
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+ } )
166185 }
167186
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
170199 }
200+ }
171201
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'
179204
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+ }
186207
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 ( )
190214
215+ this . _state = {
216+ pastDelay : false ,
217+ timedOut : false
218+ }
219+
220+ if ( res . loading ) {
191221 if ( typeof opts . delay === 'number' ) {
192222 if ( opts . delay === 0 ) {
193- this . setState ( { pastDelay : true } )
223+ this . _state . pastDelay = true
194224 } else {
195225 this . _delay = setTimeout ( ( ) => {
196- this . setState ( { pastDelay : true } )
226+ this . _update ( {
227+ pastDelay : true
228+ } )
197229 } , opts . delay )
198230 }
199231 }
200232
201233 if ( typeof opts . timeout === 'number' ) {
202234 this . _timeout = setTimeout ( ( ) => {
203- this . setState ( { timedOut : true } )
235+ this . _update ( { timedOut : true } )
204236 } , opts . timeout )
205237 }
238+ }
206239
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 ( )
218243 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+ }
230251
231- componentWillUnmount ( ) {
232- this . _mounted = false
233- this . _clearTimeouts ( )
252+ _update ( partial ) {
253+ this . _state = {
254+ ...this . _state ,
255+ ...partial
234256 }
257+ this . _callbacks . forEach ( callback => callback ( ) )
258+ }
235259
236- _clearTimeouts ( ) {
237- clearTimeout ( this . _delay )
238- clearTimeout ( this . _timeout )
239- }
260+ _clearTimeouts ( ) {
261+ clearTimeout ( this . _delay )
262+ clearTimeout ( this . _timeout )
263+ }
240264
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
245271 }
272+ }
246273
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 )
261278 }
262279 }
263280}
0 commit comments