Skip to content

Commit 947af5d

Browse files
committed
Optimize and cleanup the hooks implementation of the Loadable component, add commetns
1 parent 7153987 commit 947af5d

File tree

1 file changed

+49
-37
lines changed

1 file changed

+49
-37
lines changed

react-loadable-client.js

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EJSON } from 'meteor/ejson'
2-
import React, { useState, useEffect, useRef } from 'react'
2+
import React, { useState, useEffect, useReducer } from 'react'
33

44
const INITIALIZERS = []
55
const INITIALIZERS_BY_MODULE = {}
@@ -36,6 +36,10 @@ function resolveRender (loaded, props) {
3636
return React.createElement(resolve(loaded), props)
3737
}
3838

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+
3943
/**
4044
* Creates a "Loadable" component at startup, which will persist for the length of the program.
4145
*/
@@ -44,13 +48,13 @@ export const Loadable = ({ render = resolveRender, meteor, loader, loading, dela
4448
throw new Error('react-loadable requires a `loading` component')
4549
}
4650

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
4953
function init () {
50-
if (!res) {
51-
res = load(loader)
54+
if (!status) {
55+
status = load(loader)
5256
}
53-
return res.promise
57+
return status.promise
5458
}
5559

5660
// Store all the INITIALIZERS for later use
@@ -62,54 +66,61 @@ export const Loadable = ({ render = resolveRender, meteor, loader, loading, dela
6266
}
6367

6468
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+
}
6675

67-
const [pastDelay, setPastDelay] = useState(false)
76+
const [pastDelay, setPastDelay] = useState(delay === 0)
6877
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)
8079

80+
const wasLoading = status.loading
8181
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+
}
8390
return
8491
}
8592

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(() => {
88103
setPastDelay(true)
89-
} else {
90-
refs._delay = setTimeout(() => {
91-
setPastDelay(true)
92-
}, delay)
93-
}
104+
}, delay)
94105
}
95106

96107
if (typeof timeout === 'number') {
97-
refs._timeout = setTimeout(() => {
108+
tidTimeout = setTimeout(() => {
98109
setTimedOut(true)
99110
}, timeout)
100111
}
101112

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
108115

116+
const update = () => {
109117
_clearTimeouts()
118+
if (mounted) {
119+
forceUpdate()
120+
}
110121
}
111122

112-
res.promise
123+
status.promise
113124
.then(() => {
114125
update()
115126
})
@@ -119,14 +130,15 @@ export const Loadable = ({ render = resolveRender, meteor, loader, loading, dela
119130
})
120131

121132
return () => {
133+
mounted = false
122134
_clearTimeouts()
123135
}
124136
}, [])
125137

126138
// render
127-
if (status.isLoading || status.error) {
139+
if (status.loading || status.error) {
128140
return React.createElement(loading, {
129-
isLoading: status.isLoading,
141+
isLoading: status.loading,
130142
pastDelay: pastDelay,
131143
timedOut: timedOut,
132144
error: status.error

0 commit comments

Comments
 (0)