Skip to content

Commit add8fde

Browse files
committed
Support Concurrent Mode in Loadable
1 parent 5bce442 commit add8fde

File tree

3 files changed

+102
-79
lines changed

3 files changed

+102
-79
lines changed

packages/next/next-server/lib/loadable.js

Lines changed: 96 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2424
import React from 'react'
25+
import { useSubscription } from 'use-subscription'
2526
import { LoadableContext } from './loadable-context'
2627

2728
const 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: () => subscription.subscribe()
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
}

packages/next/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
"terser": "4.0.0",
122122
"unfetch": "4.1.0",
123123
"url": "0.11.0",
124+
"use-subscription": "1.1.1",
124125
"watchpack": "2.0.0-beta.5",
125126
"webpack": "4.39.0",
126127
"webpack-dev-middleware": "3.7.0",

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14577,6 +14577,11 @@ [email protected], url@^0.11.0:
1457714577
punycode "1.3.2"
1457814578
querystring "0.2.0"
1457914579

14580+
14581+
version "1.1.1"
14582+
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.1.1.tgz#5509363e9bb152c4fb334151d4dceb943beaa7bb"
14583+
integrity sha512-gk4fPTYvNhs6Ia7u8/+K7bM7sZ7O7AMfWtS+zPO8luH+zWuiGgGcrW0hL4MRWZSzXo+4ofNorf87wZwBKz2YdQ==
14584+
1458014585
use@^3.1.0:
1458114586
version "3.1.1"
1458214587
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"

0 commit comments

Comments
 (0)