-
-
Notifications
You must be signed in to change notification settings - Fork 22
/
preload.mjs
149 lines (135 loc) · 5.07 KB
/
preload.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
* Recursively preloads [`Query`]{@link Query} components that have the
* `loadOnMount` prop in a React element tree. Useful for server side rendering
* (SSR) or to preload components for a better user experience when they mount.
* @kind function
* @name preload
* @param {ReactElement} element A React virtual DOM element.
* @returns {Promise} Resolves once loading is done and cache is ready to be exported from the [`GraphQL`]{@link GraphQL} instance. Cache can be imported when constructing new [`GraphQL`]{@link GraphQL} instances.
* @example <caption>An async SSR function that returns a HTML string and cache JSON for client hydration.</caption>
* ```jsx
* import { GraphQL, preload, Provider } from 'graphql-react'
* import { renderToString } from 'react-dom/server'
* import { App } from './components'
*
* const graphql = new GraphQL()
* const page = (
* <Provider value={graphql}>
* <App />
* </Provider>
* )
*
* export async function ssr() {
* await preload(page)
* return {
* cache: JSON.stringify(graphql.cache),
* html: renderToString(page)
* }
* }
* ```
*/
export function preload(element) {
/**
* @kind function
* @name preload~recursePreload
* @param {ReactElement} rootElement A React virtual DOM element.
* @param {Object} [rootLegacyContext={}] Legacy React context for the root element and children.
* @param {boolean} [loadRoot=true] Should the root element be loaded.
* @returns {Promise} Resolves once loading is done.
* @ignore
*/
const recursePreload = (
rootElement,
rootLegacyContext = {},
loadRoot = true
) => {
const loading = []
/**
* @kind function
* @name preload~recursePreload~recurse
* @param {ReactElement} element A React virtual DOM element.
* @param {Object} [legacyContext] Legacy React context for the element and children.
* @ignore
*/
const recurse = (element, legacyContext) => {
if (!element) return
if (Array.isArray(element)) {
element.forEach(item => recurse(item, legacyContext))
return
}
if (
// The element is not a childless string or number and…
element.type &&
// …It’s a context consumer or a functional/class component…
(element.type.Consumer || typeof element.type === 'function')
) {
// Determine the component props.
const props = { ...element.type.defaultProps, ...element.props }
if (element.type.Consumer)
// Context consumer element.
recurse(
element.props.children(element.type.currentValue),
legacyContext
)
else if (
// The element is a class component…
element.type.prototype &&
(element.type.prototype.isReactComponent ||
element.type.prototype.isPureReactComponent)
) {
const instance = new element.type(props, legacyContext)
// Match React API for default state.
instance.state = instance.state || null
// Support setState.
instance.setState = newState => {
if (typeof newState === 'function')
newState = newState(instance.state, instance.props)
instance.state = { ...instance.state, ...newState }
}
// Deprecated componentWillMount and legacy context APIs must be
// supported until removal from React, likely in v17:
// https://github.com/facebook/react/issues/12152
// Support componentWillMount.
if (instance.componentWillMount) instance.componentWillMount()
// Support legacy context.
if (instance.getChildContext)
legacyContext = {
...legacyContext,
...instance.getChildContext()
}
if (
// The element is a GraphQL query component and…
instance.constructor.name === 'GraphQLQuery' &&
// …It’s to load on mount and…
element.props.loadOnMount &&
// …It’s not a root query already loaded…
(element !== rootElement || loadRoot)
)
loading.push(
// Load this query.
instance.load().then(() =>
// Preload children, without reloading this query as the root.
recursePreload(element, legacyContext, false)
)
)
else recurse(instance.render(), legacyContext)
}
// The element is a functional component…
else recurse(element.type(props), legacyContext)
} else if (
// The element is a context provider or DOM element and…
element.props &&
// …It has children…
element.props.children
) {
// If the element is a context provider first set the value.
if (element.type._context)
element.type._context.currentValue = element.props.value
recurse(element.props.children, legacyContext)
}
}
recurse(rootElement, rootLegacyContext)
return Promise.all(loading)
}
return recursePreload(element)
}