1
1
import { EJSON } from 'meteor/ejson'
2
- import React , { useState , useEffect , useRef } from 'react'
2
+ import React , { useState , useEffect , useReducer } from 'react'
3
3
4
4
const INITIALIZERS = [ ]
5
5
const INITIALIZERS_BY_MODULE = { }
@@ -36,6 +36,10 @@ function resolveRender (loaded, props) {
36
36
return React . createElement ( resolve ( loaded ) , props )
37
37
}
38
38
39
+ // Used to create a forceUpdate from useReducer. Forces update by
40
+ // incrementing a number whenever the dispatch method is invoked.
41
+ const fur = x => x + 1
42
+
39
43
/**
40
44
* Creates a "Loadable" component at startup, which will persist for the length of the program.
41
45
*/
@@ -44,13 +48,13 @@ export const Loadable = ({ render = resolveRender, meteor, loader, loading, dela
44
48
throw new Error ( 'react-loadable requires a `loading` component' )
45
49
}
46
50
47
- // Gets ready to load the module
48
- let res = null
51
+ // Gets ready to load the module, and maintain status
52
+ let status = null
49
53
function init ( ) {
50
- if ( ! res ) {
51
- res = load ( loader )
54
+ if ( ! status ) {
55
+ status = load ( loader )
52
56
}
53
- return res . promise
57
+ return status . promise
54
58
}
55
59
56
60
// Store all the INITIALIZERS for later use
@@ -62,54 +66,61 @@ export const Loadable = ({ render = resolveRender, meteor, loader, loading, dela
62
66
}
63
67
64
68
function Loadable ( props ) {
65
- if ( ! res || ! res . loaded ) init ( )
69
+ // We are starting load as early as possible. We are counting
70
+ // on Meteor to avoid problems if this happens to get fired
71
+ // off more than once in concurrent mode.
72
+ if ( ! status ) {
73
+ init ( )
74
+ }
66
75
67
- const [ pastDelay , setPastDelay ] = useState ( false )
76
+ const [ pastDelay , setPastDelay ] = useState ( delay === 0 )
68
77
const [ timedOut , setTimedOut ] = useState ( false )
69
- const [ status , setStatus ] = useState ( {
70
- inError : res . error ,
71
- isLoading : res . loading ,
72
- loaded : res . loaded
73
- } )
74
- const { current : refs } = useRef ( { } )
75
-
76
- const _clearTimeouts = ( ) => {
77
- clearTimeout ( refs . _delay )
78
- clearTimeout ( refs . _timeout )
79
- }
78
+ const [ , forceUpdate ] = useReducer ( fur , 0 )
80
79
80
+ const wasLoading = status . loading
81
81
useEffect ( ( ) => {
82
- if ( ! status . isLoading ) {
82
+ // If status.loading is false, then we either have an error
83
+ // state, or have loaded successfully. In either case, we
84
+ // don't need to set up any timeouts, or watch for updates.
85
+ if ( ! status . loading ) {
86
+ // It's possible loading completed between render and commit.
87
+ if ( wasLoading ) {
88
+ forceUpdate ( )
89
+ }
83
90
return
84
91
}
85
92
86
- if ( typeof delay === 'number' ) {
87
- if ( delay === 0 ) {
93
+ // If we got this far, we need to set up the two timeouts
94
+ let tidDelay
95
+ let tidTimeout
96
+ const _clearTimeouts = ( ) => {
97
+ if ( tidDelay ) clearTimeout ( tidDelay )
98
+ if ( tidTimeout ) clearTimeout ( tidTimeout )
99
+ }
100
+
101
+ if ( typeof delay === 'number' && delay > 0 ) {
102
+ tidDelay = setTimeout ( ( ) => {
88
103
setPastDelay ( true )
89
- } else {
90
- refs . _delay = setTimeout ( ( ) => {
91
- setPastDelay ( true )
92
- } , delay )
93
- }
104
+ } , delay )
94
105
}
95
106
96
107
if ( typeof timeout === 'number' ) {
97
- refs . _timeout = setTimeout ( ( ) => {
108
+ tidTimeout = setTimeout ( ( ) => {
98
109
setTimedOut ( true )
99
110
} , timeout )
100
111
}
101
112
102
- const update = ( ) => {
103
- setStatus ( {
104
- error : res . error ,
105
- loaded : res . loaded ,
106
- loading : res . loading
107
- } )
113
+ // Use to avoid updating state after unmount.
114
+ let mounted = true
108
115
116
+ const update = ( ) => {
109
117
_clearTimeouts ( )
118
+ if ( mounted ) {
119
+ forceUpdate ( )
120
+ }
110
121
}
111
122
112
- res . promise
123
+ status . promise
113
124
. then ( ( ) => {
114
125
update ( )
115
126
} )
@@ -119,14 +130,15 @@ export const Loadable = ({ render = resolveRender, meteor, loader, loading, dela
119
130
} )
120
131
121
132
return ( ) => {
133
+ mounted = false
122
134
_clearTimeouts ( )
123
135
}
124
136
} , [ ] )
125
137
126
138
// render
127
- if ( status . isLoading || status . error ) {
139
+ if ( status . loading || status . error ) {
128
140
return React . createElement ( loading , {
129
- isLoading : status . isLoading ,
141
+ isLoading : status . loading ,
130
142
pastDelay : pastDelay ,
131
143
timedOut : timedOut ,
132
144
error : status . error
0 commit comments