UNPKG

react-relay

Version:

A framework for building GraphQL-driven React applications.

447 lines (446 loc) • 22.9 kB
'use strict'; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault")["default"]; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized")); var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _objectSpread3 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2")); var _excluded = ["componentRef"], _excluded2 = ["componentRef", "__relayContext", "__rootIsQueryRenderer"], _excluded3 = ["componentRef", "__relayContext", "__rootIsQueryRenderer"]; var buildReactRelayContainer = require('./buildReactRelayContainer'); var getRootVariablesForFragments = require('./getRootVariablesForFragments'); var _require = require('./ReactRelayContainerUtils'), getComponentName = _require.getComponentName, getContainerName = _require.getContainerName; var ReactRelayContext = require('./ReactRelayContext'); var ReactRelayQueryFetcher = require('./ReactRelayQueryFetcher'); var _require2 = require('./RelayContext'), assertRelayContext = _require2.assertRelayContext; var areEqual = require("fbjs/lib/areEqual"); var invariant = require('invariant'); var React = require('react'); var _require3 = require('relay-runtime'), ConnectionInterface = _require3.ConnectionInterface, Observable = _require3.Observable, RelayFeatureFlags = _require3.RelayFeatureFlags, createFragmentSpecResolver = _require3.createFragmentSpecResolver, createOperationDescriptor = _require3.createOperationDescriptor, getDataIDsFromObject = _require3.getDataIDsFromObject, getRequest = _require3.getRequest, getVariablesFromObject = _require3.getVariablesFromObject, isScalarAndEqual = _require3.isScalarAndEqual; var warning = require("fbjs/lib/warning"); var FORWARD = 'forward'; function createGetConnectionFromProps(metadata) { var path = metadata.path; !path ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Unable to synthesize a ' + 'getConnectionFromProps function.') : invariant(false) : void 0; return function (props) { var data = props[metadata.fragmentName]; for (var i = 0; i < path.length; i++) { if (!data || typeof data !== 'object') { return null; } data = data[path[i]]; } return data; }; } function createGetFragmentVariables(metadata) { var countVariable = metadata.count; !countVariable ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Unable to synthesize a ' + 'getFragmentVariables function.') : invariant(false) : void 0; return function (prevVars, totalCount) { return (0, _objectSpread3["default"])((0, _objectSpread3["default"])({}, prevVars), {}, (0, _defineProperty2["default"])({}, countVariable, totalCount)); }; } function findConnectionMetadata(fragments) { var foundConnectionMetadata = null; var isRelayModern = false; for (var fragmentName in fragments) { var fragment = fragments[fragmentName]; var connectionMetadata = fragment.metadata && fragment.metadata.connection; if (fragment.metadata !== undefined) { isRelayModern = true; } if (connectionMetadata) { !(connectionMetadata.length === 1) ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Only a single @connection is ' + 'supported, `%s` has %s.', fragmentName, connectionMetadata.length) : invariant(false) : void 0; !!foundConnectionMetadata ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Only a single fragment with ' + '@connection is supported.') : invariant(false) : void 0; foundConnectionMetadata = (0, _objectSpread3["default"])((0, _objectSpread3["default"])({}, connectionMetadata[0]), {}, { fragmentName: fragmentName }); } } !(!isRelayModern || foundConnectionMetadata !== null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: A @connection directive must be present.') : invariant(false) : void 0; return foundConnectionMetadata || {}; } function toObserver(observerOrCallback) { return typeof observerOrCallback === 'function' ? { error: observerOrCallback, complete: observerOrCallback, unsubscribe: function unsubscribe(subscription) { typeof observerOrCallback === 'function' && observerOrCallback(); } } : observerOrCallback || {}; } function createContainerWithFragments(Component, fragments, connectionConfig) { var _class; var componentName = getComponentName(Component); var containerName = getContainerName(Component); var metadata = findConnectionMetadata(fragments); var getConnectionFromProps = connectionConfig.getConnectionFromProps || createGetConnectionFromProps(metadata); var direction = connectionConfig.direction || metadata.direction; !direction ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Unable to infer direction of the ' + 'connection, possibly because both first and last are provided.') : invariant(false) : void 0; var getFragmentVariables = connectionConfig.getFragmentVariables || createGetFragmentVariables(metadata); return _class = /*#__PURE__*/function (_React$Component) { (0, _inheritsLoose2["default"])(_class, _React$Component); function _class(props) { var _props$__rootIsQueryR, _this; _this = _React$Component.call(this, props) || this; (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleFragmentDataUpdate", function () { _this.setState({ data: _this._resolver.resolve() }); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_hasMore", function () { var connectionData = _this._getConnectionData(); return !!(connectionData && connectionData.hasMore && connectionData.cursor); }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_isLoading", function () { return !!_this._refetchSubscription; }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_refetchConnection", function (totalCount, observerOrCallback, refetchVariables) { if (!_this._canFetchPage('refetchConnection')) { return { dispose: function dispose() {} }; } _this._refetchVariables = refetchVariables; var paginatingVariables = { count: totalCount, cursor: null, totalCount: totalCount }; var fetch = _this._fetchPage(paginatingVariables, toObserver(observerOrCallback), { force: true }); return { dispose: fetch.unsubscribe }; }); (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_loadMore", function (pageSize, observerOrCallback, options) { if (!_this._canFetchPage('loadMore')) { return { dispose: function dispose() {} }; } var observer = toObserver(observerOrCallback); var connectionData = _this._getConnectionData(); if (!connectionData) { Observable.create(function (sink) { return sink.complete(); }).subscribe(observer); return null; } var totalCount = connectionData.edgeCount + pageSize; if (options && options.force) { return _this._refetchConnection(totalCount, observerOrCallback); } var _ConnectionInterface$ = ConnectionInterface.get(), END_CURSOR = _ConnectionInterface$.END_CURSOR, START_CURSOR = _ConnectionInterface$.START_CURSOR; var cursor = connectionData.cursor; process.env.NODE_ENV !== "production" ? warning(cursor != null && cursor !== '', 'ReactRelayPaginationContainer: Cannot `loadMore` without valid `%s` (got `%s`)', direction === FORWARD ? END_CURSOR : START_CURSOR, cursor) : void 0; var paginatingVariables = { count: pageSize, cursor: cursor, totalCount: totalCount }; var fetch = _this._fetchPage(paginatingVariables, observer, options); return { dispose: fetch.unsubscribe }; }); var relayContext = assertRelayContext(props.__relayContext); var rootIsQueryRenderer = (_props$__rootIsQueryR = props.__rootIsQueryRenderer) !== null && _props$__rootIsQueryR !== void 0 ? _props$__rootIsQueryR : false; _this._isARequestInFlight = false; _this._refetchSubscription = null; _this._refetchVariables = null; if (RelayFeatureFlags.ENABLE_CONTAINERS_SUBSCRIBE_ON_COMMIT === true) { _this._resolver = createFragmentSpecResolver(relayContext, containerName, fragments, props, rootIsQueryRenderer); } else { _this._resolver = createFragmentSpecResolver(relayContext, containerName, fragments, props, rootIsQueryRenderer, _this._handleFragmentDataUpdate); } _this.state = { data: _this._resolver.resolve(), prevContext: relayContext, contextForChildren: relayContext, relayProp: _this._buildRelayProp(relayContext), resolverGeneration: 0 }; _this._isUnmounted = false; _this._hasFetched = false; return _this; } var _proto = _class.prototype; _proto.componentDidMount = function componentDidMount() { this._isUnmounted = false; if (RelayFeatureFlags.ENABLE_CONTAINERS_SUBSCRIBE_ON_COMMIT === true) { this._subscribeToNewResolverAndRerenderIfStoreHasChanged(); } }; _proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) { if (RelayFeatureFlags.ENABLE_CONTAINERS_SUBSCRIBE_ON_COMMIT === true) { if (prevState.resolverGeneration !== this.state.resolverGeneration) { this._subscribeToNewResolverAndRerenderIfStoreHasChanged(); } else { this._rerenderIfStoreHasChanged(); } } }; _proto.UNSAFE_componentWillReceiveProps = function UNSAFE_componentWillReceiveProps(nextProps) { var _this2 = this; var _nextProps$__rootIsQu; var relayContext = assertRelayContext(nextProps.__relayContext); var rootIsQueryRenderer = (_nextProps$__rootIsQu = nextProps.__rootIsQueryRenderer) !== null && _nextProps$__rootIsQu !== void 0 ? _nextProps$__rootIsQu : false; var prevIDs = getDataIDsFromObject(fragments, this.props); var nextIDs = getDataIDsFromObject(fragments, nextProps); var prevRootVariables = getRootVariablesForFragments(fragments, this.props); var nextRootVariables = getRootVariablesForFragments(fragments, nextProps); if (relayContext.environment !== this.state.prevContext.environment || !areEqual(prevRootVariables, nextRootVariables) || !areEqual(prevIDs, nextIDs)) { this._cleanup(); if (RelayFeatureFlags.ENABLE_CONTAINERS_SUBSCRIBE_ON_COMMIT === true) { this._resolver = createFragmentSpecResolver(relayContext, containerName, fragments, nextProps, rootIsQueryRenderer); } else { this._resolver = createFragmentSpecResolver(relayContext, containerName, fragments, nextProps, rootIsQueryRenderer, this._handleFragmentDataUpdate); } this.setState(function (prevState) { return { prevContext: relayContext, contextForChildren: relayContext, relayProp: _this2._buildRelayProp(relayContext), resolverGeneration: prevState.resolverGeneration + 1 }; }); } else if (!this._hasFetched) { this._resolver.setProps(nextProps); } var data = this._resolver.resolve(); if (data !== this.state.data) { this.setState({ data: data }); } }; _proto.componentWillUnmount = function componentWillUnmount() { this._isUnmounted = true; this._cleanup(); }; _proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) { if (nextState.data !== this.state.data || nextState.relayProp !== this.state.relayProp || nextState.resolverGeneration !== this.state.resolverGeneration) { return true; } var keys = Object.keys(nextProps); for (var ii = 0; ii < keys.length; ii++) { var key = keys[ii]; if (key === '__relayContext') { if (nextState.prevContext.environment !== this.state.prevContext.environment) { return true; } } else { if (!fragments.hasOwnProperty(key) && !isScalarAndEqual(nextProps[key], this.props[key])) { return true; } } } return false; }; _proto._buildRelayProp = function _buildRelayProp(relayContext) { return { hasMore: this._hasMore, isLoading: this._isLoading, loadMore: this._loadMore, refetchConnection: this._refetchConnection, environment: relayContext.environment }; }; _proto._rerenderIfStoreHasChanged = function _rerenderIfStoreHasChanged() { var data = this.state.data; var maybeNewData = this._resolver.resolve(); if (data !== maybeNewData) { this.setState({ data: maybeNewData }); } }; _proto._subscribeToNewResolverAndRerenderIfStoreHasChanged = function _subscribeToNewResolverAndRerenderIfStoreHasChanged() { var data = this.state.data; var maybeNewData = this._resolver.resolve(); this._resolver.setCallback(this.props, this._handleFragmentDataUpdate); if (data !== maybeNewData) { this.setState({ data: maybeNewData }); } }; _proto._getConnectionData = function _getConnectionData() { var _this$props = this.props, _ = _this$props.componentRef, restProps = (0, _objectWithoutPropertiesLoose2["default"])(_this$props, _excluded); var props = (0, _objectSpread3["default"])((0, _objectSpread3["default"])({}, restProps), this.state.data); var connectionData = getConnectionFromProps(props); if (connectionData == null) { return null; } var _ConnectionInterface$2 = ConnectionInterface.get(), EDGES = _ConnectionInterface$2.EDGES, PAGE_INFO = _ConnectionInterface$2.PAGE_INFO, HAS_NEXT_PAGE = _ConnectionInterface$2.HAS_NEXT_PAGE, HAS_PREV_PAGE = _ConnectionInterface$2.HAS_PREV_PAGE, END_CURSOR = _ConnectionInterface$2.END_CURSOR, START_CURSOR = _ConnectionInterface$2.START_CURSOR; !(typeof connectionData === 'object') ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Expected `getConnectionFromProps()` in `%s`' + 'to return `null` or a plain object with %s and %s properties, got `%s`.', componentName, EDGES, PAGE_INFO, connectionData) : invariant(false) : void 0; var edges = connectionData[EDGES]; var pageInfo = connectionData[PAGE_INFO]; if (edges == null || pageInfo == null) { return null; } !Array.isArray(edges) ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Expected `getConnectionFromProps()` in `%s`' + 'to return an object with %s: Array, got `%s`.', componentName, EDGES, edges) : invariant(false) : void 0; !(typeof pageInfo === 'object') ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Expected `getConnectionFromProps()` in `%s`' + 'to return an object with %s: Object, got `%s`.', componentName, PAGE_INFO, pageInfo) : invariant(false) : void 0; var hasMore = direction === FORWARD ? pageInfo[HAS_NEXT_PAGE] : pageInfo[HAS_PREV_PAGE]; var cursor = direction === FORWARD ? pageInfo[END_CURSOR] : pageInfo[START_CURSOR]; if (typeof hasMore !== 'boolean' || edges.length !== 0 && typeof cursor === 'undefined') { process.env.NODE_ENV !== "production" ? warning(false, 'ReactRelayPaginationContainer: Cannot paginate without %s fields in `%s`. ' + 'Be sure to fetch %s (got `%s`) and %s (got `%s`).', PAGE_INFO, componentName, direction === FORWARD ? HAS_NEXT_PAGE : HAS_PREV_PAGE, hasMore, direction === FORWARD ? END_CURSOR : START_CURSOR, cursor) : void 0; return null; } return { cursor: cursor, edgeCount: edges.length, hasMore: hasMore }; }; _proto._getQueryFetcher = function _getQueryFetcher() { if (!this._queryFetcher) { this._queryFetcher = new ReactRelayQueryFetcher(); } return this._queryFetcher; }; _proto._canFetchPage = function _canFetchPage(method) { if (this._isUnmounted) { process.env.NODE_ENV !== "production" ? warning(false, 'ReactRelayPaginationContainer: Unexpected call of `%s` ' + 'on unmounted container `%s`. It looks like some instances ' + 'of your container still trying to fetch data but they already ' + 'unmounted. Please make sure you clear all timers, intervals, async ' + 'calls, etc that may trigger `%s` call.', method, containerName, method) : void 0; return false; } return true; }; _proto._fetchPage = function _fetchPage(paginatingVariables, observer, options) { var _this3 = this; var _assertRelayContext = assertRelayContext(this.props.__relayContext), environment = _assertRelayContext.environment; var _this$props2 = this.props, _ = _this$props2.componentRef, __relayContext = _this$props2.__relayContext, __rootIsQueryRenderer = _this$props2.__rootIsQueryRenderer, restProps = (0, _objectWithoutPropertiesLoose2["default"])(_this$props2, _excluded2); var props = (0, _objectSpread3["default"])((0, _objectSpread3["default"])({}, restProps), this.state.data); var fragmentVariables; var rootVariables = getRootVariablesForFragments(fragments, restProps); fragmentVariables = getVariablesFromObject(fragments, restProps); fragmentVariables = (0, _objectSpread3["default"])((0, _objectSpread3["default"])((0, _objectSpread3["default"])({}, rootVariables), fragmentVariables), this._refetchVariables); var fetchVariables = connectionConfig.getVariables(props, { count: paginatingVariables.count, cursor: paginatingVariables.cursor }, fragmentVariables); !(typeof fetchVariables === 'object' && fetchVariables !== null) ? process.env.NODE_ENV !== "production" ? invariant(false, 'ReactRelayPaginationContainer: Expected `getVariables()` to ' + 'return an object, got `%s` in `%s`.', fetchVariables, componentName) : invariant(false) : void 0; fetchVariables = (0, _objectSpread3["default"])((0, _objectSpread3["default"])({}, fetchVariables), this._refetchVariables); fragmentVariables = (0, _objectSpread3["default"])((0, _objectSpread3["default"])({}, fetchVariables), fragmentVariables); var cacheConfig = options ? { force: !!options.force } : undefined; if (cacheConfig != null && (options === null || options === void 0 ? void 0 : options.metadata) != null) { cacheConfig.metadata = options === null || options === void 0 ? void 0 : options.metadata; } var request = getRequest(connectionConfig.query); var operation = createOperationDescriptor(request, fetchVariables, cacheConfig); var refetchSubscription = null; if (this._refetchSubscription) { this._refetchSubscription.unsubscribe(); } this._hasFetched = true; var onNext = function onNext(payload, complete) { var prevData = _this3._resolver.resolve(); _this3._resolver.setVariables(getFragmentVariables(fragmentVariables, paginatingVariables.totalCount), operation.request.node); var nextData = _this3._resolver.resolve(); if (!areEqual(prevData, nextData)) { _this3.setState({ data: nextData, contextForChildren: { environment: _this3.props.__relayContext.environment } }, complete); } else { complete(); } }; var cleanup = function cleanup() { if (_this3._refetchSubscription === refetchSubscription) { _this3._refetchSubscription = null; _this3._isARequestInFlight = false; } }; this._isARequestInFlight = true; refetchSubscription = this._getQueryFetcher().execute({ environment: environment, operation: operation, preservePreviousReferences: true }).mergeMap(function (payload) { return Observable.create(function (sink) { onNext(payload, function () { sink.next(); sink.complete(); }); }); })["do"]({ error: cleanup, complete: cleanup, unsubscribe: cleanup }).subscribe(observer || {}); this._refetchSubscription = this._isARequestInFlight ? refetchSubscription : null; return refetchSubscription; }; _proto._cleanup = function _cleanup() { this._resolver.dispose(); this._refetchVariables = null; this._hasFetched = false; if (this._refetchSubscription) { this._refetchSubscription.unsubscribe(); this._refetchSubscription = null; this._isARequestInFlight = false; } if (this._queryFetcher) { this._queryFetcher.dispose(); } }; _proto.render = function render() { var _this$props3 = this.props, componentRef = _this$props3.componentRef, __relayContext = _this$props3.__relayContext, __rootIsQueryRenderer = _this$props3.__rootIsQueryRenderer, props = (0, _objectWithoutPropertiesLoose2["default"])(_this$props3, _excluded3); return /*#__PURE__*/React.createElement(ReactRelayContext.Provider, { value: this.state.contextForChildren }, /*#__PURE__*/React.createElement(Component, (0, _extends2["default"])({}, props, this.state.data, { ref: componentRef, relay: this.state.relayProp }))); }; return _class; }(React.Component), (0, _defineProperty2["default"])(_class, "displayName", containerName), _class; } function createContainer(Component, fragmentSpec, connectionConfig) { return buildReactRelayContainer(Component, fragmentSpec, function (ComponentClass, fragments) { return createContainerWithFragments(ComponentClass, fragments, connectionConfig); }); } module.exports = { createContainer: createContainer };