UNPKG

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