Skip to content
This repository was archived by the owner on Apr 13, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Expect active development and potentially significant breaking changes in the `0

- Feature: Remove nested imports for apollo-client. Making local development eaiser. [#234](https://github.com/apollostack/react-apollo/pull/234)
- Feature: Move types to dev deps [#251](https://github.com/apollostack/react-apollo/pull/251)
- Feature: New method for skipping queries which bypasses HOC internals [#253](https://github.com/apollostack/react-apollo/pull/253)

### v0.5.7

Expand Down
22 changes: 19 additions & 3 deletions src/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export declare interface QueryOptions {
noFetch?: boolean;
pollInterval?: number;
fragments?: FragmentDefinition[] | FragmentDefinition[][];
// deprecated
skip?: boolean;
}

Expand All @@ -62,6 +63,7 @@ const defaultQueryData = {

const defaultMapPropsToOptions = props => ({});
const defaultMapResultToProps = props => props;
const defaultMapPropsToSkip = props => false;

function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
Expand Down Expand Up @@ -109,6 +111,7 @@ export function withApollo(WrappedComponent) {
export interface OperationOption {
options?: Object | ((props: any) => QueryOptions | MutationOptions);
props?: (props: any) => any;
skip?: boolean | ((props: any) => boolean);
name?: string;
withRef?: boolean;
}
Expand All @@ -119,10 +122,14 @@ export default function graphql(
) {

// extract options
const { options = defaultMapPropsToOptions } = operationOptions;
const { options = defaultMapPropsToOptions, skip = defaultMapPropsToSkip } = operationOptions;

let mapPropsToOptions = options as (props: any) => QueryOptions | MutationOptions;
if (typeof mapPropsToOptions !== 'function') mapPropsToOptions = () => options;

let mapPropsToSkip = skip as (props: any) => boolean;
if (typeof mapPropsToSkip !== 'function') mapPropsToSkip = (() => skip as any);

const mapResultToProps = operationOptions.props;

// safety check on the operation
Expand Down Expand Up @@ -181,6 +188,7 @@ export default function graphql(
}

function fetchData(props, { client }) {
if (mapPropsToSkip(props)) return;
if (operation.type === DocumentType.Mutation) return false;
const opts = calculateOptions(props) as any;
opts.query = document;
Expand Down Expand Up @@ -257,6 +265,7 @@ export default function graphql(
this.queryObservable = {};
this.querySubscription = {};

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

}
Expand All @@ -265,11 +274,16 @@ export default function graphql(
this.hasMounted = true;
if (this.type === DocumentType.Mutation) return;

if (mapPropsToSkip(this.props)) return;
this.subscribeToQuery(this.props);

}

componentWillReceiveProps(nextProps) {
// if this has changed, remove data and unsubscribeFromQuery
if (!mapPropsToSkip(this.props) && mapPropsToSkip(nextProps)) {
delete this.data;
return this.unsubscribeFromQuery();
}
if (shallowEqual(this.props, nextProps)) return;

if (this.type === DocumentType.Mutation) {
Expand Down Expand Up @@ -312,7 +326,7 @@ export default function graphql(

const queryOptions = this.calculateOptions(this.props);
const fragments = calculateFragments(queryOptions.fragments);
const { variables, forceFetch, skip } = queryOptions as QueryOptions;
const { variables, forceFetch, skip } = queryOptions as QueryOptions; // tslint:disable-line

let queryData = assign({}, defaultQueryData) as any;
queryData.variables = variables;
Expand Down Expand Up @@ -558,6 +572,8 @@ export default function graphql(
}

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

const { haveOwnPropsChanged, hasOperationDataChanged, renderedElement, props, data } = this;

this.haveOwnPropsChanged = false;
Expand Down
148 changes: 147 additions & 1 deletion test/react-web/client/graphql/queries-1.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ describe('queries', () => {
renderer.create(<ProviderMock client={client}><ChangingProps /></ProviderMock>);
});

it('allows you to skip a query', (done) => {
it('allows you to skip a query (deprecated)', (done) => {
const query = gql`query people { allPeople(first: 1) { people { name } } }`;
const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
Expand All @@ -400,6 +400,152 @@ describe('queries', () => {
}, 25);
});

it('allows you to skip a query without running it', (done) => {
const query = gql`query people { allPeople(first: 1) { people { name } } }`;
const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
const client = new ApolloClient({ networkInterface });

let queryExecuted;
@graphql(query, { skip: ({ skip }) => skip })
class Container extends React.Component<any, any> {
componentWillReceiveProps(props) {
queryExecuted = true;
}
render() {
expect(this.props.data).toBeFalsy();
return null;
}
};

renderer.create(<ProviderMock client={client}><Container skip={true} /></ProviderMock>);

setTimeout(() => {
if (!queryExecuted) { done(); return; }
done(new Error('query ran even though skip present'));
}, 25);
});

it('doesn\'t run options or props when skipped', (done) => {
const query = gql`query people { allPeople(first: 1) { people { name } } }`;
const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
const client = new ApolloClient({ networkInterface });

let queryExecuted;
@graphql(query, {
skip: ({ skip }) => skip,
options: ({ willThrowIfAccesed }) => ({ pollInterval: willThrowIfAccesed.pollInterval }),
props: ({ willThrowIfAccesed }) => ({ pollInterval: willThrowIfAccesed.pollInterval }),
})
class Container extends React.Component<any, any> {
componentWillReceiveProps(props) {
queryExecuted = true;
}
render() {
expect(this.props.data).toBeFalsy();
return null;
}
};

renderer.create(<ProviderMock client={client}><Container skip={true} /></ProviderMock>);

setTimeout(() => {
if (!queryExecuted) { done(); return; }
done(new Error('query ran even though skip present'));
}, 25);
});

it('allows you to skip a query without running it (alternate syntax)', (done) => {
const query = gql`query people { allPeople(first: 1) { people { name } } }`;
const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
const client = new ApolloClient({ networkInterface });

let queryExecuted;
@graphql(query, { skip: true })
class Container extends React.Component<any, any> {
componentWillReceiveProps(props) {
queryExecuted = true;
}
render() {
expect(this.props.data).toBeFalsy();
return null;
}
};

renderer.create(<ProviderMock client={client}><Container /></ProviderMock>);

setTimeout(() => {
if (!queryExecuted) { done(); return; }
done(new Error('query ran even though skip present'));
}, 25);
});

it('removes the injected props if skip becomes true', (done) => {
let count = 0;
const query = gql`
query people($first: Int) {
allPeople(first: $first) { people { name } }
}
`;

const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const variables1 = { first: 1 };

const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
const variables2 = { first: 2 };

const networkInterface = mockNetworkInterface(
{ request: { query, variables: variables1 }, result: { data: data1 } },
{ request: { query, variables: variables2 }, result: { data: data2 } }
);

const client = new ApolloClient({ networkInterface });

@graphql(query, {
skip: () => count === 1,
options: (props) => ({ variables: props }),
})
class Container extends React.Component<any, any> {
componentWillReceiveProps({ data }) {
// loading is true, but data still there
if (count === 0) expect(data.allPeople).toEqual(data1.allPeople);
if (count === 1 ) expect(data).toBeFalsy();
if (count === 2 && data.loading) expect(data.allPeople).toBeFalsy();
if (count === 2 && !data.loading) {
expect(data.allPeople).toEqual(data2.allPeople);
done();
}
}
render() {
return null;
}
};

class ChangingProps extends React.Component<any, any> {
state = { first: 1 };

componentDidMount() {
setTimeout(() => {
count++;
this.setState({ first: 2 });
}, 50);

setTimeout(() => {
count++;
this.setState({ first: 3 });
}, 100);
}

render() {
return <Container first={this.state.first} />;
}
}

renderer.create(<ProviderMock client={client}><ChangingProps /></ProviderMock>);
});

it('reruns the query if it changes', (done) => {
let count = 0;
const query = gql`
Expand Down
46 changes: 46 additions & 0 deletions test/react-web/server/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,52 @@ describe('SSR', () => {
;
});

it('should correctly skip queries (deprecated)', () => {

const query = gql`{ currentUser { firstName } }`;
const data = { currentUser: { firstName: 'James' } };
const networkInterface = mockNetworkInterface(
{ request: { query }, result: { data }, delay: 50 }
);
const apolloClient = new ApolloClient({ networkInterface });

const WrappedElement = graphql(query, { options: { skip: true }})(({ data }) => (
<div>{data.loading ? 'loading' : 'skipped'}</div>
));

const app = (<ApolloProvider client={apolloClient}><WrappedElement /></ApolloProvider>);

return getDataFromTree(app)
.then(() => {
const markup = ReactDOM.renderToString(app);
expect(markup).toMatch(/skipped/);
})
;
});

it('should correctly skip queries (deprecated)', () => {

const query = gql`{ currentUser { firstName } }`;
const data = { currentUser: { firstName: 'James' } };
const networkInterface = mockNetworkInterface(
{ request: { query }, result: { data }, delay: 50 }
);
const apolloClient = new ApolloClient({ networkInterface });

const WrappedElement = graphql(query, { skip: true })(({ data }) => (
<div>{!data ? 'skipped' : 'dang'}</div>
));

const app = (<ApolloProvider client={apolloClient}><WrappedElement /></ApolloProvider>);

return getDataFromTree(app)
.then(() => {
const markup = ReactDOM.renderToString(app);
expect(markup).toMatch(/skipped/);
})
;
});

it('should run return the initial state for hydration', () => {
const query = gql`{ currentUser { firstName } }`;
const data = { currentUser: { firstName: 'James' } };
Expand Down