├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── issue-tracker ├── .babelrc ├── .eslintrc ├── .gitignore ├── .prettierrc ├── README.md ├── config-overrides.js ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── schema │ └── schema.graphql ├── src │ ├── ErrorBoundary.tsx │ ├── HomeRoot.tsx │ ├── IssueActions.tsx │ ├── IssueDetailComments.tsx │ ├── IssueDetailRoot.tsx │ ├── IssueListItem.tsx │ ├── Issues.tsx │ ├── JSResource.ts │ ├── RelayEnvironment.ts │ ├── Root.tsx │ ├── SuspenseImage.tsx │ ├── __generated__ │ │ ├── HomeRootIssuesQuery.graphql.ts │ │ ├── IssueActionsAddCommentMutation.graphql.ts │ │ ├── IssueActionsCloseIssueMutation.graphql.ts │ │ ├── IssueActionsReopenIssueMutation.graphql.ts │ │ ├── IssueActions_issue.graphql.ts │ │ ├── IssueDetailCommentsQuery.graphql.ts │ │ ├── IssueDetailComments_issue.graphql.ts │ │ ├── IssueDetailRootQuery.graphql.ts │ │ ├── IssueListItem_issue.graphql.ts │ │ ├── IssuesPaginationQuery.graphql.ts │ │ ├── Issues_repository.graphql.ts │ │ └── RootQuery.graphql.ts │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── routes.ts │ ├── routing │ │ ├── Link.tsx │ │ ├── RouteRenderer.css │ │ ├── RouteRenderer.tsx │ │ ├── RoutingContext.ts │ │ └── createRouter.ts │ └── useMutation.ts ├── tsconfig.json └── yarn.lock └── todo ├── .babelrc ├── .editorconfig ├── .gitignore ├── .prettierrc ├── README.md ├── data ├── database.js ├── schema.graphql └── schema.js ├── package.json ├── public ├── base.css ├── index.css ├── index.html └── learn.json ├── scripts └── updateSchema.js ├── server.js ├── ts ├── ErrorBoundaryWithRetry.tsx ├── __relay_artifacts__ │ ├── AddTodoMutation.graphql.ts │ ├── ChangeTodoStatusMutation.graphql.ts │ ├── MarkAllTodosMutation.graphql.ts │ ├── RemoveCompletedTodosMutation.graphql.ts │ ├── RemoveTodoMutation.graphql.ts │ ├── RenameTodoMutation.graphql.ts │ ├── TodoApp_viewer.graphql.ts │ ├── TodoListFooter_viewer.graphql.ts │ ├── TodoList_viewer.graphql.ts │ ├── TodoRootQuery.graphql.ts │ ├── Todo_todo.graphql.ts │ └── Todo_viewer.graphql.ts ├── app.tsx ├── components │ ├── Todo.tsx │ ├── TodoApp.tsx │ ├── TodoList.tsx │ ├── TodoListFooter.tsx │ ├── TodoRoot.tsx │ └── TodoTextInput.tsx ├── definitions.d.ts └── mutations │ ├── AddTodoMutation.ts │ ├── ChangeTodoStatusMutation.ts │ ├── MarkAllTodosMutation.ts │ ├── RemoveCompletedTodosMutation.ts │ ├── RemoveTodoMutation.ts │ └── RenameTodoMutation.ts ├── tsconfig.json ├── tslint.json └── yarn.lock /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Facebook has adopted a Code of Conduct that we expect project participants to adhere to. 4 | Please read the [full text](https://code.fb.com/codeofconduct/) 5 | so that you can understand what actions will and will not be tolerated. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We want to make contributing to this project as easy and transparent as possible. 4 | 5 | ## Our Development Process 6 | 7 | We actively welcome your input via pull requests or issues. 8 | 9 | ## Pull Requests 10 | 11 | 1. Fork the repo and create your branch from `master`. 12 | 2. If you haven't already, complete the Contributor License Agreement ("CLA"). 13 | 14 | ## Contributor License Agreement ("CLA") 15 | 16 | In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects. 17 | 18 | Complete your CLA [here](https://code.facebook.com/cla). 19 | 20 | ## Issues 21 | 22 | Ask questions, provide feedback, or open new topics for discussion by [opening an issue](https://github.com/relayjs/relay-examples/issues). 23 | 24 | ## License 25 | 26 | By contributing to relay-examples, you agree that your contributions will be licensed under the terms described in [LICENSE.md](https://github.com/relayjs/relay-examples/blob/master/LICENSE.md). 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-present, Facebook, Inc. All rights reserved. 2 | 3 | The examples provided by Facebook are for non-commercial testing and evaluation purposes only. Facebook reserves all rights not expressly granted. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Relay Examples [![Build Status](https://travis-ci.org/relayjs/relay-examples.svg?branch=master)](https://travis-ci.org/relayjs/relay-examples) 2 | 3 | A collection of example applications using [Relay](https://github.com/facebook/relay). 4 | 5 | # Contributing 6 | 7 | See [CONTRIBUTING.md](https://github.com/relayjs/relay-examples/blob/master/CONTRIBUTING.md). 8 | 9 | # License 10 | 11 | See [LICENSE.md](https://github.com/relayjs/relay-examples/blob/master/LICENSE.md). 12 | -------------------------------------------------------------------------------- /issue-tracker/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-proposal-optional-chaining"] 3 | } 4 | -------------------------------------------------------------------------------- /issue-tracker/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "react-app", 7 | "globals": { 8 | "Atomics": "readonly", 9 | "SharedArrayBuffer": "readonly" 10 | }, 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "jsx": true 15 | }, 16 | "ecmaVersion": 2018, 17 | "sourceType": "module" 18 | }, 19 | "plugins": ["react", "@typescript-eslint/eslint-plugin"], 20 | "rules": { 21 | "indent": ["error", 2], 22 | "linebreak-style": ["error", "unix"], 23 | "quotes": ["error", "single"], 24 | "semi": ["error", "never"] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /issue-tracker/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .vscode 26 | -------------------------------------------------------------------------------- /issue-tracker/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "semi": false 5 | } 6 | -------------------------------------------------------------------------------- /issue-tracker/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /issue-tracker/config-overrides.js: -------------------------------------------------------------------------------- 1 | const { useBabelRc, override } = require('customize-cra') 2 | module.exports = override(useBabelRc()) 3 | -------------------------------------------------------------------------------- /issue-tracker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "issue-tracker", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "yarn run relay; concurrently --kill-others --names \"react-app-rewired,relay\" \"react-app-rewired start\" \"yarn run relay --watch\"", 7 | "build": "yarn run relay && react-app-rewired build", 8 | "test": "react-app-rewired test", 9 | "eject": "react-scripts eject", 10 | "lint": "eslint --fix --ext .js,.ts,.tsx", 11 | "update-schema": "yarn get-graphql-schema -h \"Authorization=bearer $REACT_APP_GITHUB_AUTH_TOKEN\" https://api.github.com/graphql > schema/schema.graphql", 12 | "relay": "yarn run relay-compiler --schema schema/schema.graphql --src ./src/ $@ --language typescript" 13 | }, 14 | "dependencies": { 15 | "history": "^4.10.1", 16 | "react": "^0.0.0-experimental-38dd17ab9", 17 | "react-dom": "^0.0.0-experimental-38dd17ab9", 18 | "react-markdown": "^4.2.2", 19 | "react-relay": "^0.0.0-experimental-a1a40b68", 20 | "react-router": "^5.1.2", 21 | "react-router-config": "^5.1.1", 22 | "relay-runtime": "7.0.0" 23 | }, 24 | "devDependencies": { 25 | "@babel/plugin-proposal-optional-chaining": "^7.6.0", 26 | "@types/history": "^4.7.3", 27 | "@types/jest": "24.0.21", 28 | "@types/node": "12.12.5", 29 | "@types/react": "16.9.11", 30 | "@types/react-dom": "16.9.3", 31 | "@types/react-relay": "^7.0.0", 32 | "@types/react-router-config": "^5.0.1", 33 | "@types/relay-runtime": "^6.0.9", 34 | "@typescript-eslint/eslint-plugin": "^2.6.1", 35 | "@typescript-eslint/parser": "^2.6.1", 36 | "babel-plugin-relay": "7.0.0", 37 | "concurrently": "^5.0.0", 38 | "customize-cra": "^0.8.0", 39 | "eslint": "^6.6.0", 40 | "eslint-config-react-app": "^5.0.2", 41 | "eslint-plugin-flowtype": "^4.3.0", 42 | "eslint-plugin-import": "^2.18.2", 43 | "eslint-plugin-jsx-a11y": "^6.2.3", 44 | "eslint-plugin-react": "^7.16.0", 45 | "eslint-plugin-react-hooks": "^2.2.0", 46 | "get-graphql-schema": "^2.1.2", 47 | "graphql": "^14.5.8", 48 | "husky": "^3.0.9", 49 | "lint-staged": "^9.4.2", 50 | "prettier": "^1.19.1", 51 | "react-app-rewired": "^2.1.5", 52 | "react-scripts": "3.2.0", 53 | "relay-compiler": "7.0.0", 54 | "relay-compiler-language-typescript": "^10.1.0", 55 | "typescript": "^3.7.2" 56 | }, 57 | "browserslist": { 58 | "production": [ 59 | ">0.2%", 60 | "not dead", 61 | "not op_mini all" 62 | ], 63 | "development": [ 64 | "last 1 chrome version", 65 | "last 1 firefox version", 66 | "last 1 safari version" 67 | ] 68 | }, 69 | "husky": { 70 | "hooks": { 71 | "pre-commit": "lint-staged" 72 | } 73 | }, 74 | "lint-staged": { 75 | "src/**/*.{js,ts,jsx,tsx}": [ 76 | "yarn prettier --write", 77 | "yarn lint", 78 | "git add" 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /issue-tracker/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renanmav/relay-examples-typescript/aa6b3a97af23ab188ed2ae2b7637c17519b00450/issue-tracker/public/favicon.ico -------------------------------------------------------------------------------- /issue-tracker/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | Relay Example App 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /issue-tracker/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renanmav/relay-examples-typescript/aa6b3a97af23ab188ed2ae2b7637c17519b00450/issue-tracker/public/logo192.png -------------------------------------------------------------------------------- /issue-tracker/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renanmav/relay-examples-typescript/aa6b3a97af23ab188ed2ae2b7637c17519b00450/issue-tracker/public/logo512.png -------------------------------------------------------------------------------- /issue-tracker/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Relay Example App", 3 | "name": "Relay Example App - GitHub Issues Clone", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } -------------------------------------------------------------------------------- /issue-tracker/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /issue-tracker/src/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | interface State { 4 | error: { 5 | message: string 6 | source: {} 7 | } | null 8 | } 9 | 10 | /** 11 | * A reusable component for handling errors in a React (sub)tree. 12 | */ 13 | export default class ErrorBoundary extends Component { 14 | state: State = { error: null } 15 | 16 | static getDerivedStateFromError(error: Error) { 17 | return { 18 | error, 19 | } 20 | } 21 | 22 | render() { 23 | const { error } = this.state 24 | if (error) { 25 | return ( 26 |
27 |
Error: {error.message}
28 |
29 |
{JSON.stringify(error.source, null, 2)}
30 |
31 |
32 | ) 33 | } 34 | return this.props.children 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /issue-tracker/src/HomeRoot.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { usePreloadedQuery } from 'react-relay/hooks' 3 | import graphql from 'babel-plugin-relay/macro' 4 | 5 | import { HomeRootIssuesQuery } from './__generated__/HomeRootIssuesQuery.graphql' 6 | import { PreloadedQuery } from 'react-relay/lib/relay-experimental/EntryPointTypes' 7 | import Issues from './Issues' 8 | 9 | interface Props { 10 | prepared: { 11 | issuesQuery: PreloadedQuery 12 | } 13 | } 14 | 15 | const HomeRoot = (props: Props) => { 16 | // Defines *what* data the component needs via a query. The responsibility of 17 | // actually fetching this data belongs to the route definition: it calls 18 | // preloadQuery() with the query and variables, and the result is passed 19 | // on props.prepared.issuesQuery - see src/routes.js 20 | const data = usePreloadedQuery( 21 | graphql` 22 | query HomeRootIssuesQuery($owner: String!, $name: String!) { 23 | repository(owner: $owner, name: $name) { 24 | # Compose the data dependencies of child components 25 | # by spreading their fragments: 26 | ...Issues_repository 27 | } 28 | } 29 | `, 30 | props.prepared.issuesQuery, 31 | ) 32 | const { repository } = data 33 | 34 | return 35 | } 36 | 37 | export default HomeRoot 38 | -------------------------------------------------------------------------------- /issue-tracker/src/IssueActions.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback } from 'react' 2 | import { ConnectionHandler } from 'relay-runtime' 3 | import { useFragment } from 'react-relay/hooks' 4 | import graphql from 'babel-plugin-relay/macro' 5 | 6 | import useMutation from './useMutation' 7 | 8 | import { IssueActions_issue$key } from './__generated__/IssueActions_issue.graphql' 9 | 10 | import { IssueActionsAddCommentMutation as AddComment } from './__generated__/IssueActionsAddCommentMutation.graphql' 11 | import { IssueActionsCloseIssueMutation as CloseIssue } from './__generated__/IssueActionsCloseIssueMutation.graphql' 12 | import { IssueActionsReopenIssueMutation as ReopenIssue } from './__generated__/IssueActionsReopenIssueMutation.graphql' 13 | 14 | const AddCommentMutation = graphql` 15 | mutation IssueActionsAddCommentMutation($input: AddCommentInput!) { 16 | addComment(input: $input) { 17 | subject { 18 | id 19 | } 20 | commentEdge { 21 | __id 22 | node { 23 | id 24 | author { 25 | login 26 | avatarUrl 27 | } 28 | body 29 | } 30 | } 31 | } 32 | } 33 | ` 34 | 35 | const CloseIssueMutation = graphql` 36 | mutation IssueActionsCloseIssueMutation($input: CloseIssueInput!) { 37 | closeIssue(input: $input) { 38 | issue { 39 | closed 40 | } 41 | } 42 | } 43 | ` 44 | 45 | const ReopenIssueMutation = graphql` 46 | mutation IssueActionsReopenIssueMutation($input: ReopenIssueInput!) { 47 | reopenIssue(input: $input) { 48 | issue { 49 | closed 50 | } 51 | } 52 | } 53 | ` 54 | 55 | interface Props { 56 | issue: IssueActions_issue$key 57 | } 58 | 59 | export default function IssueActions(props: Props) { 60 | // Track the current comment text - this is used as the value of the comment textarea 61 | const [commentText, setCommentText] = useState('') 62 | 63 | const [isCommentPending, addComment] = useMutation( 64 | AddCommentMutation, 65 | ) 66 | const [isClosePending, closeIssue] = useMutation( 67 | CloseIssueMutation, 68 | ) 69 | const [isReopenPending, reopenIssue] = useMutation( 70 | ReopenIssueMutation, 71 | ) 72 | const isPending = isCommentPending || isClosePending || isReopenPending 73 | 74 | // Get the data we need about the issue in order to execute the mutation. Right now that's just 75 | // the id, but in the future this component might neeed more information. 76 | const data = useFragment( 77 | graphql` 78 | fragment IssueActions_issue on Issue { 79 | id 80 | closed 81 | } 82 | `, 83 | props.issue, 84 | ) 85 | const issueId = data.id 86 | 87 | const onChange = useCallback((e: React.ChangeEvent) => { 88 | setCommentText(e.target.value) 89 | }, []) 90 | 91 | const onSubmit = useCallback( 92 | (e: React.FormEvent) => { 93 | e.preventDefault() 94 | addComment({ 95 | variables: { 96 | input: { 97 | body: commentText, 98 | subjectId: issueId, 99 | }, 100 | }, 101 | /** 102 | * Relay merges data from the mutation result based on each response object's `id` value. 103 | * In this case, however, we also want to add the new comment to the list of issues: Relay 104 | * doesn't magically know where addComment.commentEdge should be added into the data graph. 105 | * So we define an `updater` function to imperatively update thee store. 106 | */ 107 | updater: store => { 108 | // Get a reference to the issue 109 | const issue = store.get(issueId) 110 | if (issue == null) return 111 | // Get the list of comments using the same 'key' value as defined in 112 | // IssueDetailComments 113 | const comments = ConnectionHandler.getConnection( 114 | issue, 115 | 'IssueDetailComments_comments', // See IssueDetailsComments @connection 116 | ) 117 | if (comments == null) return 118 | // Insert the edge at the end of the list 119 | ConnectionHandler.insertEdgeAfter( 120 | comments, 121 | store.getRootField('addComment').getLinkedRecord('commentEdge'), 122 | null, // we can specify a cursor value here to insert the new edge after that cursor 123 | ) 124 | }, 125 | }) 126 | // Reset the comment text 127 | setCommentText('') 128 | }, 129 | [addComment, commentText, issueId], 130 | ) 131 | 132 | const onToggleOpen = useCallback( 133 | (e: React.MouseEvent) => { 134 | e.preventDefault() 135 | 136 | // Switch mutation based on the current open/close status 137 | const config = { 138 | variables: { 139 | input: { 140 | issueId, 141 | }, 142 | }, 143 | } 144 | if (data.closed) { 145 | reopenIssue(config) 146 | } else { 147 | closeIssue(config) 148 | } 149 | }, 150 | [closeIssue, data.closed, issueId, reopenIssue], 151 | ) 152 | 153 | return ( 154 |
155 |