Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Commit

Permalink
WIP - refactor to calculate data at render time
Browse files Browse the repository at this point in the history
  • Loading branch information
tmeasday committed Oct 12, 2016
1 parent 5f5afbb commit c9ee4b4
Showing 1 changed file with 70 additions and 93 deletions.
163 changes: 70 additions & 93 deletions src/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,18 @@ export default function graphql(
return opts;
}

function shouldSkip(props) {
return mapPropsToSkip(props) || (mapPropsToOptions(props) as QueryOptions).skip;
}

function fetchData(props, { client }) {
if (mapPropsToSkip(props)) return false;
if (shouldSkip(props)) return false;
if (
operation.type === DocumentType.Mutation || operation.type === DocumentType.Subscription
) return false;

const opts = calculateOptions(props) as any;
if (opts.ssr === false || opts.skip) return false;
if (opts.ssr === false) return false;

const observable = client.watchQuery(assign({ query: document }, opts));
const result = observable.currentResult();
Expand Down Expand Up @@ -229,17 +233,16 @@ export default function graphql(
// data storage
private store: ApolloStore;
private client: ApolloClient; // apollo client
private data: any = {}; // apollo data
private type: DocumentType;

// request / action storage. Note that we delete querySubscription if we
// unsubscribe but never delete queryObservable once it is created.
private queryObservable: ObservableQuery | any;
private querySubscription: Subscription;
private previousData: any = {};

// calculated switches to control rerenders
private haveOwnPropsChanged: boolean;
private hasOperationDataChanged: boolean;
private shouldRerender: boolean;

// the element to render
private renderedElement: any;
Expand All @@ -259,41 +262,42 @@ export default function graphql(

this.type = operation.type;

if (mapPropsToSkip(props)) return;
if (this.shouldSkip(props)) return;
this.setInitialProps();
}

componentDidMount() {
this.hasMounted = true;
if (this.type === DocumentType.Mutation) return;

if (mapPropsToSkip(this.props)) return;
this.subscribeToQuery(this.props);
if (!this.shouldSkip(this.props)) {
this.subscribeToQuery(this.props);
}
}

componentWillReceiveProps(nextProps) {
if (mapPropsToSkip(nextProps)) {
if (!mapPropsToSkip(this.props)) {
// if this has changed, remove data and unsubscribeFromQuery
this.data = assign({}, skippedQueryData) as any;
this.unsubscribeFromQuery();
}
return;
}
if (shallowEqual(this.props, nextProps)) return;

this.shouldRerender = true;

if (this.type === DocumentType.Mutation) {
this.createWrappedMutation(nextProps, true);
return;
};

if (this.shouldSkip(nextProps)) {
if (!this.shouldSkip(this.props)) {
// if this has changed, we better unsubscribe
this.unsubscribeFromQuery();
}
return;
}

// we got new props, we need to unsubscribe and re-subscribe with the new data
this.haveOwnPropsChanged = true;
this.subscribeToQuery(nextProps);
}

shouldComponentUpdate(nextProps, nextState, nextContext) {
return !!nextContext || this.haveOwnPropsChanged || this.hasOperationDataChanged;
return !!nextContext || this.shouldRerender;
}

componentWillUnmount() {
Expand All @@ -316,18 +320,15 @@ export default function graphql(
}

setInitialProps() {
if (this.type === DocumentType.Mutation) return this.createWrappedMutation(this.props);
if (this.type === DocumentType.Mutation) {
return;
}

// Create the observable but don't subscribe yet. The query won't
// fire until we do.
const opts: QueryOptions = this.calculateOptions(this.props);

if (opts.skip) {
this.data = assign({}, skippedQueryData) as any;
} else {
this.data = assign({}, defaultQueryData) as any;
this.createQuery(opts);
}
this.createQuery(opts);
}

createQuery(opts: QueryOptions) {
Expand All @@ -340,80 +341,34 @@ export default function graphql(
query: document,
}, opts));
}

this.initializeData(opts);
}

initializeData(opts: QueryOptions) {
assign(this.data, observableQueryFields(this.queryObservable));

if (this.type === DocumentType.Subscription) {
opts = this.calculateOptions(this.props, opts);
assign(this.data, { loading: true }, { variables: opts.variables });
} else if (!opts.forceFetch) {
const currentResult = this.queryObservable.currentResult();
// try and fetch initial data from the store
assign(this.data, currentResult.data, { loading: currentResult.loading });
} else {
assign(this.data, { loading: true });
}
}

subscribeToQuery(props): boolean {
const opts = calculateOptions(props) as QueryOptions;

if (opts.skip) {
if (this.querySubscription) {
this.hasOperationDataChanged = true;
this.data = assign({}, skippedQueryData) as any;
this.unsubscribeFromQuery();
this.forceRenderChildren();
}
return;
}

// We've subscribed already, just update with our new options and
// take the latest result
if (this.querySubscription) {
this.queryObservable.setOptions(opts);

// Ensure we are up-to-date with the latest state of the world
assign(this.data,
{ loading: this.queryObservable.currentResult().loading },
observableQueryFields(this.queryObservable)
);

return;
}

// if we skipped initially, we may not have yet created the observable
if (!this.queryObservable) {
this.createQuery(opts);
} else if (!this.data.refetch) {
// we've run this query before, but then we've skipped it (resetting
// data to skippedQueryData) and now we're unskipping it. Make sure
// the data fields are set as if we hadn't run it.
this.initializeData(opts);
}

const next = (results: any) => {
if (this.type === DocumentType.Subscription) {
results = { data: results, loading: false, error: null };
results = { data: results };
}
const { data, loading, error = null } = results;
const clashingKeys = Object.keys(observableQueryFields(data));
const clashingKeys = Object.keys(observableQueryFields(results.data));
invariant(clashingKeys.length === 0,
`the result of the '${graphQLDisplayName}' operation contains keys that ` +
`conflict with the return object.` +
clashingKeys.map(k => `'${k}'`).join(', ') + ` not allowed.`
);

this.hasOperationDataChanged = true;
this.data = assign({
loading,
error,
}, data, observableQueryFields(this.queryObservable));

this.forceRenderChildren();
};

Expand All @@ -439,8 +394,13 @@ export default function graphql(
}
}

shouldSkip(props) {
return shouldSkip(props)
}

forceRenderChildren() {
// force a rerender that goes through shouldComponentUpdate
this.shouldRerender = true;
if (this.hasMounted) this.setState({});
}

Expand All @@ -453,36 +413,54 @@ export default function graphql(
return (this.refs as any).wrappedInstance;
}

createWrappedMutation(props: any, reRender = false) {
if (this.type !== DocumentType.Mutation) return;

this.data = (opts: MutationOptions) => {
opts = this.calculateOptions(props, opts);
dataForChild() {
if (this.type === DocumentType.Mutation) {
return (mutationOpts: MutationOptions) => {
const opts = this.calculateOptions(this.props, mutationOpts);

if (typeof opts.variables === 'undefined') delete opts.variables;
if (typeof opts.variables === 'undefined') delete opts.variables;

(opts as any).mutation = document;
return this.client.mutate((opts as any));
};
(opts as any).mutation = document;
return this.client.mutate((opts as any));
};
}

if (!reRender) return;
const opts = this.calculateOptions(this.props);
const data = {};
assign(data, observableQueryFields(this.queryObservable));

this.hasOperationDataChanged = true;
this.forceRenderChildren();
if (this.type === DocumentType.Subscription) {
assign(data, { loading: true }, { variables: opts.variables });
} else {
// fetch the current result (if any) from the store
const currentResult = this.queryObservable.currentResult();
const { loading, error, data } = currentResult;
assign(data, { loading, error });

if (loading) {
// while loading, we should us any previous data we have
assign(data, this.previousData, currentResult.data);
} else {
assign(data, currentResult.data);
this.previousData = currentResult.data;
}
}
return data;
}

render() {
if (mapPropsToSkip(this.props)) return createElement(WrappedComponent, this.props);

const { haveOwnPropsChanged, hasOperationDataChanged, renderedElement, props, data } = this;
if (this.shouldSkip(this.props)) {
return createElement(WrappedComponent, this.props);
}

this.haveOwnPropsChanged = false;
this.hasOperationDataChanged = false;
const { shouldRerender, renderedElement, props } = this;
this.shouldRerender = false;

const data = this.dataForChild();
const clientProps = this.calculateResultProps(data);
const mergedPropsAndData = assign({}, props, clientProps);

if (!haveOwnPropsChanged && !hasOperationDataChanged && renderedElement) {
if (!shouldRerender && renderedElement) {
return renderedElement;
}

Expand All @@ -497,7 +475,6 @@ export default function graphql(

// Make sure we preserve any custom statics on the original component.
return hoistNonReactStatics(GraphQL, WrappedComponent);

};

};

0 comments on commit c9ee4b4

Please sign in to comment.