@@ -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 = [ ]
@@ -121,13 +122,19 @@ function createLoadableComponent (loadFn, options) {
121122 options
122123 )
123124
124- let res = null
125+ let subscription = null
125126
126127 function init ( ) {
127- if ( ! res ) {
128- res = loadFn ( opts . loader )
128+ if ( ! subscription ) {
129+ const sub = new LoadableSubscription ( loadFn , opts )
130+ subscription = {
131+ getCurrentValue : sub . getCurrentValue . bind ( sub ) ,
132+ subscribe : sub . subscribe . bind ( sub ) ,
133+ retry : sub . retry . bind ( sub ) ,
134+ promise : sub . promise . bind ( sub )
135+ }
129136 }
130- return res . promise
137+ return subscription . promise ( )
131138 }
132139
133140 // Server only
@@ -151,113 +158,128 @@ function createLoadableComponent (loadFn, options) {
151158 } )
152159 }
153160
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
165- }
166- }
161+ const LoadableComponent = ( props , ref ) => {
162+ init ( )
163+
164+ const context = React . useContext ( LoadableContext )
165+ const state = useSubscription ( subscription )
166+
167+ React . useImperativeHandle ( ref , ( ) => ( {
168+ retry : subscription . retry
169+ } ) )
167170
168- static preload ( ) {
169- return init ( )
171+ if ( context && Array . isArray ( opts . modules ) ) {
172+ opts . modules . forEach ( moduleName => {
173+ context ( moduleName )
174+ } )
170175 }
171176
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 ( )
177+ if ( state . loading || state . error ) {
178+ return React . createElement ( opts . loading , {
179+ isLoading : state . loading ,
180+ pastDelay : state . pastDelay ,
181+ timedOut : state . timedOut ,
182+ error : state . error ,
183+ retry : subscription . retry
184+ } )
185+ } else if ( state . loaded ) {
186+ return opts . render ( state . loaded , props )
187+ } else {
188+ return null
178189 }
190+ }
179191
180- _loadModule ( ) {
181- if ( this . context && Array . isArray ( opts . modules ) ) {
182- opts . modules . forEach ( moduleName => {
183- this . context ( moduleName )
184- } )
185- }
192+ LoadableComponent . preload = ( ) => init ( )
193+ LoadableComponent . displayName = 'LoadableComponent'
186194
187- if ( ! res . loading ) {
188- return
189- }
195+ return React . forwardRef ( LoadableComponent )
196+ }
190197
198+ class LoadableSubscription {
199+ constructor ( loadFn , opts ) {
200+ this . _loadFn = loadFn
201+ this . _opts = opts
202+ this . _callbacks = new Set ( )
203+ this . _delay = null
204+ this . _timeout = null
205+
206+ this . retry ( )
207+ }
208+
209+ promise ( ) {
210+ return this . _res . promise
211+ }
212+
213+ retry ( ) {
214+ this . _clearTimeouts ( )
215+ this . _res = this . _loadFn ( this . _opts . loader )
216+
217+ this . _state = {
218+ pastDelay : false ,
219+ timedOut : false
220+ }
221+
222+ const { _res : res , _opts : opts } = this
223+
224+ if ( res . loading ) {
191225 if ( typeof opts . delay === 'number' ) {
192226 if ( opts . delay === 0 ) {
193- this . setState ( { pastDelay : true } )
227+ this . _state . pastDelay = true
194228 } else {
195229 this . _delay = setTimeout ( ( ) => {
196- this . setState ( { pastDelay : true } )
230+ this . _update ( {
231+ pastDelay : true
232+ } )
197233 } , opts . delay )
198234 }
199235 }
200236
201237 if ( typeof opts . timeout === 'number' ) {
202238 this . _timeout = setTimeout ( ( ) => {
203- this . setState ( { timedOut : true } )
239+ this . _update ( { timedOut : true } )
204240 } , opts . timeout )
205241 }
242+ }
206243
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-
244+ this . _res . promise
245+ . then ( ( ) => {
246+ this . _update ( )
218247 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- }
248+ } )
249+ // eslint-disable-next-line handle-callback-err
250+ . catch ( err => {
251+ this . _update ( )
252+ this . _clearTimeouts ( )
253+ } )
254+ this . _update ( { } )
255+ }
230256
231- componentWillUnmount ( ) {
232- this . _mounted = false
233- this . _clearTimeouts ( )
257+ _update ( partial ) {
258+ this . _state = {
259+ ...this . _state ,
260+ ...partial
234261 }
262+ this . _callbacks . forEach ( callback => callback ( ) )
263+ }
235264
236- _clearTimeouts ( ) {
237- clearTimeout ( this . _delay )
238- clearTimeout ( this . _timeout )
239- }
265+ _clearTimeouts ( ) {
266+ clearTimeout ( this . _delay )
267+ clearTimeout ( this . _timeout )
268+ }
240269
241- retry = ( ) => {
242- this . setState ( { error : null , loading : true , timedOut : false } )
243- res = loadFn ( opts . loader )
244- this . _loadModule ( )
270+ getCurrentValue ( ) {
271+ return {
272+ ...this . _state ,
273+ error : this . _res . error ,
274+ loaded : this . _res . loaded ,
275+ loading : this . _res . loading
245276 }
277+ }
246278
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- }
279+ subscribe ( callback ) {
280+ this . _callbacks . add ( callback )
281+ return ( ) => {
282+ this . _callbacks . delete ( callback )
261283 }
262284 }
263285}
0 commit comments