UNPKG

9.77 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 _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
16
17var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
18
19var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose"));
20
21var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
22
23function 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; }
24
25function _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; }
26
27var React = require('react');
28
29var areEqual = require("fbjs/lib/areEqual");
30
31var buildReactRelayContainer = require('./buildReactRelayContainer');
32
33var getRootVariablesForFragments = require('./getRootVariablesForFragments');
34
35var _require = require('./ReactRelayContainerUtils'),
36 getContainerName = _require.getContainerName;
37
38var _require2 = require('./RelayContext'),
39 assertRelayContext = _require2.assertRelayContext;
40
41var _require3 = require('relay-runtime'),
42 createFragmentSpecResolver = _require3.createFragmentSpecResolver,
43 getDataIDsFromObject = _require3.getDataIDsFromObject,
44 isScalarAndEqual = _require3.isScalarAndEqual;
45
46/**
47 * Composes a React component class, returning a new class that intercepts
48 * props, resolving them with the provided fragments and subscribing for
49 * updates.
50 */
51function createContainerWithFragments(Component, fragments) {
52 var _class, _temp;
53
54 var containerName = getContainerName(Component);
55 return _temp = _class = /*#__PURE__*/function (_React$Component) {
56 (0, _inheritsLoose2["default"])(_class, _React$Component);
57
58 function _class(props) {
59 var _this;
60
61 _this = _React$Component.call(this, props) || this;
62 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleFragmentDataUpdate", function () {
63 var resolverFromThisUpdate = _this.state.resolver;
64
65 _this.setState(function (updatedState) {
66 return (// If this event belongs to the current data source, update.
67 // Otherwise we should ignore it.
68 resolverFromThisUpdate === updatedState.resolver ? {
69 data: updatedState.resolver.resolve(),
70 relayProp: getRelayProp(updatedState.relayProp.environment)
71 } : null
72 );
73 });
74 });
75 var relayContext = assertRelayContext(props.__relayContext); // Do not provide a subscription/callback here.
76 // It is possible for this render to be interrupted or aborted,
77 // In which case the subscription would cause a leak.
78 // We will add the subscription in componentDidMount().
79
80 var resolver = createFragmentSpecResolver(relayContext, containerName, fragments, props);
81 _this.state = {
82 data: resolver.resolve(),
83 prevProps: props,
84 prevPropsContext: relayContext,
85 relayProp: getRelayProp(relayContext.environment),
86 resolver: resolver
87 };
88 return _this;
89 }
90 /**
91 * When new props are received, read data for the new props and subscribe
92 * for updates. Props may be the same in which case previous data and
93 * subscriptions can be reused.
94 */
95
96
97 _class.getDerivedStateFromProps = function getDerivedStateFromProps(nextProps, prevState) {
98 // Any props change could impact the query, so we mirror props in state.
99 // This is an unusual pattern, but necessary for this container usecase.
100 var prevProps = prevState.prevProps;
101 var relayContext = assertRelayContext(nextProps.__relayContext);
102 var prevIDs = getDataIDsFromObject(fragments, prevProps);
103 var nextIDs = getDataIDsFromObject(fragments, nextProps);
104 var resolver = prevState.resolver; // If the environment has changed or props point to new records then
105 // previously fetched data and any pending fetches no longer apply:
106 // - Existing references are on the old environment.
107 // - Existing references are based on old variables.
108 // - Pending fetches are for the previous records.
109
110 if (prevState.prevPropsContext.environment !== relayContext.environment || !areEqual(prevIDs, nextIDs)) {
111 // Do not provide a subscription/callback here.
112 // It is possible for this render to be interrupted or aborted,
113 // In which case the subscription would cause a leak.
114 // We will add the subscription in componentDidUpdate().
115 resolver = createFragmentSpecResolver(relayContext, containerName, fragments, nextProps);
116 return {
117 data: resolver.resolve(),
118 prevPropsContext: relayContext,
119 prevProps: nextProps,
120 relayProp: getRelayProp(relayContext.environment),
121 resolver: resolver
122 };
123 } else {
124 resolver.setProps(nextProps);
125 var data = resolver.resolve();
126
127 if (data !== prevState.data) {
128 return {
129 data: data,
130 prevProps: nextProps,
131 prevPropsContext: relayContext,
132 relayProp: getRelayProp(relayContext.environment)
133 };
134 }
135 }
136
137 return null;
138 };
139
140 var _proto = _class.prototype;
141
142 _proto.componentDidMount = function componentDidMount() {
143 this._subscribeToNewResolver();
144
145 this._rerenderIfStoreHasChanged();
146 };
147
148 _proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) {
149 if (this.state.resolver !== prevState.resolver) {
150 prevState.resolver.dispose();
151
152 this._subscribeToNewResolver();
153 }
154
155 this._rerenderIfStoreHasChanged();
156 };
157
158 _proto.componentWillUnmount = function componentWillUnmount() {
159 this.state.resolver.dispose();
160 };
161
162 _proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
163 // Short-circuit if any Relay-related data has changed
164 if (nextState.data !== this.state.data) {
165 return true;
166 } // Otherwise, for convenience short-circuit if all non-Relay props
167 // are scalar and equal
168
169
170 var keys = Object.keys(nextProps);
171
172 for (var ii = 0; ii < keys.length; ii++) {
173 var _key = keys[ii];
174
175 if (_key === '__relayContext') {
176 if (nextState.prevPropsContext.environment !== this.state.prevPropsContext.environment) {
177 return true;
178 }
179 } else {
180 if (!fragments.hasOwnProperty(_key) && !isScalarAndEqual(nextProps[_key], this.props[_key])) {
181 return true;
182 }
183 }
184 }
185
186 return false;
187 }
188 /**
189 * Render new data for the existing props/context.
190 */
191 ;
192
193 _proto._rerenderIfStoreHasChanged = function _rerenderIfStoreHasChanged() {
194 var _this$state = this.state,
195 data = _this$state.data,
196 resolver = _this$state.resolver; // External values could change between render and commit.
197 // Check for this case, even though it requires an extra store read.
198
199 var maybeNewData = resolver.resolve();
200
201 if (data !== maybeNewData) {
202 this.setState({
203 data: maybeNewData
204 });
205 }
206 };
207
208 _proto._subscribeToNewResolver = function _subscribeToNewResolver() {
209 var resolver = this.state.resolver; // Event listeners are only safe to add during the commit phase,
210 // So they won't leak if render is interrupted or errors.
211
212 resolver.setCallback(this._handleFragmentDataUpdate);
213 };
214
215 _proto.render = function render() {
216 var _this$props = this.props,
217 componentRef = _this$props.componentRef,
218 _ = _this$props.__relayContext,
219 props = (0, _objectWithoutPropertiesLoose2["default"])(_this$props, ["componentRef", "__relayContext"]);
220 return React.createElement(Component, _objectSpread({}, props, {}, this.state.data, {
221 ref: componentRef,
222 relay: this.state.relayProp
223 }));
224 };
225
226 return _class;
227 }(React.Component), (0, _defineProperty2["default"])(_class, "displayName", containerName), _temp;
228}
229
230function getRelayProp(environment) {
231 return {
232 environment: environment
233 };
234}
235/**
236 * Wrap the basic `createContainer()` function with logic to adapt to the
237 * `context.relay.environment` in which it is rendered. Specifically, the
238 * extraction of the environment-specific version of fragments in the
239 * `fragmentSpec` is memoized once per environment, rather than once per
240 * instance of the container constructed/rendered.
241 */
242
243
244function createContainer(Component, fragmentSpec) {
245 return buildReactRelayContainer(Component, fragmentSpec, createContainerWithFragments);
246}
247
248module.exports = {
249 createContainer: createContainer
250};
\No newline at end of file