UNPKG

11.8 kBJavaScriptView Raw
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 *
8 * @format
9 */
10// flowlint ambiguous-object-type:error
11'use strict';
12
13var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
14
15var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));
16
17var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose"));
18
19var React = require('react');
20
21var ReactRelayContext = require('./ReactRelayContext');
22
23var ReactRelayQueryFetcher = require('./ReactRelayQueryFetcher');
24
25var areEqual = require("fbjs/lib/areEqual");
26
27var _require = require('relay-runtime'),
28 createOperationDescriptor = _require.createOperationDescriptor,
29 deepFreeze = _require.deepFreeze,
30 getRequest = _require.getRequest;
31
32/**
33 * React may double-fire the constructor, and we call 'fetch' in the
34 * constructor. If a request is already in flight from a previous call to the
35 * constructor, just reuse the query fetcher and wait for the response.
36 */
37var requestCache = {};
38
39/**
40 * @public
41 *
42 * Orchestrates fetching and rendering data for a single view or view hierarchy:
43 * - Fetches the query/variables using the given network implementation.
44 * - Normalizes the response(s) to that query, publishing them to the given
45 * store.
46 * - Renders the pending/fail/success states with the provided render function.
47 * - Subscribes for updates to the root data and re-renders with any changes.
48 */
49var ReactRelayQueryRenderer = /*#__PURE__*/function (_React$Component) {
50 (0, _inheritsLoose2["default"])(ReactRelayQueryRenderer, _React$Component);
51
52 function ReactRelayQueryRenderer(props) {
53 var _this;
54
55 _this = _React$Component.call(this, props) || this; // Callbacks are attached to the current instance and shared with static
56 // lifecyles by bundling with state. This is okay to do because the
57 // callbacks don't change in reaction to props. However we should not
58 // "leak" them before mounting (since we would be unable to clean up). For
59 // that reason, we define them as null initially and fill them in after
60 // mounting to avoid leaking memory.
61
62 var retryCallbacks = {
63 handleDataChange: null,
64 handleRetryAfterError: null
65 };
66 var queryFetcher;
67 var requestCacheKey;
68
69 if (props.query) {
70 var query = props.query;
71 var request = getRequest(query);
72 requestCacheKey = getRequestCacheKey(request.params, props.variables);
73 queryFetcher = requestCache[requestCacheKey] ? requestCache[requestCacheKey].queryFetcher : new ReactRelayQueryFetcher();
74 } else {
75 queryFetcher = new ReactRelayQueryFetcher();
76 }
77
78 _this.state = (0, _objectSpread2["default"])({
79 prevPropsEnvironment: props.environment,
80 prevPropsVariables: props.variables,
81 prevQuery: props.query,
82 queryFetcher: queryFetcher,
83 retryCallbacks: retryCallbacks
84 }, fetchQueryAndComputeStateFromProps(props, queryFetcher, retryCallbacks, requestCacheKey));
85 return _this;
86 }
87
88 ReactRelayQueryRenderer.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
89 if (prevState.prevQuery !== nextProps.query || prevState.prevPropsEnvironment !== nextProps.environment || !areEqual(prevState.prevPropsVariables, nextProps.variables)) {
90 var query = nextProps.query;
91 var prevSelectionReferences = prevState.queryFetcher.getSelectionReferences();
92 prevState.queryFetcher.disposeRequest();
93 var queryFetcher;
94
95 if (query) {
96 var request = getRequest(query);
97 var requestCacheKey = getRequestCacheKey(request.params, nextProps.variables);
98 queryFetcher = requestCache[requestCacheKey] ? requestCache[requestCacheKey].queryFetcher : new ReactRelayQueryFetcher(prevSelectionReferences);
99 } else {
100 queryFetcher = new ReactRelayQueryFetcher(prevSelectionReferences);
101 }
102
103 return (0, _objectSpread2["default"])({
104 prevQuery: nextProps.query,
105 prevPropsEnvironment: nextProps.environment,
106 prevPropsVariables: nextProps.variables,
107 queryFetcher: queryFetcher
108 }, fetchQueryAndComputeStateFromProps(nextProps, queryFetcher, prevState.retryCallbacks // passing no requestCacheKey will cause it to be recalculated internally
109 // and we want the updated requestCacheKey, since variables may have changed
110 ));
111 }
112
113 return null;
114 };
115
116 var _proto = ReactRelayQueryRenderer.prototype;
117
118 _proto.componentDidMount = function componentDidMount() {
119 var _this2 = this;
120
121 var _this$state = this.state,
122 retryCallbacks = _this$state.retryCallbacks,
123 queryFetcher = _this$state.queryFetcher,
124 requestCacheKey = _this$state.requestCacheKey;
125
126 if (requestCacheKey) {
127 delete requestCache[requestCacheKey];
128 }
129
130 retryCallbacks.handleDataChange = function (params) {
131 var error = params.error == null ? null : params.error;
132 var snapshot = params.snapshot == null ? null : params.snapshot;
133
134 _this2.setState(function (prevState) {
135 var prevRequestCacheKey = prevState.requestCacheKey;
136
137 if (prevRequestCacheKey) {
138 delete requestCache[prevRequestCacheKey];
139 } // Don't update state if nothing has changed.
140
141
142 if (snapshot === prevState.snapshot && error === prevState.error) {
143 return null;
144 }
145
146 return {
147 renderProps: getRenderProps(error, snapshot, prevState.queryFetcher, prevState.retryCallbacks),
148 snapshot: snapshot,
149 requestCacheKey: null
150 };
151 });
152 };
153
154 retryCallbacks.handleRetryAfterError = function (error) {
155 return _this2.setState(function (prevState) {
156 var prevRequestCacheKey = prevState.requestCacheKey;
157
158 if (prevRequestCacheKey) {
159 delete requestCache[prevRequestCacheKey];
160 }
161
162 return {
163 renderProps: getLoadingRenderProps(),
164 requestCacheKey: null
165 };
166 });
167 }; // Re-initialize the ReactRelayQueryFetcher with callbacks.
168 // If data has changed since constructions, this will re-render.
169
170
171 if (this.props.query) {
172 queryFetcher.setOnDataChange(retryCallbacks.handleDataChange);
173 }
174 };
175
176 _proto.componentDidUpdate = function componentDidUpdate() {
177 // We don't need to cache the request after the component commits
178 var requestCacheKey = this.state.requestCacheKey;
179
180 if (requestCacheKey) {
181 delete requestCache[requestCacheKey]; // HACK
182
183 delete this.state.requestCacheKey;
184 }
185 };
186
187 _proto.componentWillUnmount = function componentWillUnmount() {
188 this.state.queryFetcher.dispose();
189 };
190
191 _proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
192 return nextProps.render !== this.props.render || nextState.renderProps !== this.state.renderProps;
193 };
194
195 _proto.render = function render() {
196 var _this$state2 = this.state,
197 renderProps = _this$state2.renderProps,
198 relayContext = _this$state2.relayContext; // Note that the root fragment results in `renderProps.props` is already
199 // frozen by the store; this call is to freeze the renderProps object and
200 // error property if set.
201
202 if (process.env.NODE_ENV !== "production") {
203 deepFreeze(renderProps);
204 }
205
206 return /*#__PURE__*/React.createElement(ReactRelayContext.Provider, {
207 value: relayContext
208 }, this.props.render(renderProps));
209 };
210
211 return ReactRelayQueryRenderer;
212}(React.Component);
213
214function getLoadingRenderProps() {
215 return {
216 error: null,
217 props: null,
218 // `props: null` indicates that the data is being fetched (i.e. loading)
219 retry: null
220 };
221}
222
223function getEmptyRenderProps() {
224 return {
225 error: null,
226 props: {},
227 // `props: {}` indicates no data available
228 retry: null
229 };
230}
231
232function getRenderProps(error, snapshot, queryFetcher, retryCallbacks) {
233 return {
234 error: error ? error : null,
235 props: snapshot ? snapshot.data : null,
236 retry: function retry(cacheConfigOverride) {
237 var syncSnapshot = queryFetcher.retry(cacheConfigOverride);
238
239 if (syncSnapshot && typeof retryCallbacks.handleDataChange === 'function') {
240 retryCallbacks.handleDataChange({
241 snapshot: syncSnapshot
242 });
243 } else if (error && typeof retryCallbacks.handleRetryAfterError === 'function') {
244 // If retrying after an error and no synchronous result available,
245 // reset the render props
246 retryCallbacks.handleRetryAfterError(error);
247 }
248 }
249 };
250}
251
252function getRequestCacheKey(request, variables) {
253 return JSON.stringify({
254 id: request.cacheID ? request.cacheID : request.id,
255 variables: variables
256 });
257}
258
259function fetchQueryAndComputeStateFromProps(props, queryFetcher, retryCallbacks, requestCacheKey) {
260 var environment = props.environment,
261 query = props.query,
262 variables = props.variables,
263 cacheConfig = props.cacheConfig;
264 var genericEnvironment = environment;
265
266 if (query) {
267 var request = getRequest(query);
268 var operation = createOperationDescriptor(request, variables, cacheConfig);
269 var relayContext = {
270 environment: genericEnvironment
271 };
272
273 if (typeof requestCacheKey === 'string' && requestCache[requestCacheKey]) {
274 // This same request is already in flight.
275 var snapshot = requestCache[requestCacheKey].snapshot;
276
277 if (snapshot) {
278 // Use the cached response
279 return {
280 error: null,
281 relayContext: relayContext,
282 renderProps: getRenderProps(null, snapshot, queryFetcher, retryCallbacks),
283 snapshot: snapshot,
284 requestCacheKey: requestCacheKey
285 };
286 } else {
287 // Render loading state
288 return {
289 error: null,
290 relayContext: relayContext,
291 renderProps: getLoadingRenderProps(),
292 snapshot: null,
293 requestCacheKey: requestCacheKey
294 };
295 }
296 }
297
298 try {
299 var storeSnapshot = queryFetcher.lookupInStore(genericEnvironment, operation, props.fetchPolicy);
300 var querySnapshot = queryFetcher.fetch({
301 environment: genericEnvironment,
302 onDataChange: retryCallbacks.handleDataChange,
303 operation: operation
304 }); // Use network data first, since it may be fresher
305
306 var _snapshot = querySnapshot || storeSnapshot; // cache the request to avoid duplicate requests
307
308
309 requestCacheKey = requestCacheKey || getRequestCacheKey(request.params, props.variables);
310 requestCache[requestCacheKey] = {
311 queryFetcher: queryFetcher,
312 snapshot: _snapshot
313 };
314
315 if (!_snapshot) {
316 return {
317 error: null,
318 relayContext: relayContext,
319 renderProps: getLoadingRenderProps(),
320 snapshot: null,
321 requestCacheKey: requestCacheKey
322 };
323 }
324
325 return {
326 error: null,
327 relayContext: relayContext,
328 renderProps: getRenderProps(null, _snapshot, queryFetcher, retryCallbacks),
329 snapshot: _snapshot,
330 requestCacheKey: requestCacheKey
331 };
332 } catch (error) {
333 return {
334 error: error,
335 relayContext: relayContext,
336 renderProps: getRenderProps(error, null, queryFetcher, retryCallbacks),
337 snapshot: null,
338 requestCacheKey: requestCacheKey
339 };
340 }
341 } else {
342 queryFetcher.dispose();
343 var _relayContext = {
344 environment: genericEnvironment
345 };
346 return {
347 error: null,
348 relayContext: _relayContext,
349 renderProps: getEmptyRenderProps(),
350 requestCacheKey: null // if there is an error, don't cache request
351
352 };
353 }
354}
355
356module.exports = ReactRelayQueryRenderer;
\No newline at end of file