Skip to content

Commit 3ab9c75

Browse files
committed
10-GraphQL Caching of Queries with Apollo Client in React
1 parent 65cb143 commit 3ab9c75

File tree

9 files changed

+298
-10
lines changed

9 files changed

+298
-10
lines changed

package-lock.json

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"react": "^16.6.0",
1414
"react-apollo": "^2.2.4",
1515
"react-dom": "^16.6.0",
16+
"react-router-dom": "^4.3.1",
1617
"react-scripts": "2.0.5"
1718
},
1819
"scripts": {

src/App/Navigation/index.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from 'react';
2+
import { Link, withRouter } from 'react-router-dom';
3+
4+
import * as routes from '../../constants/routes';
5+
import Button from '../../Button';
6+
import Input from '../../Input';
7+
8+
import './style.css';
9+
10+
const Navigation = ({
11+
location: { pathname },
12+
organizationName,
13+
onOrganizationSearch,
14+
}) => (
15+
<header className="Navigation">
16+
<div className="Navigation-link">
17+
<Link to={routes.PROFILE}>Profile</Link>
18+
</div>
19+
<div className="Navigation-link">
20+
<Link to={routes.ORGANIZATION}>Organization</Link>
21+
</div>
22+
23+
{pathname === routes.ORGANIZATION && (
24+
<OrganizationSearch
25+
organizationName={organizationName}
26+
onOrganizationSearch={onOrganizationSearch}
27+
/>
28+
)}
29+
</header>
30+
);
31+
32+
class OrganizationSearch extends React.Component {
33+
state = {
34+
value: this.props.organizationName,
35+
};
36+
37+
onChange = event => {
38+
this.setState({ value: event.target.value });
39+
};
40+
41+
onSubmit = event => {
42+
this.props.onOrganizationSearch(this.state.value);
43+
44+
event.preventDefault();
45+
};
46+
47+
render() {
48+
const { value } = this.state;
49+
50+
return (
51+
<div className="Navigation-search">
52+
<form onSubmit={this.onSubmit}>
53+
<Input
54+
color={'white'}
55+
type="text"
56+
value={value}
57+
onChange={this.onChange}
58+
/>{' '}
59+
<Button color={'white'} type="submit">
60+
Search
61+
</Button>
62+
</form>
63+
</div>
64+
);
65+
}
66+
}
67+
68+
export default withRouter(Navigation);

src/App/index.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,60 @@
11
import React, { Component } from 'react';
2+
import { BrowserRouter as Router, Route } from 'react-router-dom';
23

4+
import Navigation from './Navigation';
5+
import Footer from './Footer';
36
import Profile from '../Profile';
7+
import Organization from '../Organization';
8+
9+
import * as routes from '../constants/routes';
10+
11+
import './style.css';
412

513
class App extends Component {
14+
state = {
15+
organizationName: 'the-road-to-learn-react',
16+
};
17+
18+
onOrganizationSearch = value => {
19+
this.setState({ organizationName: value });
20+
};
21+
622
render() {
7-
return <Profile />;
23+
const { organizationName } = this.state;
24+
25+
return (
26+
<Router>
27+
<div className="App">
28+
<Navigation
29+
organizationName={organizationName}
30+
onOrganizationSearch={this.onOrganizationSearch}
31+
/>
32+
33+
<div className="App-main">
34+
<Route
35+
exact
36+
path={routes.ORGANIZATION}
37+
component={() => (
38+
<div className="App-content_large-header">
39+
<Organization organizationName={organizationName} />
40+
</div>
41+
)}
42+
/>
43+
<Route
44+
exact
45+
path={routes.PROFILE}
46+
component={() => (
47+
<div className="App-content_small-header">
48+
<Profile />
49+
</div>
50+
)}
51+
/>
52+
</div>
53+
54+
<Footer />
55+
</div>
56+
</Router>
57+
);
858
}
959
}
1060

src/Input/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
import './style.css';
4+
5+
const Input = ({ children, color = 'black', ...props }) => (
6+
<input className={`Input Input_${color}`} {...props}>
7+
{children}
8+
</input>
9+
);
10+
11+
export default Input;

src/Organization/index.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from 'react';
2+
import gql from 'graphql-tag';
3+
import { Query } from 'react-apollo';
4+
5+
import RepositoryList, { REPOSITORY_FRAGMENT } from '../Repository';
6+
import Loading from '../Loading';
7+
import ErrorMessage from '../Error';
8+
9+
const GET_REPOSITORIES_OF_ORGANIZATION = gql`
10+
query($organizationName: String!, $cursor: String) {
11+
organization(login: $organizationName) {
12+
repositories(first: 5, after: $cursor) {
13+
edges {
14+
node {
15+
...repository
16+
}
17+
}
18+
pageInfo {
19+
endCursor
20+
hasNextPage
21+
}
22+
}
23+
}
24+
}
25+
${REPOSITORY_FRAGMENT}
26+
`;
27+
28+
const Organization = ({ organizationName }) => (
29+
<Query
30+
query={GET_REPOSITORIES_OF_ORGANIZATION}
31+
variables={{
32+
organizationName,
33+
}}
34+
skip={organizationName === ''}
35+
notifyOnNetworkStatusChange={true}
36+
>
37+
{({ data, loading, error, fetchMore }) => {
38+
if (error) {
39+
return <ErrorMessage error={error} />;
40+
}
41+
42+
const { organization } = data;
43+
44+
if (loading && !organization) {
45+
return <Loading />;
46+
}
47+
48+
return (
49+
<RepositoryList
50+
loading={loading}
51+
repositories={organization.repositories}
52+
fetchMore={fetchMore}
53+
entry={'organization'}
54+
/>
55+
);
56+
}}
57+
</Query>
58+
);
59+
60+
export default Organization;

src/Profile/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const Profile = () => (
5151
loading={loading}
5252
repositories={viewer.repositories}
5353
fetchMore={fetchMore}
54+
entry={'viewer'}
5455
/>
5556
);
5657
}}

src/Repository/RepositoryList/index.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,36 @@ import RepositoryItem from '../RepositoryItem';
55

66
import '../style.css';
77

8-
const updateQuery = (previousResult, { fetchMoreResult }) => {
8+
const getUpdateQuery = entry => (
9+
previousResult,
10+
{ fetchMoreResult },
11+
) => {
912
if (!fetchMoreResult) {
1013
return previousResult;
1114
}
1215

1316
return {
1417
...previousResult,
15-
viewer: {
16-
...previousResult.viewer,
18+
[entry]: {
19+
...previousResult[entry],
1720
repositories: {
18-
...previousResult.viewer.repositories,
19-
...fetchMoreResult.viewer.repositories,
21+
...previousResult[entry].repositories,
22+
...fetchMoreResult[entry].repositories,
2023
edges: [
21-
...previousResult.viewer.repositories.edges,
22-
...fetchMoreResult.viewer.repositories.edges,
24+
...previousResult[entry].repositories.edges,
25+
...fetchMoreResult[entry].repositories.edges,
2326
],
2427
},
2528
},
2629
};
2730
};
2831

29-
const RepositoryList = ({ repositories, loading, fetchMore }) => (
32+
const RepositoryList = ({
33+
repositories,
34+
loading,
35+
fetchMore,
36+
entry,
37+
}) => (
3038
<Fragment>
3139
{repositories.edges.map(({ node }) => (
3240
<div key={node.id} className="RepositoryItem">
@@ -40,7 +48,7 @@ const RepositoryList = ({ repositories, loading, fetchMore }) => (
4048
variables={{
4149
cursor: repositories.pageInfo.endCursor,
4250
}}
43-
updateQuery={updateQuery}
51+
updateQuery={getUpdateQuery(entry)}
4452
fetchMore={fetchMore}
4553
>
4654
Repositories

src/constants/routes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const ORGANIZATION = '/';
2+
export const PROFILE = '/profile';

0 commit comments

Comments
 (0)