Skip to content
Draft
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
7 changes: 3 additions & 4 deletions packages/apollo-fragment-react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "apollo-fragment-react",
"version": "0.7.0",
"description": "A Query component to connect React components to GraphQL fragments",
"description": "A React hook to connect React components to GraphQL fragments in Apollo Client cache",
"author": "Abhi Aiyer <abhiaiyer91@gmail.com>",
"license": "MIT",
"main": "./lib/bundle.umd.js",
Expand Down Expand Up @@ -42,10 +42,10 @@
}
],
"peerDependencies": {
"apollo-link-state": "0.4.1",
"react-apollo": "^3.0.0-beta.3"
"@apollo/client": "^3.2.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"@apollo/client": "^3.2.0"
"@apollo/client": "^3.5.6"

At this point, @apollo/client is up to 3.5.6

},
"devDependencies": {
"@apollo/client": "^3.2.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"@apollo/client": "^3.2.0",
"@apollo/client": "^3.5.6",

Same here

"@types/graphql": "0.11.5",
"@types/jest": "22.1.x",
"apollo-cache-inmemory": "^1.1.5",
Expand All @@ -64,7 +64,6 @@
"pre-commit": "1.2.2",
"prettier": "1.7.4",
"react": "^16.8.6",
"react-apollo": "^3.0.0-beta.3",
"react-dom": "^16.8.6",
"rimraf": "2.6.1",
"rollup": "0.56.x",
Expand Down
187 changes: 5 additions & 182 deletions packages/apollo-fragment-react/src/__tests__/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import * as React from 'react';
import gql from 'graphql-tag';
import { ApolloLink } from 'apollo-link';
import { ApolloClient } from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient, InMemoryCache, ApolloLink, gql } from '@apollo/client';
import { ApolloProvider } from '@apollo/client/react';
import { mount, ReactWrapper } from 'enzyme';
import mockLink from '../mocks/mockLink';
import {
fragmentCacheRedirect,
fragmentLinkState,
} from '../../../apollo-link-state-fragment/src';
import { ApolloFragment, withApolloFragment, useApolloFragment } from '../';
import { fragmentCacheConfig, useApolloFragment } from '../';

type Wrapper = ReactWrapper<any, any> | null;

Expand All @@ -28,18 +21,12 @@ const fragment = `

const createTestClient = () => {
const cache = new InMemoryCache({
cacheRedirects: {
Query: {
...fragmentCacheRedirect(),
},
},
...fragmentCacheConfig,
});

const local = fragmentLinkState(cache);

return new ApolloClient({
cache,
link: ApolloLink.from([local, mockLink]),
link: ApolloLink.from([mockLink]),
});
};

Expand Down Expand Up @@ -68,73 +55,6 @@ describe('apollo-fragment-react core behaviour', () => {
}
`;

it('Should return Fragment Data from HOC Component', () => {
return client
.query({
query: PEOPLE_QUERY,
})
.then(() => {
let SomeComponent: React.ComponentType<any> = function Foo(props) {
expect(props.data.id).toEqual('1');
expect(props.data.name).toEqual('John Smith');
return <p>hi</p>;
};

SomeComponent = withApolloFragment(fragment)(SomeComponent);

wrapper = mount(
<ApolloProvider client={client}>
<SomeComponent id="1" />
</ApolloProvider>,
);
});
});

it('Should return Fragment Data from HOC Component with custom "id"', () => {
return client
.query({
query: PEOPLE_QUERY,
})
.then(() => {
let SomeComponent: React.ComponentType<any> = function Foo(props) {
expect(props.data.id).toEqual('1');
expect(props.data.name).toEqual('John Smith');
return <p>hi</p>;
};

SomeComponent = withApolloFragment(
fragment,
'fragmentId',
)(SomeComponent);

wrapper = mount(
<ApolloProvider client={client}>
<SomeComponent fragmentId="1" />
</ApolloProvider>,
);
});
});

it('Should return Fragment Data from Render Prop Component', () => {
return client
.query({
query: PEOPLE_QUERY,
})
.then(() => {
wrapper = mount(
<ApolloProvider client={client}>
<ApolloFragment fragment={fragment} id="1">
{(result: any) => {
expect(result.data.id).toEqual('1');
expect(result.data.name).toEqual('John Smith');
return <p>hi</p>;
}}
</ApolloFragment>
</ApolloProvider>,
);
});
});

it('Should return Fragment Data from React Hook', () => {
return client
.query({
Expand Down Expand Up @@ -233,65 +153,6 @@ Available data:

Make sure that the fields requested in the fragment are fetched by some query`;

it('Should log error when fragment data is incomplete for HOC Component', () => {
return client
.query({
query: INCOMPLETE_QUERY,
})
.then(() => {
let SomeComponent: React.ComponentType<any> = function Foo(props) {
expect(props.data).toBeUndefined();
return <p>hi</p>;
};

SomeComponent = withApolloFragment(fragment)(SomeComponent);

wrapper = mountWithApollo(<SomeComponent id="1" />);

expect(consoleOutput[0]).toEqual(expectedErrorMessage);
});
});

it('Should log error when fragment data is incomplete for HOC Component with custom "id"', () => {
return client
.query({
query: INCOMPLETE_QUERY,
})
.then(() => {
let SomeComponent: React.ComponentType<any> = function Foo(props) {
expect(props.data).toBeUndefined();
return <p>hi</p>;
};

SomeComponent = withApolloFragment(
fragment,
'fragmentId',
)(SomeComponent);

wrapper = mountWithApollo(<SomeComponent fragmentId="1" />);

expect(consoleOutput[0]).toEqual(expectedErrorMessage);
});
});

it('Should log error when fragment data is incomplete for Render Prop Component', () => {
return client
.query({
query: INCOMPLETE_QUERY,
})
.then(() => {
wrapper = mountWithApollo(
<ApolloFragment fragment={fragment} id="1">
{(result: any) => {
expect(result.data).toEqual({});
return <p>hi</p>;
}}
</ApolloFragment>,
);
expect(consoleOutput[0]).toEqual(expectedErrorMessage);
});
});

it('Should log error when fragment data is incomplete for hook', () => {
return client
.query({
Expand Down Expand Up @@ -331,44 +192,6 @@ Make sure that the fields requested in the fragment are fetched by some query`;
});
});

it('Should NOT log error when fragment data is empty for HOC Component', () => {
let SomeComponent: React.ComponentType<any> = function Foo(props) {
expect(props.data).toBeUndefined();
return <p>hi</p>;
};

SomeComponent = withApolloFragment(fragment)(SomeComponent);

wrapper = mountWithApollo(<SomeComponent id="1" />);

expect(consoleOutput).toHaveLength(0);
});

it('Should NOT log error when fragment data is empty for HOC Component with custom "id"', () => {
let SomeComponent: React.ComponentType<any> = function Foo(props) {
expect(props.data).toBeUndefined();
return <p>hi</p>;
};

SomeComponent = withApolloFragment(fragment, 'fragmentId')(SomeComponent);

wrapper = mountWithApollo(<SomeComponent fragmentId="1" />);

expect(consoleOutput).toHaveLength(0);
});

it('Should NOT log error when fragment data is empty for Render Prop Component', () => {
wrapper = mountWithApollo(
<ApolloFragment fragment={fragment} id="1">
{(result: any) => {
expect(result.data).toEqual({});
return <p>hi</p>;
}}
</ApolloFragment>,
);
expect(consoleOutput).toHaveLength(0);
});

it('Should NOT log error when fragment data is empty for hook', () => {
let SomeComponent = function Foo() {
const fragmentData = useApolloFragment<FragmentData>(fragment, '1');
Expand Down
108 changes: 16 additions & 92 deletions packages/apollo-fragment-react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
import * as React from 'react';
import {
Query,
graphql,
useQuery,
withApollo,
QueryResult,
} from 'react-apollo';
import { ApolloClient, InMemoryCacheConfig } from '@apollo/client';
import { useQuery, QueryResult } from '@apollo/client/react';
import { DocumentNode, Location } from 'graphql';
import { getFragmentInfo, buildFragmentQuery } from 'apollo-fragment-utils';
import ApolloClient from 'apollo-client';
// compose-tiny doesn't have a default export, so we have to use `* as`
// However, rollup creates a synthetic default module, so we have to to import it with `default as`.
import * as _compose from 'compose-tiny';
// @ts-ignore
import { default as _rollupCompose } from 'compose-tiny';

const compose = _rollupCompose || _compose;

type FragmentQueryData<TData = any> = {
getFragment?: TData;
Expand Down Expand Up @@ -68,87 +55,24 @@ export function useApolloFragment<TData = any>(
};
}

export function withApolloFragment(
fragment: SupportedFragment,
idPropName: string = 'id',
) {
const fragmentQuery = createFragmentQuery(fragment);
return compose(
withApollo,
graphql<any, any>(fragmentQuery.query, {
options: props => {
return {
fetchPolicy: 'cache-only',
variables: {
id: props[idPropName],
__typename: fragmentQuery.fragmentTypeName,
},
};
},
props: ({ data, ownProps: { client, ...ownProps }, ...rest }) => {
const fragmentData = data && data.getFragment;

if (!fragmentData) {
checkDataCompleteness({
fragmentQuery,
id: ownProps[idPropName],
client,
export const fragmentCacheConfig: Required<Pick<
InMemoryCacheConfig,
'typePolicies'
>> = {
typePolicies: {
Query: {
fields: {
getFragment(_, { args, toReference }) {
return toReference({
id: args ? args.id : undefined,
__typename: args ? args.__typename : undefined,
});
}

return {
data: fragmentData,
ownProps,
...rest,
};
},
},
}),
);
}

type ApolloFragmentChildrenData<TData = any> = Omit<
ApolloFragmentResult<TData>,
'data'
> & { data: ApolloFragmentResult<TData>['data'] | {} };

export type ApolloFragmentProps<TData = any> = {
id: string;
fragment: SupportedFragment;
children: (
fragmentQueryResult: ApolloFragmentChildrenData<TData>,
) => React.ReactElement;
},
},
};

export function ApolloFragment<TData = any>({
children,
fragment,
id,
}: ApolloFragmentProps<TData>) {
const fragmentQuery = createFragmentQuery(fragment);

return (
<Query<FragmentQueryData<TData>>
fetchPolicy="cache-only"
query={fragmentQuery.query}
variables={{ id, __typename: fragmentQuery.fragmentTypeName }}
>
{({ data, client, ...rest }) => {
const fragmentData = data && data.getFragment;

if (!fragmentData) {
checkDataCompleteness({
fragmentQuery,
id,
client,
});
}

return children({ data: fragmentData || {}, client, ...rest });
}}
</Query>
);
}

type FragmentQuery = {
query: DocumentNode;
fragmentTypeName: string;
Expand Down
9 changes: 1 addition & 8 deletions packages/apollo-fragment-react/src/mocks/mockLink.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { graphql, print, ExecutionResult } from 'graphql';
import {
Operation,
GraphQLRequest,
ApolloLink,
FetchResult,
Observable,
// Observer,
} from 'apollo-link';
import { ApolloLink, FetchResult, Observable } from '@apollo/client';
import { schema } from './mockSchema';

export default new ApolloLink(operation => {
Expand Down