├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json └── src ├── client ├── App.jsx ├── index.html ├── index.js └── reducer.js └── server └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | /node_modules 4 | /npm-debug.log 5 | /build 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Daishi Kato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-apollo-github-api-infinite-scroll-example 2 | =============================================== 3 | 4 | This is example code with the following libraries/services: 5 | - [apollo-client](https://github.com/apollographql/apollo-client) 6 | - [react-apollo](https://github.com/apollographql/react-apollo) 7 | - [GitHub GraphQL API v4](https://developer.github.com/v4/) 8 | - [react-infinite-scroller](https://github.com/CassetteRocks/react-infinite-scroller) 9 | 10 | How to run 11 | ---------- 12 | 13 | ``` 14 | $ GITHUB_CLIENT_ID=... GITHUB_CLIENT_SECRET=... npm start 15 | ``` 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-apollo-github-api-infinite-scroll-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "React Apollo GitHub GraphQL API infinite scroll example code", 6 | "scripts": { 7 | "test": "npm run eslint", 8 | "eslint": "eslint --ext .js,.jsx .", 9 | "start": "node src/server", 10 | "build": "express-react-redux build-client" 11 | }, 12 | "dependencies": { 13 | "apollo-cache-inmemory": "^1.1.0", 14 | "apollo-client": "^2.0.3", 15 | "apollo-link-http": "^1.2.0", 16 | "express": "^4.16.2", 17 | "express-react-redux": "^0.5.2", 18 | "graphql": "^0.11.7", 19 | "graphql-tag": "^2.5.0", 20 | "react": "^16.1.1", 21 | "react-apollo": "^2.0.1", 22 | "react-compose-state": "^1.1.0", 23 | "react-dom": "^16.1.1", 24 | "react-infinite-scroller": "^1.1.1", 25 | "request": "^2.83.0" 26 | }, 27 | "devDependencies": { 28 | "eslint": "^4.11.0", 29 | "eslint-config-airbnb": "^16.1.0", 30 | "eslint-plugin-import": "^2.8.0", 31 | "eslint-plugin-jsx-a11y": "^6.0.2", 32 | "eslint-plugin-react": "^7.5.1" 33 | }, 34 | "license": "MIT", 35 | "eslintConfig": { 36 | "extends": [ 37 | "airbnb" 38 | ], 39 | "rules": { 40 | "global-require": 0, 41 | "react/prop-types": 0 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/client/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import gql from 'graphql-tag'; 3 | import { graphql } from 'react-apollo'; 4 | import { composeWithState } from 'react-compose-state'; 5 | import InfiniteScroll from 'react-infinite-scroller'; 6 | 7 | const RepoList = ({ 8 | data: { 9 | loading, error, search, loadMore, 10 | }, 11 | }) => { 12 | if (loading) return

Loading...

; 13 | if (error) { 14 | return ( 15 |
16 |

Not Logged In

17 | Log in with GitHub 18 |
19 | ); 20 | } 21 | const { 22 | nodes = [], 23 | pageInfo: { hasNextPage } = {}, 24 | } = search || {}; 25 | return ( 26 | Loading...

} 30 | > 31 | 36 |
37 | ); 38 | }; 39 | 40 | const QUERY_REPOS = gql` 41 | query ($q: String!, $end: String) { 42 | search(first: 20, type: REPOSITORY, query: $q, after: $end) { 43 | nodes { 44 | ... on Repository { 45 | name 46 | url 47 | } 48 | } 49 | pageInfo { 50 | endCursor 51 | hasNextPage 52 | } 53 | } 54 | } 55 | `; 56 | 57 | const withQuery = graphql(QUERY_REPOS, { 58 | options: ({ q }) => ({ variables: { q } }), 59 | props: ({ data }) => ({ 60 | data: { 61 | ...data, 62 | loadMore: () => data.fetchMore({ 63 | variables: { end: data.search.pageInfo.endCursor }, 64 | updateQuery: (previousResult = {}, { fetchMoreResult = {} }) => { 65 | const previousSearch = previousResult.search || {}; 66 | const currentSearch = fetchMoreResult.search || {}; 67 | const previousNodes = previousSearch.nodes || []; 68 | const currentNodes = currentSearch.nodes || []; 69 | return { 70 | ...previousResult, 71 | search: { 72 | ...previousSearch, 73 | nodes: [...previousNodes, ...currentNodes], 74 | pageInfo: currentSearch.pageInfo, 75 | }, 76 | }; 77 | }, 78 | }), 79 | }, 80 | }), 81 | }); 82 | 83 | const RepoListWithQuery = withQuery(RepoList); 84 | 85 | const App = ({ searchKeyword, setSearchKeyword }) => ( 86 |
87 | setSearchKeyword(evt.target.value)} 90 | /> 91 | 92 |
93 | ); 94 | 95 | const withState = composeWithState({ searchKeyword: '' }); 96 | 97 | export default withState(App); 98 | -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | React Apollo GitHub GraphQL API infinite scroll example code 4 | 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /src/client/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import { createElement as h } from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { ApolloClient } from 'apollo-client'; 6 | import { HttpLink } from 'apollo-link-http'; 7 | import { InMemoryCache } from 'apollo-cache-inmemory'; 8 | import { ApolloProvider } from 'react-apollo'; 9 | 10 | import App from './App'; 11 | 12 | const createApolloClient = () => { 13 | const match = /access_token=([a-f0-9]+)/.exec(window.location.hash); 14 | const accessToken = match && match[1]; 15 | const link = new HttpLink({ 16 | uri: 'https://api.github.com/graphql', 17 | headers: { 18 | authorization: `Bearer ${accessToken}`, 19 | }, 20 | }); 21 | const cache = new InMemoryCache(); 22 | return new ApolloClient({ link, cache }); 23 | }; 24 | 25 | const render = (Component) => { 26 | ReactDOM.render( 27 | h(ApolloProvider, { client: createApolloClient() }, h(Component)), 28 | document.getElementById('app'), 29 | ); 30 | }; 31 | 32 | render(App); 33 | -------------------------------------------------------------------------------- /src/client/reducer.js: -------------------------------------------------------------------------------- 1 | // unused 2 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const querystring = require('querystring'); 3 | const request = require('request'); 4 | 5 | const app = express(); 6 | 7 | app.get('/auth/github', (req, res) => { 8 | const query = querystring.stringify({ 9 | client_id: process.env.GITHUB_CLIENT_ID, 10 | }); 11 | res.redirect(`https://github.com/login/oauth/authorize?${query}`); 12 | }); 13 | 14 | app.get('/auth/github/callback', async (req, res) => { 15 | const { code } = req.query; 16 | request({ 17 | uri: 'https://github.com/login/oauth/access_token', 18 | qs: { 19 | client_id: process.env.GITHUB_CLIENT_ID, 20 | client_secret: process.env.GITHUB_CLIENT_SECRET, 21 | code, 22 | }, 23 | }, (error, response, body) => { 24 | if (error) { 25 | res.status(500).send(); 26 | } else { 27 | res.redirect(`/#${body}`); 28 | } 29 | }); 30 | }); 31 | 32 | app.use(require('express-react-redux')()); 33 | 34 | app.listen(process.env.PORT || 3000); 35 | --------------------------------------------------------------------------------