UNPKG

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