Created
December 27, 2017 08:21
-
-
Save nodkz/ee25063925ea51a753c4ace7c165c1ef to your computer and use it in GitHub Desktop.
Relay.Modern query renderers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function getRelayQueryRenderer(opts, routeProps, clientStores) { | |
if (!opts.relayQuery) { | |
return 'Empty Relay Query'; | |
} | |
const { query, variables, prepareProps } = opts.relayQuery; | |
const rendererProps = { | |
environment: clientStores.relayStore, | |
query, | |
variables: !variables | |
? {} | |
: variables.call ? variables(routeProps, clientStores) : routeProps.match.params, | |
render: ({ error, props }) => { | |
if (!opts.component) { | |
throw new Error( | |
'You should provide `component` property for RouteSwipeColumn, ' + | |
`if you use relayQuery property for path '${opts.path || ''}'.` | |
); | |
} | |
if (error) { | |
return <BrokenPage devMessage={error.message} />; | |
} else if (props) { | |
return React.createElement(opts.component, { | |
...routeProps, | |
...(opts.componentProps ? opts.componentProps(routeProps, clientStores) : null), | |
...(prepareProps ? prepareProps(props, routeProps, clientStores) : props), | |
...(opts.children ? { children: React.createElement(opts.children) } : null), | |
}); | |
} | |
return <LoadingPage />; | |
}, | |
// TODO | |
// forceFetch={ | |
// (opts.relayForceFetch && isFunction(opts.relayForceFetch) | |
// ? opts.relayForceFetch(routeProps, clientStores) | |
// : !!opts.relayForceFetch) || false | |
// } | |
}; | |
// __IS_SERVER__ is defined via Webpack DefinePlugin | |
return __IS_SERVER__ ? ( | |
<RelaySSRQueryRenderer {...rendererProps} /> | |
) : ( | |
// <QueryRenderer {...rendererProps} /> | |
<RelayLookupQueryRenderer lookup {...rendererProps} /> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @flow */ | |
/* eslint-disable no-use-before-define, react/no-unused-prop-types */ | |
import * as React from 'react'; | |
import areEqual from 'fbjs/lib/areEqual'; | |
// forked from https://github.com/robrichard/relay-query-lookup-renderer | |
// import type { CacheConfig, Disposable } from 'RelayCombinedEnvironmentTypes'; | |
// import type { RelayEnvironmentInterface as ClassicEnvironment } from 'RelayEnvironment'; | |
// import type { GraphQLTaggedNode } from 'RelayModernGraphQLTag'; | |
// import type { Environment, OperationSelector, RelayContext, Snapshot } from 'RelayStoreTypes'; | |
// import type { RerunParam, Variables } from 'RelayTypes'; | |
type CacheConfig = any; | |
type Disposable = any; | |
type ClassicEnvironment = any; | |
type GraphQLTaggedNode = any; | |
type Environment = any; | |
type OperationSelector = any; | |
type RelayContext = any; | |
type Snapshot = any; | |
type RerunParam = any; | |
type Variables = any; | |
export type Props = { | |
cacheConfig?: ?CacheConfig, | |
environment: Environment | ClassicEnvironment, | |
query: ?GraphQLTaggedNode, | |
render: (readyState: ReadyState) => ?React.Element<any>, | |
variables: Variables, | |
rerunParamExperimental?: RerunParam, | |
}; | |
export type ReadyState = { | |
error: ?Error, | |
props: ?Object, | |
retry: ?() => void, | |
}; | |
type State = { | |
readyState: ReadyState, | |
}; | |
/** | |
* @public | |
* | |
* Orchestrates fetching and rendering data for a single view or view hierarchy: | |
* - Fetches the query/variables using the given network implementation. | |
* - Normalizes the response(s) to that query, publishing them to the given | |
* store. | |
* - Renders the pending/fail/success states with the provided render function. | |
* - Subscribes for updates to the root data and re-renders with any changes. | |
*/ | |
export default class ReactRelayQueryRenderer extends React.Component<Props, State> { | |
_pendingFetch: ?Disposable; | |
_relayContext: RelayContext; | |
_rootSubscription: ?Disposable; | |
_selectionReference: ?Disposable; | |
static childContextTypes = { | |
relay: () => {}, | |
}; | |
constructor(props: Props, context: Object) { | |
super(props, context); | |
this._pendingFetch = null; | |
this._rootSubscription = null; | |
this._selectionReference = null; | |
this.state = { | |
readyState: this._fetchForProps(props), | |
}; | |
} | |
componentWillReceiveProps(nextProps: Props): void { | |
if ( | |
nextProps.query !== this.props.query || | |
nextProps.environment !== this.props.environment || | |
!areEqual(nextProps.variables, this.props.variables) | |
) { | |
this.setState({ | |
readyState: this._fetchForProps(nextProps), | |
}); | |
} | |
} | |
componentWillUnmount(): void { | |
this._release(); | |
} | |
shouldComponentUpdate(nextProps: Props, nextState: State): boolean { | |
return nextProps.render !== this.props.render || nextState.readyState !== this.state.readyState; | |
} | |
_release(): void { | |
if (this._pendingFetch) { | |
this._pendingFetch.dispose(); | |
this._pendingFetch = null; | |
} | |
if (!this.props.retain && this._rootSubscription) { | |
this._rootSubscription.dispose(); | |
this._rootSubscription = null; | |
} | |
if (!this.props.retain && this._selectionReference) { | |
this._selectionReference.dispose(); | |
this._selectionReference = null; | |
} | |
} | |
_fetchForProps(props: Props): ReadyState { | |
// TODO (#16225453) QueryRenderer works with old and new environment, but | |
// the flow typing doesn't quite work abstracted. | |
// $FlowFixMe | |
const environment: Environment = props.environment; | |
const { query, variables } = props; | |
if (query) { | |
const { createOperationSelector, getOperation } = environment.unstable_internal; | |
const operation = createOperationSelector(getOperation(query), variables); | |
this._relayContext = { | |
environment, | |
variables: operation.variables, | |
}; | |
if (props.lookup && environment.check(operation.root)) { | |
// data is available in the store, render without making any requests | |
const snapshot = environment.lookup(operation.fragment); | |
return { | |
error: null, | |
props: snapshot.data, | |
retry: () => { | |
this._fetch(operation, props.cacheConfig); | |
}, | |
}; | |
} | |
return this._fetch(operation, props.cacheConfig) || getDefaultState(); | |
} | |
this._relayContext = { | |
environment, | |
variables, | |
}; | |
this._release(); | |
return { | |
error: null, | |
props: {}, | |
retry: null, | |
}; | |
} | |
_fetch(operation: OperationSelector, cacheConfig: ?CacheConfig): ?ReadyState { | |
const { environment } = this._relayContext; | |
// Immediately retain the results of the new query to prevent relevant data | |
// from being freed. This is not strictly required if all new data is | |
// fetched in a single step, but is necessary if the network could attempt | |
// to incrementally load data (ex: multiple query entries or incrementally | |
// loading records from disk cache). | |
const nextReference = environment.retain(operation.root); | |
let readyState = getDefaultState(); | |
let snapshot: ?Snapshot; // results of the root fragment | |
let hasSyncResult = false; | |
let hasFunctionReturned = false; | |
if (this._pendingFetch) { | |
this._pendingFetch.dispose(); | |
} | |
if (this._rootSubscription) { | |
this._rootSubscription.dispose(); | |
} | |
const request = environment | |
.execute({ operation, cacheConfig }) | |
.finally(() => { | |
this._pendingFetch = null; | |
}) | |
.subscribe({ | |
next: () => { | |
// `next` can be called multiple times by network layers that support | |
// data subscriptions. Wait until the first payload to render `props` | |
// and subscribe for data updates. | |
if (snapshot) { | |
return; | |
} | |
snapshot = environment.lookup(operation.fragment); | |
readyState = { | |
error: null, | |
props: snapshot.data, | |
retry: () => { | |
// Do not reset the default state if refetching after success, | |
// handling the case where _fetch may return syncronously instead | |
// of calling setState. | |
const syncReadyState = this._fetch(operation, cacheConfig); | |
if (syncReadyState) { | |
this.setState({ readyState: syncReadyState }); | |
} | |
}, | |
}; | |
if (this._selectionReference) { | |
this._selectionReference.dispose(); | |
} | |
this._rootSubscription = environment.subscribe(snapshot, this._onChange); | |
this._selectionReference = nextReference; | |
// This line should be called only once. | |
hasSyncResult = true; | |
if (hasFunctionReturned) { | |
this.setState({ readyState }); | |
} | |
}, | |
error: error => { | |
readyState = { | |
error, | |
props: null, | |
retry: () => { | |
// Return to the default state when retrying after an error, | |
// handling the case where _fetch may return syncronously instead | |
// of calling setState. | |
const syncReadyState = this._fetch(operation, cacheConfig); | |
this.setState({ readyState: syncReadyState || getDefaultState() }); | |
}, | |
}; | |
if (this._selectionReference) { | |
this._selectionReference.dispose(); | |
} | |
this._selectionReference = nextReference; | |
hasSyncResult = true; | |
if (hasFunctionReturned) { | |
this.setState({ readyState }); | |
} | |
}, | |
}); | |
this._pendingFetch = { | |
dispose() { | |
request.unsubscribe(); | |
nextReference.dispose(); | |
}, | |
}; | |
hasFunctionReturned = true; | |
return hasSyncResult ? readyState : null; | |
} | |
_onChange = (snapshot: Snapshot): void => { | |
this.setState({ | |
readyState: { | |
...this.state.readyState, | |
props: snapshot.data, | |
}, | |
}); | |
}; | |
getChildContext(): Object { | |
return { | |
relay: this._relayContext, | |
}; | |
} | |
render() { | |
// Note that the root fragment results in `readyState.props` is already | |
// frozen by the store; this call is to freeze the readyState object and | |
// error property if set. | |
// if (__DEV__) { | |
// deepFreeze(this.state.readyState); | |
// } | |
return this.props.render(this.state.readyState); | |
} | |
} | |
function getDefaultState(): ReadyState { | |
return { | |
error: null, | |
props: null, | |
retry: null, | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* @flow */ | |
import * as React from 'react'; | |
import type RelayStore, { RelayQuery } from 'clientStores/RelayStore'; | |
type ReadyState = {| | |
error: ?{ | |
message: string, | |
}, | |
props: ?mixed, | |
retry: ?() => {}, | |
|}; | |
type Props = { | |
environment: RelayStore, | |
query: RelayQuery, | |
variables: Object, | |
render: (state: ReadyState) => React.Node, | |
}; | |
type State = {| | |
readyState: ReadyState, | |
|}; | |
export default class RelaySSRQueryRenderer extends React.Component<Props, State> { | |
static childContextTypes = { | |
relay: () => {}, | |
}; | |
_relayContext: any; | |
getChildContext() { | |
const ctx = this._relayContext; | |
return ctx; | |
} | |
state: State = { | |
readyState: { | |
error: null, | |
props: null, | |
retry: null, | |
}, | |
}; | |
componentWillMount() { | |
const { environment, query, variables } = this.props; | |
const { getOperation, createOperationSelector } = environment.unstable_internal; | |
const request = getOperation(query); | |
const operation = createOperationSelector(request, variables || {}); | |
this._relayContext = { | |
relay: { | |
environment, | |
variables: operation.variables || {}, | |
}, | |
}; | |
// take data from Environment | |
if (environment.check(operation.root)) { | |
const snapshot = environment.lookup(operation.fragment); | |
if (snapshot) { | |
this.setState({ | |
readyState: { | |
error: null, | |
props: snapshot.data || null, | |
retry: null, | |
}, | |
}); | |
return; | |
} | |
} | |
// take data from Cache (as fallback) | |
// const queryID = request.id || request.name; | |
// const res = environment._cache.get(queryID, variables); | |
// if (res) { | |
// this.setState({ | |
// readyState: { | |
// error: res.error || null, | |
// props: res.data || null, | |
// retry: null, | |
// }, | |
// }); | |
// return; | |
// } | |
// just run request (React SSR does not work with async methods) | |
// so serverRenderHtml will wait all current requests, | |
// and re-run rendering when data will be avaliable in store/cache | |
environment.fetch({ | |
query, | |
variables, | |
}); | |
} | |
render() { | |
return this.props.render(this.state.readyState); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment