Skip to content

Commit 1a9cd36

Browse files
committed
implement watch feature
1 parent 86e68e4 commit 1a9cd36

File tree

2 files changed

+86
-76
lines changed

2 files changed

+86
-76
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# react-apollo-client-optimistic-ui-example
22

3-
A minimal React application using Apollo Client with GitHub's GraphQL API. As example Apollo Client's optimistic UI feature is implemented.
3+
A minimal React application using Apollo Client with GitHub's GraphQL API. As example **Apollo Client's optimistic UI feature is implemented**.
44

55
## Installation
66

src/App.js

Lines changed: 85 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,89 @@ import { Query, Mutation } from 'react-apollo';
44

55
import './App.css';
66

7+
const REPOSITORY_FRAGMENT = gql`
8+
fragment repository on Repository {
9+
id
10+
name
11+
url
12+
watchers {
13+
totalCount
14+
}
15+
viewerSubscription
16+
}
17+
`;
18+
719
const GET_REPOSITORIES_OF_ORGANIZATION = gql`
820
{
921
organization(login: "the-road-to-learn-react") {
1022
repositories(first: 20) {
1123
edges {
1224
node {
13-
id
14-
name
15-
url
16-
viewerHasStarred
25+
...repository
1726
}
1827
}
1928
}
2029
}
2130
}
31+
32+
${REPOSITORY_FRAGMENT}
2233
`;
2334

24-
const STAR_REPOSITORY = gql`
25-
mutation($id: ID!) {
26-
addStar(input: { starrableId: $id }) {
27-
starrable {
35+
const WATCH_REPOSITORY = gql`
36+
mutation($id: ID!, $viewerSubscription: SubscriptionState!) {
37+
updateSubscription(
38+
input: { state: $viewerSubscription, subscribableId: $id }
39+
) {
40+
subscribable {
2841
id
29-
viewerHasStarred
42+
viewerSubscription
3043
}
3144
}
3245
}
3346
`;
3447

48+
const VIEWER_SUBSCRIPTIONS = {
49+
SUBSCRIBED: 'SUBSCRIBED',
50+
UNSUBSCRIBED: 'UNSUBSCRIBED',
51+
};
52+
53+
const isWatch = viewerSubscription =>
54+
viewerSubscription === VIEWER_SUBSCRIPTIONS.SUBSCRIBED;
55+
56+
const updateWatch = (
57+
client,
58+
{
59+
data: {
60+
updateSubscription: {
61+
subscribable: { id, viewerSubscription },
62+
},
63+
},
64+
},
65+
) => {
66+
const repository = client.readFragment({
67+
id: `Repository:${id}`,
68+
fragment: REPOSITORY_FRAGMENT,
69+
});
70+
71+
let { totalCount } = repository.watchers;
72+
totalCount =
73+
viewerSubscription === VIEWER_SUBSCRIPTIONS.SUBSCRIBED
74+
? totalCount + 1
75+
: totalCount - 1;
76+
77+
client.writeFragment({
78+
id: `Repository:${id}`,
79+
fragment: REPOSITORY_FRAGMENT,
80+
data: {
81+
...repository,
82+
watchers: {
83+
...repository.watchers,
84+
totalCount,
85+
},
86+
},
87+
});
88+
};
89+
3590
const App = () => (
3691
<Query query={GET_REPOSITORIES_OF_ORGANIZATION}>
3792
{({ data: { organization }, loading }) => {
@@ -46,79 +101,34 @@ const App = () => (
46101
</Query>
47102
);
48103

49-
class Repositories extends React.Component {
50-
state = {
51-
selectedRepositoryIds: [],
52-
};
53-
54-
toggleSelectRepository = (id, isSelected) => {
55-
let { selectedRepositoryIds } = this.state;
56-
57-
selectedRepositoryIds = isSelected
58-
? selectedRepositoryIds.filter(itemId => itemId !== id)
59-
: selectedRepositoryIds.concat(id);
60-
61-
this.setState({ selectedRepositoryIds });
62-
};
63-
64-
render() {
65-
return (
66-
<RepositoryList
67-
repositories={this.props.repositories}
68-
selectedRepositoryIds={this.state.selectedRepositoryIds}
69-
toggleSelectRepository={this.toggleSelectRepository}
70-
/>
71-
);
72-
}
73-
}
74-
75-
const RepositoryList = ({
76-
repositories,
77-
selectedRepositoryIds,
78-
toggleSelectRepository,
79-
}) => (
104+
const Repositories = ({ repositories }) => (
80105
<ul>
81-
{repositories.edges.map(({ node }) => {
82-
const isSelected = selectedRepositoryIds.includes(node.id);
83-
84-
const rowClassName = ['row'];
85-
86-
if (isSelected) {
87-
rowClassName.push('row_selected');
88-
}
89-
90-
return (
91-
<li className={rowClassName.join(' ')} key={node.id}>
92-
<Select
93-
id={node.id}
94-
isSelected={isSelected}
95-
toggleSelectRepository={toggleSelectRepository}
96-
/>{' '}
97-
<a href={node.url}>{node.name}</a>{' '}
98-
{!node.viewerHasStarred && <Star id={node.id} />}
99-
</li>
100-
);
101-
})}
106+
{repositories.edges.map(({ node }) => (
107+
<li key={node.id}>
108+
<a href={node.url}>{node.name}</a> <Watch repository={node} />
109+
</li>
110+
))}
102111
</ul>
103112
);
104113

105-
const Star = ({ id }) => (
106-
<Mutation mutation={STAR_REPOSITORY} variables={{ id }}>
107-
{starRepository => (
108-
<button type="button" onClick={starRepository}>
109-
Star
114+
const Watch = ({ repository }) => (
115+
<Mutation
116+
mutation={WATCH_REPOSITORY}
117+
variables={{
118+
id: repository.id,
119+
viewerSubscription: isWatch(repository.viewerSubscription)
120+
? VIEWER_SUBSCRIPTIONS.UNSUBSCRIBED
121+
: VIEWER_SUBSCRIPTIONS.SUBSCRIBED,
122+
}}
123+
update={updateWatch}
124+
>
125+
{(updateSubscription, { data, loading, error }) => (
126+
<button type="button" onClick={updateSubscription}>
127+
{repository.watchers.totalCount}{' '}
128+
{isWatch(repository.viewerSubscription) ? 'Unwatch' : 'Watch'}
110129
</button>
111130
)}
112131
</Mutation>
113132
);
114133

115-
const Select = ({ id, isSelected, toggleSelectRepository }) => (
116-
<button
117-
type="button"
118-
onClick={() => toggleSelectRepository(id, isSelected)}
119-
>
120-
{isSelected ? 'Unselect' : 'Select'}
121-
</button>
122-
);
123-
124134
export default App;

0 commit comments

Comments
 (0)