├── .DS_Store
├── .gitignore
├── README.md
├── apollo-local-state
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src
│ ├── cache.tsx
│ ├── components
│ │ ├── App.tsx
│ │ ├── Footer.tsx
│ │ ├── Header.tsx
│ │ ├── Link.tsx
│ │ ├── MainSection.tsx
│ │ ├── TodoItem.tsx
│ │ ├── TodoList.tsx
│ │ └── TodoTextInput.tsx
│ ├── containers
│ │ ├── Header.tsx
│ │ ├── MainSection.tsx
│ │ └── VisibleTodoList.tsx
│ ├── index.tsx
│ ├── models
│ │ ├── Todos.tsx
│ │ └── VisibilityFilter.tsx
│ ├── operations
│ │ ├── mutations
│ │ │ ├── addTodo
│ │ │ │ ├── addTodo.spec.tsx
│ │ │ │ └── addTodo.tsx
│ │ │ ├── clearCompletedTodos
│ │ │ │ ├── clearCompletedTodos.spec.tsx
│ │ │ │ └── clearCompletedTodos.tsx
│ │ │ ├── completeAllTodos
│ │ │ │ └── completeAllTodos.tsx
│ │ │ ├── completeTodo
│ │ │ │ ├── completeTodo.spec.tsx
│ │ │ │ └── completeTodo.tsx
│ │ │ ├── deleteTodo
│ │ │ │ ├── deleteTodo.spec.tsx
│ │ │ │ └── deleteTodo.tsx
│ │ │ ├── editTodo
│ │ │ │ ├── editTodo.spec.tsx
│ │ │ │ └── editTodo.tsx
│ │ │ ├── index.tsx
│ │ │ └── setVisibilityFilter
│ │ │ │ ├── setVisibilityFilter.spec.tsx
│ │ │ │ └── setVisibilityFilter.tsx
│ │ └── queries
│ │ │ ├── getAllTodos.tsx
│ │ │ └── getVisibilityFilter.tsx
│ ├── react-app-env.d.ts
│ └── tests
│ │ ├── createMockReactiveVar.spec.ts
│ │ ├── createMockReactiveVar.ts
│ │ └── mocks
│ │ ├── mockTodosVar.ts
│ │ └── mockVisibilityFilter.ts
└── tsconfig.json
├── apollo-remote-state-advanced-cache-apis
├── README.md
├── client
│ ├── .gitignore
│ ├── __generated__
│ │ └── globalTypes.ts
│ ├── apollo.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── cache.tsx
│ │ ├── components
│ │ │ ├── App.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── Link.tsx
│ │ │ ├── MainSection.tsx
│ │ │ ├── TodoItem.tsx
│ │ │ ├── TodoList.tsx
│ │ │ └── TodoTextInput.tsx
│ │ ├── containers
│ │ │ ├── Header.tsx
│ │ │ ├── MainSection.tsx
│ │ │ └── VisibleTodoList.tsx
│ │ ├── index.tsx
│ │ ├── models
│ │ │ ├── Todos.tsx
│ │ │ └── VisibilityFilter.tsx
│ │ ├── operations
│ │ │ ├── __generated__
│ │ │ │ └── GetAllTodos.ts
│ │ │ ├── mutations
│ │ │ │ ├── __generated__
│ │ │ │ │ ├── AddTodo.ts
│ │ │ │ │ ├── ClearCompletedTodos.ts
│ │ │ │ │ ├── CompleteAllTodos.ts
│ │ │ │ │ ├── CompleteTodo.ts
│ │ │ │ │ ├── DeleteTodo.ts
│ │ │ │ │ └── EditTodo.ts
│ │ │ │ ├── addTodo.tsx
│ │ │ │ ├── clearCompletedTodos.tsx
│ │ │ │ ├── completeAllTodos.tsx
│ │ │ │ ├── completeTodo.tsx
│ │ │ │ ├── deleteTodo.tsx
│ │ │ │ ├── editTodo.tsx
│ │ │ │ └── setVisibilityFilter
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── setVisibilityFilter.spec.tsx
│ │ │ │ │ └── setVisibilityFilter.tsx
│ │ │ └── queries
│ │ │ │ ├── __generated__
│ │ │ │ └── GetAllTodos.ts
│ │ │ │ ├── getAllTodos.tsx
│ │ │ │ └── getVisibilityFilter.tsx
│ │ ├── react-app-env.d.ts
│ │ └── tests
│ │ │ ├── createMockReactiveVar.spec.ts
│ │ │ ├── createMockReactiveVar.ts
│ │ │ └── mocks
│ │ │ ├── mockTodosVar.ts
│ │ │ └── mockVisibilityFilter.ts
│ └── tsconfig.json
└── server
│ ├── .gitignore
│ ├── README.md
│ ├── apollo.config.js
│ ├── codegen.yml
│ ├── graphql.schema.json
│ ├── nodemon.json
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── generated
│ │ └── graphql.ts
│ ├── index.ts
│ ├── modules
│ │ └── todos
│ │ │ └── repos
│ │ │ ├── implementations
│ │ │ └── InMemoryTodoRepo.ts
│ │ │ ├── index.ts
│ │ │ └── todoRepo.ts
│ ├── resolvers.ts
│ ├── schema.ts
│ └── shared
│ │ ├── core
│ │ └── result.ts
│ │ ├── mappers
│ │ └── todoMapper.ts
│ │ └── utils
│ │ ├── numberUtils.ts
│ │ └── paginationUtils.ts
│ └── tsconfig.json
├── apollo-remote-state-no-relay
├── .DS_Store
├── README.md
├── client
│ ├── .gitignore
│ ├── NOTES.md
│ ├── __generated__
│ │ └── globalTypes.ts
│ ├── apollo.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── cache.spec.ts
│ │ ├── cache.tsx
│ │ ├── components
│ │ │ ├── App.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── Link.tsx
│ │ │ ├── MainSection.tsx
│ │ │ ├── TodoItem.tsx
│ │ │ ├── TodoList.tsx
│ │ │ └── TodoTextInput.tsx
│ │ ├── containers
│ │ │ ├── Header.tsx
│ │ │ ├── MainSection.tsx
│ │ │ └── VisibleTodoList.tsx
│ │ ├── index.tsx
│ │ ├── models
│ │ │ ├── Todos.tsx
│ │ │ └── VisibilityFilter.tsx
│ │ ├── operations
│ │ │ ├── mutations
│ │ │ │ ├── __generated__
│ │ │ │ │ ├── AddTodo.ts
│ │ │ │ │ ├── ClearCompletedTodos.ts
│ │ │ │ │ ├── CompleteAllTodos.ts
│ │ │ │ │ ├── CompleteTodo.ts
│ │ │ │ │ ├── DeleteTodo.ts
│ │ │ │ │ └── EditTodo.ts
│ │ │ │ ├── addTodo.tsx
│ │ │ │ ├── clearCompletedTodos.tsx
│ │ │ │ ├── completeAllTodos.tsx
│ │ │ │ ├── completeTodo.tsx
│ │ │ │ ├── deleteTodo.tsx
│ │ │ │ ├── editTodo.tsx
│ │ │ │ └── setVisibilityFilter
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── setVisibilityFilter.spec.tsx
│ │ │ │ │ └── setVisibilityFilter.tsx
│ │ │ └── queries
│ │ │ │ ├── __generated__
│ │ │ │ └── GetAllTodos.ts
│ │ │ │ ├── getAllTodos.tsx
│ │ │ │ └── getVisibilityFilter.tsx
│ │ └── react-app-env.d.ts
│ └── tsconfig.json
├── package-lock.json
└── server
│ ├── .gitignore
│ ├── apollo.config.js
│ ├── codegen.yml
│ ├── graphql.schema.json
│ ├── nodemon.json
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── generated
│ │ └── graphql.ts
│ ├── index.ts
│ ├── modules
│ │ └── todos
│ │ │ └── repos
│ │ │ ├── implementations
│ │ │ └── InMemoryTodoRepo.ts
│ │ │ ├── index.ts
│ │ │ └── todoRepo.ts
│ ├── resolvers.ts
│ ├── schema.ts
│ └── shared
│ │ ├── core
│ │ └── result.ts
│ │ ├── mappers
│ │ └── todoMapper.ts
│ │ └── utils
│ │ ├── numberUtils.ts
│ │ └── paginationUtils.ts
│ └── tsconfig.json
├── apollo-remote-state
├── .DS_Store
├── README.md
├── client
│ ├── .gitignore
│ ├── __generated__
│ │ └── globalTypes.ts
│ ├── apollo.config.js
│ ├── graphql-schema.json
│ ├── local-schema.graphql
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── cache.tsx
│ │ ├── components
│ │ │ ├── App.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── Link.tsx
│ │ │ ├── MainSection.tsx
│ │ │ ├── TodoItem.tsx
│ │ │ ├── TodoList.tsx
│ │ │ └── TodoTextInput.tsx
│ │ ├── containers
│ │ │ ├── Header.tsx
│ │ │ ├── MainSection.tsx
│ │ │ ├── VisibleTodoList.tsx
│ │ │ └── __generated__
│ │ │ │ └── GetLastTodos.ts
│ │ ├── index.tsx
│ │ ├── models
│ │ │ ├── Todos.tsx
│ │ │ └── VisibilityFilter.tsx
│ │ ├── operations
│ │ │ ├── __generated__
│ │ │ │ └── GetAllTodos.ts
│ │ │ ├── mutations
│ │ │ │ ├── __generated__
│ │ │ │ │ ├── AddTodo.ts
│ │ │ │ │ ├── ClearCompletedTodos.ts
│ │ │ │ │ ├── CompleteAllTodos.ts
│ │ │ │ │ ├── CompleteTodo.ts
│ │ │ │ │ ├── DeleteTodo.ts
│ │ │ │ │ └── EditTodo.ts
│ │ │ │ ├── addTodo.tsx
│ │ │ │ ├── clearCompletedTodos.tsx
│ │ │ │ ├── completeAllTodos.tsx
│ │ │ │ ├── completeTodo.tsx
│ │ │ │ ├── deleteTodo.tsx
│ │ │ │ ├── editTodo.tsx
│ │ │ │ └── setVisibilityFilter
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ ├── setVisibilityFilter.spec.tsx
│ │ │ │ │ └── setVisibilityFilter.tsx
│ │ │ └── queries
│ │ │ │ ├── __generated__
│ │ │ │ ├── GetAllTodos.ts
│ │ │ │ └── GetVisibilityFilter.ts
│ │ │ │ ├── getAllTodos.tsx
│ │ │ │ └── getVisibilityFilter.tsx
│ │ ├── react-app-env.d.ts
│ │ └── tests
│ │ │ ├── createMockReactiveVar.spec.ts
│ │ │ ├── createMockReactiveVar.ts
│ │ │ └── mocks
│ │ │ ├── mockTodosVar.ts
│ │ │ └── mockVisibilityFilter.ts
│ └── tsconfig.json
├── package-lock.json
└── server
│ ├── .gitignore
│ ├── apollo.config.js
│ ├── codegen.yml
│ ├── graphql.schema.json
│ ├── nodemon.json
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ ├── generated
│ │ └── graphql.ts
│ ├── index.ts
│ ├── modules
│ │ └── todos
│ │ │ └── repos
│ │ │ ├── implementations
│ │ │ └── InMemoryTodoRepo.ts
│ │ │ ├── index.ts
│ │ │ └── todoRepo.ts
│ ├── resolvers.ts
│ ├── schema.ts
│ └── shared
│ │ ├── core
│ │ └── result.ts
│ │ ├── mappers
│ │ └── todoMapper.ts
│ │ └── utils
│ │ ├── numberUtils.ts
│ │ └── paginationUtils.ts
│ └── tsconfig.json
├── package-lock.json
└── redux-local-state
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
└── index.html
└── src
├── actions
├── index.js
└── index.spec.js
├── components
├── App.js
├── App.spec.js
├── Footer.js
├── Footer.spec.js
├── Header.js
├── Header.spec.js
├── Link.js
├── Link.spec.js
├── MainSection.js
├── MainSection.spec.js
├── TodoItem.js
├── TodoItem.spec.js
├── TodoList.js
├── TodoList.spec.js
├── TodoTextInput.js
└── TodoTextInput.spec.js
├── constants
├── ActionTypes.js
└── TodoFilters.js
├── containers
├── FilterLink.js
├── Header.js
├── MainSection.js
└── VisibleTodoList.js
├── index.js
├── reducers
├── index.js
├── todos.js
├── todos.spec.js
└── visibilityFilter.js
└── selectors
└── index.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apollographql/ac3-state-management-examples/57ddd7aff0b86e12ea7f63db625c004add712ba4/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apollographql/ac3-state-management-examples/57ddd7aff0b86e12ea7f63db625c004add712ba4/.gitignore
--------------------------------------------------------------------------------
/apollo-local-state/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # production
7 | build
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log
12 |
--------------------------------------------------------------------------------
/apollo-local-state/README.md:
--------------------------------------------------------------------------------
1 | # apollo-local-state
2 |
3 | > Summary: Using Apollo Client 3's **Reactive Variables API**, we can store the entire application state locally (and optionally persist it using local storage).
4 |
5 | The key components that make this work are:
6 |
7 | - Reactive Variables: Holds onto local state. Perform _local mutations_ against them. Used in conjunction with cache policies, can act as a replacement for client-side resolvers from AC2.x.
8 | - Cache Policies: Can perform pre-processing on any `query` or `mutation` before the field is returned. Combining this with Reactive Variables broadcasts changes to all client-side queries.
9 |
10 | ## Getting started
11 |
12 | To start the project, run the following commands:
13 |
14 | ```
15 | npm install && npm run start
16 | ```
17 |
18 | The app should start at `http://localhost:3000/`.
19 |
--------------------------------------------------------------------------------
/apollo-local-state/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todomvc-apollo",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "@types/classnames": "^2.2.9",
7 | "@types/react-test-renderer": "^16.9.2",
8 | "react-scripts": "^3.0.0",
9 | "react-test-renderer": "^16.8.6"
10 | },
11 | "dependencies": {
12 | "@apollo/client": "^3.2.7",
13 | "@types/jest": "^25.1.3",
14 | "@types/node": "^13.7.6",
15 | "@types/react": "^16.9.23",
16 | "@types/react-dom": "^16.9.5",
17 | "classnames": "^2.2.6",
18 | "graphql": "^14.6.0",
19 | "pkg.json": "^2.0.8",
20 | "prop-types": "^15.7.2",
21 | "react": "^16.8.6",
22 | "react-dom": "^16.8.6",
23 | "reselect": "^4.0.0",
24 | "todomvc-app-css": "^2.2.0",
25 | "typescript": "^3.8.2"
26 | },
27 | "scripts": {
28 | "start": "react-scripts start",
29 | "build": "react-scripts build",
30 | "eject": "react-scripts eject",
31 | "test": "react-scripts test --env=node"
32 | },
33 | "browserslist": [
34 | ">0.2%",
35 | "not dead",
36 | "not ie <= 11",
37 | "not op_mini all"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/apollo-local-state/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux TodoMVC Example
7 |
8 |
9 |
10 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/apollo-local-state/src/cache.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { InMemoryCache, ReactiveVar, makeVar } from "@apollo/client";
3 | import { Todos } from "./models/Todos";
4 | import { VisibilityFilter, VisibilityFilters } from "./models/VisibilityFilter";
5 |
6 | export const cache: InMemoryCache = new InMemoryCache({
7 | typePolicies: {
8 | Query: {
9 | fields: {
10 | todos: {
11 | read () {
12 | return todosVar();
13 | }
14 | },
15 | visibilityFilter: {
16 | read () {
17 | return visibilityFilterVar();
18 | },
19 | }
20 | }
21 | }
22 | }
23 | });
24 |
25 | /**
26 | * Set initial values when we create cache variables.
27 | */
28 |
29 | const todosInitialValue: Todos = [
30 | {
31 | id: 0,
32 | completed: false,
33 | text: "Use Apollo Client 3"
34 | }
35 | ]
36 |
37 | export const todosVar: ReactiveVar = makeVar(
38 | todosInitialValue
39 | );
40 |
41 | export const visibilityFilterVar = makeVar(
42 | VisibilityFilters.SHOW_ALL
43 | )
44 |
--------------------------------------------------------------------------------
/apollo-local-state/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from '../containers/Header'
3 | import MainSection from '../containers/MainSection'
4 |
5 | const App = () => (
6 |
7 |
8 |
9 |
10 | )
11 |
12 | export default App
13 |
--------------------------------------------------------------------------------
/apollo-local-state/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Link from './Link'
4 | import { VisibilityFilters, VisibilityFilter } from '../models/VisibilityFilter';
5 |
6 | interface FooterProps {
7 | activeVisibilityFilter: VisibilityFilter;
8 | activeCount: number;
9 | completedCount: number;
10 | onClearCompleted: () => void;
11 | setVisibilityFilter: (filter: VisibilityFilter) => void;
12 | }
13 |
14 | const Footer = (props: FooterProps) => {
15 | const { activeCount, completedCount, onClearCompleted, activeVisibilityFilter, setVisibilityFilter } = props;
16 | const itemWord = activeCount === 1 ? "item" : "items";
17 | return (
18 |
38 | );
39 | };
40 |
41 | Footer.propTypes = {
42 | completedCount: PropTypes.number.isRequired,
43 | activeCount: PropTypes.number.isRequired,
44 | onClearCompleted: PropTypes.func.isRequired,
45 | setVisibilityFilter: PropTypes.func.isRequired
46 | }
47 |
48 | export default Footer
49 |
--------------------------------------------------------------------------------
/apollo-local-state/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoTextInput from './TodoTextInput'
4 |
5 | interface HeaderProps {
6 | addTodo: (text: string) => void;
7 | }
8 |
9 | const Header = ({ addTodo }: HeaderProps) => (
10 |
11 | todos
12 | {
15 | if (text.length !== 0) {
16 | addTodo(text);
17 | }
18 | }}
19 | placeholder="What needs to be done?"
20 | />
21 |
22 | );
23 |
24 | Header.propTypes = {
25 | addTodo: PropTypes.func.isRequired
26 | }
27 |
28 | export default Header
29 |
--------------------------------------------------------------------------------
/apollo-local-state/src/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | interface LinkProps {
6 | setFilter: () => any;
7 | active: boolean;
8 | children: any;
9 | }
10 |
11 | const Link = ({ active, children, setFilter }: LinkProps) =>
12 | (
13 | // eslint-disable-next-line jsx-a11y/anchor-is-valid
14 | setFilter()}
18 | >
19 | {children}
20 |
21 | )
22 |
23 |
24 | Link.propTypes = {
25 | active: PropTypes.bool.isRequired,
26 | children: PropTypes.node.isRequired,
27 | setFilter: PropTypes.func.isRequired
28 | }
29 |
30 | export default Link
31 |
--------------------------------------------------------------------------------
/apollo-local-state/src/components/MainSection.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Footer from './Footer'
4 | import VisibleTodoList from '../containers/VisibleTodoList'
5 | import { VisibilityFilter } from '../models/VisibilityFilter';
6 |
7 | interface MainSectionProps {
8 | activeVisibilityFilter: VisibilityFilter;
9 | todosCount: number;
10 | completedCount: number;
11 | actions: any;
12 | }
13 |
14 | /**
15 | * This is a view component. It doesn't define anything that
16 | * is responsible for querying or mutating, it just relies
17 | * on it from the upper layer component (namely, actions)
18 | */
19 |
20 | const MainSection = ({
21 | activeVisibilityFilter,
22 | todosCount,
23 | completedCount,
24 | actions
25 | }: MainSectionProps) => (
26 |
49 | );
50 |
51 | MainSection.propTypes = {
52 | todosCount: PropTypes.number.isRequired,
53 | completedCount: PropTypes.number.isRequired,
54 | actions: PropTypes.object.isRequired
55 | }
56 |
57 | export default MainSection;
--------------------------------------------------------------------------------
/apollo-local-state/src/components/TodoItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 | import TodoTextInput from './TodoTextInput'
5 |
6 | type Props = any;
7 |
8 | export default class TodoItem extends Component {
9 | static propTypes = {
10 | todo: PropTypes.object.isRequired,
11 | editTodo: PropTypes.func.isRequired,
12 | deleteTodo: PropTypes.func.isRequired,
13 | completeTodo: PropTypes.func.isRequired
14 | }
15 |
16 | state = {
17 | editing: false
18 | }
19 |
20 | handleDoubleClick = () => {
21 | this.setState({ editing: true })
22 | }
23 |
24 | handleSave = (id: number, text: string) => {
25 | if (text.length === 0) {
26 | this.props.deleteTodo(id)
27 | } else {
28 | this.props.editTodo(id, text)
29 | }
30 | this.setState({ editing: false })
31 | }
32 |
33 | render() {
34 | const { todo, completeTodo, deleteTodo } = this.props
35 |
36 | let element
37 | if (this.state.editing) {
38 | element = (
39 | this.handleSave(todo.id, text)} />
42 | )
43 | } else {
44 | element = (
45 |
46 | {
50 | completeTodo(todo.id)
51 | }} />
52 |
55 |
58 | )
59 | }
60 |
61 | return (
62 |
66 | {element}
67 |
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/apollo-local-state/src/components/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoItem from './TodoItem'
4 | import { Todo } from '../models/Todos'
5 |
6 | const TodoList = ({ filteredTodos, actions }: any) => (
7 |
8 | {filteredTodos.map((todo: Todo) =>
9 |
10 | )}
11 |
12 | )
13 |
14 | TodoList.propTypes = {
15 | filteredTodos: PropTypes.arrayOf(PropTypes.shape({
16 | id: PropTypes.number.isRequired,
17 | completed: PropTypes.bool.isRequired,
18 | text: PropTypes.string.isRequired
19 | }).isRequired).isRequired,
20 | actions: PropTypes.object.isRequired
21 | }
22 |
23 | export default TodoList
24 |
--------------------------------------------------------------------------------
/apollo-local-state/src/components/TodoTextInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | export default class TodoTextInput extends Component {
6 | static propTypes = {
7 | onSave: PropTypes.func.isRequired,
8 | text: PropTypes.string,
9 | placeholder: PropTypes.string,
10 | editing: PropTypes.bool,
11 | newTodo: PropTypes.bool
12 | }
13 |
14 | state = {
15 | text: this.props.text || ''
16 | }
17 |
18 | handleSubmit = (e: any) => {
19 | const text = e.target.value.trim()
20 | if (e.which === 13) {
21 | this.props.onSave(text)
22 | if (this.props.newTodo) {
23 | this.setState({ text: '' })
24 | }
25 | }
26 | }
27 |
28 | handleChange = (e: any) => {
29 | this.setState({ text: e.target.value })
30 | }
31 |
32 | handleBlur = (e: any) => {
33 | if (!this.props.newTodo) {
34 | this.props.onSave(e.target.value)
35 | }
36 | }
37 |
38 | render() {
39 | return (
40 |
52 | )
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/apollo-local-state/src/containers/Header.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import Header from '../components/Header'
4 | import { todoMutations } from '../operations/mutations';
5 |
6 | export default function () {
7 | return ;
8 | }
--------------------------------------------------------------------------------
/apollo-local-state/src/containers/MainSection.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import MainSection from '../components/MainSection'
4 | import { useQuery } from '@apollo/client'
5 | import { VisibilityFilter } from '../models/VisibilityFilter'
6 | import { Todos } from '../models/Todos'
7 | import { GET_ALL_TODOS } from '../operations/queries/getAllTodos'
8 | import { GET_VISIBILITY_FILTER } from '../operations/queries/getVisibilityFilter'
9 | import { todoMutations } from '../operations/mutations'
10 |
11 | export default function Main () {
12 | const todosQueryResult = useQuery(GET_ALL_TODOS);
13 | const visibilityFilterQueryResult = useQuery(GET_VISIBILITY_FILTER);
14 | const todos: Todos = todosQueryResult.data.todos;
15 | const visibilityFilter: VisibilityFilter = visibilityFilterQueryResult.data.visibilityFilter;
16 | const { completeAllTodos, setVisibilityFilter, clearCompletedTodos } = todoMutations;
17 |
18 | return (
19 | t.completed).length}
23 | actions={{
24 | completeAllTodos,
25 | setVisibilityFilter,
26 | clearCompletedTodos
27 | }}
28 | />
29 | );
30 | };
31 |
32 |
--------------------------------------------------------------------------------
/apollo-local-state/src/containers/VisibleTodoList.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { visibilityFilterVar, todosVar } from '../cache'
4 | import TodoList from '../components/TodoList';
5 | import { VisibilityFilter, VisibilityFilters } from '../models/VisibilityFilter';
6 | import { Todos } from '../models/Todos';
7 | import { todoMutations } from '../operations/mutations';
8 |
9 | function filterTodosByVisibility(visibilityFilter: VisibilityFilter, todos: Todos) {
10 | switch (visibilityFilter.id) {
11 | case VisibilityFilters.SHOW_ALL.id:
12 | return todos;
13 | case VisibilityFilters.SHOW_COMPLETED.id:
14 | return todos.filter(t => t.completed);
15 | case VisibilityFilters.SHOW_ACTIVE.id:
16 | return todos.filter(t => !t.completed);
17 | default:
18 | throw new Error("Unknown filter: " + visibilityFilter);
19 | }
20 | }
21 |
22 | export default function VisibleTodoList () {
23 | const { completeTodo, deleteTodo, editTodo } = todoMutations;
24 | const todos = todosVar();
25 | const filteredTodos = filterTodosByVisibility(visibilityFilterVar(), todos);
26 |
27 | return ;
34 | }
35 |
--------------------------------------------------------------------------------
/apollo-local-state/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import App from './components/App'
4 | import { ApolloClient, ApolloProvider } from '@apollo/client';
5 | import 'todomvc-app-css/index.css'
6 | import { cache } from './cache';
7 |
8 | export const client = new ApolloClient({
9 | cache,
10 | connectToDevTools: true,
11 | });
12 |
13 | render(
14 |
15 |
16 | ,
17 | document.getElementById('root')
18 | )
19 |
--------------------------------------------------------------------------------
/apollo-local-state/src/models/Todos.tsx:
--------------------------------------------------------------------------------
1 |
2 | export interface Todo {
3 | text: string;
4 | completed: boolean;
5 | id: number
6 | }
7 |
8 | export type Todos = Todo[];
--------------------------------------------------------------------------------
/apollo-local-state/src/models/VisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | export type VisibilityFilter = {
3 | id: string;
4 | displayName: string;
5 | }
6 |
7 | export const VisibilityFilters: { [filter: string]: VisibilityFilter } = {
8 | SHOW_ALL: {
9 | id: "show_all",
10 | displayName: "All"
11 | },
12 | SHOW_COMPLETED: {
13 | id: "show_completed",
14 | displayName: "Completed"
15 | },
16 | SHOW_ACTIVE: {
17 | id: "show_active",
18 | displayName: "Active"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/addTodo/addTodo.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createAddTodo from './addTodo'
3 | import { mockTodosVar } from '../../../tests/mocks/mockTodosVar';
4 |
5 | const addTodo = createAddTodo(mockTodosVar);
6 |
7 | describe('addTodos hook', () => {
8 | beforeEach(() => mockTodosVar([]));
9 |
10 | it('should add a todo', () => {
11 | addTodo('First todo')
12 | expect(
13 | mockTodosVar()
14 | ).toHaveLength(1)
15 |
16 | expect(
17 | mockTodosVar()[0].id
18 | ).toEqual(0)
19 |
20 | expect(
21 | mockTodosVar()[0].completed
22 | ).toEqual(false)
23 |
24 | expect(
25 | mockTodosVar()[0].text
26 | ).toEqual('First todo')
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/addTodo/addTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Todos, Todo } from "../../../models/Todos";
3 | import { ReactiveVar } from "@apollo/client";
4 |
5 | export default function createAddTodo (todosVar: ReactiveVar) {
6 | const createNewTodoId = (allTodos: Todos) => {
7 | return allTodos.reduce(
8 | (maxId: number, todo: Todo) => Math.max(todo.id, maxId),
9 | -1
10 | ) + 1;
11 | }
12 |
13 | const createNewTodo = (text: string, allTodos: Todos) => {
14 | return { text, completed: false, id: createNewTodoId(allTodos) }
15 | }
16 |
17 | return (text: string) => {
18 | const allTodos = todosVar();
19 | todosVar(allTodos.concat([createNewTodo(text, allTodos)]));
20 | };
21 | }
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/clearCompletedTodos/clearCompletedTodos.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createClearCompletedTodos from './clearCompletedTodos'
3 | import { mockTodosVar } from '../../../tests/mocks/mockTodosVar';
4 |
5 | const clearCompletedTodos = createClearCompletedTodos(mockTodosVar);
6 |
7 | describe('clearCompletedTodos hook', () => {
8 | beforeEach(() => mockTodosVar([]));
9 |
10 | it('should clear a list of completed todos', () => {
11 | mockTodosVar([{ id: 0, text: 'Completed todo', completed: true }])
12 | clearCompletedTodos();
13 |
14 | expect(
15 | mockTodosVar()
16 | ).toHaveLength(0)
17 | })
18 |
19 | it('should not clear todos that are not completed', () => {
20 | mockTodosVar([
21 | { id: 0, text: 'Completed todo', completed: true },
22 | { id: 1, text: 'Not completed todo', completed: false }
23 | ])
24 | clearCompletedTodos();
25 |
26 | expect(
27 | mockTodosVar()
28 | ).toHaveLength(1)
29 |
30 | expect(
31 | mockTodosVar()[0].text
32 | ).toEqual('Not completed todo')
33 |
34 | })
35 | })
36 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/clearCompletedTodos/clearCompletedTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { ReactiveVar } from "@apollo/client";
3 | import { Todos } from "../../../models/Todos";
4 |
5 | export default function createClearCompletedTodos (
6 | todosVar: ReactiveVar
7 | ) {
8 | return () => {
9 | const nonCompletedTodos = todosVar()
10 | .filter((t) => t.completed !== true);
11 |
12 | todosVar(nonCompletedTodos);
13 |
14 | }
15 | }
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/completeAllTodos/completeAllTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Todo, Todos } from "../../../models/Todos";
3 | import { ReactiveVar } from "@apollo/client";
4 |
5 | export default function completeAllTodos (
6 | todosVar: ReactiveVar
7 | ) {
8 | return () => {
9 | const allTodosCompleted = todosVar()
10 | .map((t: Todo) => ({ ...t, completed: true }));
11 |
12 | todosVar(allTodosCompleted);
13 | }
14 | }
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/completeTodo/completeTodo.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createCompleteTodo from './completeTodo'
3 | import { mockTodosVar } from '../../../tests/mocks/mockTodosVar';
4 |
5 | const completeTodo = createCompleteTodo(mockTodosVar);
6 |
7 | describe('completeTodo hook', () => {
8 | beforeEach(() => mockTodosVar([]));
9 |
10 | it('should mark a todo as completed', () => {
11 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }])
12 | completeTodo(0);
13 |
14 | expect(
15 | mockTodosVar()[0].completed
16 | ).toBeTruthy()
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/completeTodo/completeTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { ReactiveVar } from "@apollo/client";
3 | import { Todos } from "../../../models/Todos";
4 |
5 | export default function createCompleteTodo (
6 | todosVar: ReactiveVar
7 | ) {
8 | return (id: number) => {
9 | const allTodos = todosVar();
10 |
11 | todosVar(allTodos.map((t) => t.id === id ? { ...t, completed: true } : t));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/deleteTodo/deleteTodo.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createDeleteTodo from './deleteTodo'
3 | import { mockTodosVar } from '../../../tests/mocks/mockTodosVar';
4 |
5 | const deleteTodo = createDeleteTodo(mockTodosVar);
6 |
7 | describe('deleteTodo hook', () => {
8 | beforeEach(() => mockTodosVar([]));
9 |
10 | it('should delete a todo from the list of todos', () => {
11 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }])
12 | deleteTodo(0);
13 |
14 | expect(
15 | mockTodosVar().length
16 | ).toEqual(0)
17 | })
18 | })
19 |
20 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/deleteTodo/deleteTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Todo, Todos } from "../../../models/Todos";
3 | import { ReactiveVar } from "@apollo/client";
4 |
5 | export default function deleteTodo (todosVar: ReactiveVar) {
6 | return (id: number) => {
7 | const allTodos = todosVar();
8 | const filteredTodos = allTodos.filter((todo: Todo) => todo.id !== id);
9 | todosVar(filteredTodos);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/editTodo/editTodo.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createEditTodo from './editTodo'
3 | import { mockTodosVar } from '../../../tests/mocks/mockTodosVar';
4 |
5 | const editTodo = createEditTodo(mockTodosVar);
6 |
7 | describe('editTodo hook', () => {
8 | beforeEach(() => mockTodosVar([]));
9 |
10 | it('should mark a todo as completed', () => {
11 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }])
12 | editTodo(0, "This was edited");
13 |
14 | expect(
15 | mockTodosVar()[0].text
16 | ).toEqual("This was edited")
17 | });
18 | })
19 |
20 |
21 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/editTodo/editTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { Todo, Todos } from "../../../models/Todos";
3 | import { ReactiveVar } from "@apollo/client";
4 |
5 | export default (todosVar: ReactiveVar) => {
6 | return (id: number, text: string) => {
7 | let todosWithEditedTodo = todosVar()
8 | .map((todo: Todo) => todo.id === id ? { ...todo, text } : todo);
9 |
10 | todosVar(todosWithEditedTodo);
11 | }
12 | }
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createAddTodo from "./addTodo/addTodo";
3 | import createClearCompletedTodos from "./clearCompletedTodos/clearCompletedTodos";
4 | import createCompleteTodo from "./completeTodo/completeTodo";
5 | import createCompleteAllTodos from "./completeAllTodos/completeAllTodos";
6 | import createDeleteTodo from "./deleteTodo/deleteTodo";
7 | import createEditTodo from "./editTodo/editTodo";
8 | import createSetVisibilityFilter from "./setVisibilityFilter/setVisibilityFilter";
9 | import { todosVar, visibilityFilterVar } from "../../cache";
10 |
11 | export const todoMutations = {
12 | addTodo: createAddTodo(todosVar),
13 | clearCompletedTodos: createClearCompletedTodos(todosVar),
14 | completeTodo: createCompleteTodo(todosVar),
15 | completeAllTodos: createCompleteAllTodos(todosVar),
16 | deleteTodo: createDeleteTodo(todosVar),
17 | editTodo: createEditTodo(todosVar),
18 | setVisibilityFilter: createSetVisibilityFilter(visibilityFilterVar)
19 | }
20 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/setVisibilityFilter/setVisibilityFilter.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createSetVisibilityFilter from './setVisibilityFilter'
3 | import { VisibilityFilters } from '../../../models/VisibilityFilter';
4 | import { mockVisibilityFilter } from '../../../tests/mocks/mockVisibilityFilter';
5 |
6 | const setVisibilityFilter = createSetVisibilityFilter(mockVisibilityFilter);
7 |
8 | describe('setVisibilityFilter hook', () => {
9 | beforeEach(() => mockVisibilityFilter(VisibilityFilters.SHOW_ALL));
10 |
11 | it('should change the visibility filter', () => {
12 | setVisibilityFilter(VisibilityFilters.SHOW_ALL);
13 |
14 | expect(
15 | mockVisibilityFilter().displayName
16 | ).toEqual("All")
17 |
18 | setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED);
19 |
20 | expect(
21 | mockVisibilityFilter().displayName
22 | ).toEqual("Completed")
23 | });
24 | })
25 |
26 |
27 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/mutations/setVisibilityFilter/setVisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { VisibilityFilter } from "../../../models/VisibilityFilter";
3 | import { ReactiveVar } from "@apollo/client";
4 |
5 | export default (visibilityFilterVar: ReactiveVar) => {
6 | return (filter: VisibilityFilter) => {
7 | visibilityFilterVar(filter);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/queries/getAllTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql } from "@apollo/client";
3 |
4 | export const GET_ALL_TODOS = gql`
5 | query GetAllTodos {
6 | todos @client {
7 | id
8 | text
9 | completed
10 | }
11 | }
12 | `
--------------------------------------------------------------------------------
/apollo-local-state/src/operations/queries/getVisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql } from "@apollo/client";
3 |
4 | export const GET_VISIBILITY_FILTER = gql`
5 | query GetVisibilityFilter {
6 | visibilityFilter @client {
7 | id
8 | displayName
9 | }
10 | }
11 | `
--------------------------------------------------------------------------------
/apollo-local-state/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apollo-local-state/src/tests/createMockReactiveVar.spec.ts:
--------------------------------------------------------------------------------
1 | import { mockTodosVar } from "./mocks/mockTodosVar";
2 |
3 | describe('Testing mock reactive variables', () => {
4 | beforeEach(() => mockTodosVar([]));
5 |
6 | it('should set the current value if provided', () => {
7 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }]);
8 | expect(mockTodosVar()?.length).toEqual(1);
9 | })
10 |
11 | it('should overwrite the current value', () => {
12 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }]);
13 | expect(mockTodosVar()?.length).toEqual(1);
14 |
15 | mockTodosVar([{ id: 1, text: 'Second todo', completed: false }]);
16 | expect(mockTodosVar()?.length).toEqual(1);
17 | });
18 |
19 | it('should not overwrite the current value if no value provided', () => {
20 | mockTodosVar([
21 | { id: 0, text: 'First todo', completed: false },
22 | { id: 1, text: 'Second todo', completed: false }
23 | ]);
24 | expect(mockTodosVar()?.length).toEqual(2);
25 |
26 | mockTodosVar();
27 | expect(mockTodosVar()?.length).toEqual(2);
28 | })
29 | })
--------------------------------------------------------------------------------
/apollo-local-state/src/tests/createMockReactiveVar.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ReactiveVar } from "@apollo/client";
3 |
4 | // This is how to create a mock reactive variable.
5 | // It's a good idea to do this because then we can test our
6 | // interaction logic.
7 |
8 | export function createMockReactiveVar (defaultValue?: T): any {
9 | let currentValue: T | undefined = defaultValue;
10 |
11 | return function mockReactiveVar (
12 | newValue?: T
13 | ) : T {
14 |
15 | if (newValue) {
16 | currentValue = newValue;
17 | }
18 | return currentValue as T;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apollo-local-state/src/tests/mocks/mockTodosVar.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Todos } from "../../models/Todos";
3 | import { createMockReactiveVar } from "../createMockReactiveVar";
4 |
5 | export const mockTodosVar = createMockReactiveVar([]);
--------------------------------------------------------------------------------
/apollo-local-state/src/tests/mocks/mockVisibilityFilter.ts:
--------------------------------------------------------------------------------
1 |
2 | import { createMockReactiveVar } from "../createMockReactiveVar";
3 | import { VisibilityFilter, VisibilityFilters } from "../../models/VisibilityFilter";
4 |
5 | export const mockVisibilityFilter = createMockReactiveVar(
6 | VisibilityFilters.SHOW_ALL
7 | );
8 |
--------------------------------------------------------------------------------
/apollo-local-state/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/README.md:
--------------------------------------------------------------------------------
1 | # apollo-remote-state-advanced-cache-apis
2 |
3 | Summary: Hooking Apollo Client up to a remote GraphQL API, the client-side cache is smart enough to automatically update the cache after most mutations successfully complete. For mutations that perform interactions against arrays or have additional client-side side-effects, we can help the cache decide what to do next by writing our update logic in the `useMutation`'s `update` function.
4 |
5 | This example uses the new Apollo Client 3 cache manipulation APIs: `cache.modify` and `cache.evict`. For users familiar with how cache normalization works, this approach is generally preferred because it provides more direct access to the cache over the simpler `cache.readQuery`/`cache.writeQuery` approach.
6 |
7 | For those just starting out with Apollo Client, we recommend using the `cache.readQuery` and `cache.writeQuery` API example [found here](https://github.com/apollographql/ac3-state-management-examples/tree/master/apollo-remote-state) instead.
8 |
9 | ## Getting started
10 |
11 | There is a `client/` and `server/` portion to this example.
12 |
13 | ### Running the client
14 |
15 | To start the project, run the following commands:
16 |
17 | ```
18 | cd client
19 | npm install && npm run start
20 | ```
21 |
22 | The app should start at http://localhost:3000/.
23 |
24 | ### Running the server
25 |
26 | To start the project, run the following commands:
27 |
28 | ```
29 | cd server
30 | npm install && npm run start
31 | ```
32 |
33 | The server should start at http://localhost:4000/.
34 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # production
7 | build
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log
12 | .env
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/__generated__/globalTypes.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | //==============================================================
6 | // START Enums and Input Objects
7 | //==============================================================
8 |
9 | //==============================================================
10 | // END Enums and Input Objects
11 | //==============================================================
12 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | client: {
3 | service: {
4 | name: 'ac3-todos-backend',
5 | url: `http://localhost:4000`,
6 | },
7 | },
8 | };
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todomvc-apollo",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "@types/classnames": "^2.2.9",
7 | "@types/react-test-renderer": "^16.9.2",
8 | "react-scripts": "^3.0.0",
9 | "react-test-renderer": "^16.8.6"
10 | },
11 | "dependencies": {
12 | "@apollo/client": "^3.0.1",
13 | "@types/jest": "^25.1.3",
14 | "@types/node": "^13.7.6",
15 | "@types/react": "^16.9.23",
16 | "@types/react-dom": "^16.9.5",
17 | "classnames": "^2.2.6",
18 | "graphql": "^14.6.0",
19 | "prop-types": "^15.7.2",
20 | "react": "^16.8.6",
21 | "react-dom": "^16.8.6",
22 | "reselect": "^4.0.0",
23 | "todomvc-app-css": "^2.2.0",
24 | "typescript": "^3.8.2"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "eject": "react-scripts eject",
30 | "test": "react-scripts test --env=node",
31 | "generate": "apollo client:codegen --target typescript --excludes src/operations/queries/getVisibilityFilter.tsx"
32 | },
33 | "browserslist": [
34 | ">0.2%",
35 | "not dead",
36 | "not ie <= 11",
37 | "not op_mini all"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux TodoMVC Example
7 |
8 |
9 |
10 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/cache.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { InMemoryCache, makeVar } from "@apollo/client";
3 | import { VisibilityFilters, VisibilityFilter } from "./models/VisibilityFilter";
4 |
5 | export const cache: InMemoryCache = new InMemoryCache({
6 | typePolicies: {
7 | Query: {
8 | fields: {
9 | visibilityFilter: {
10 | read () {
11 | return visibilityFilterVar();
12 | }
13 | }
14 | }
15 | }
16 | }
17 | });
18 |
19 | /**
20 | * Set initial values when we create cache variables.
21 | */
22 |
23 | export const visibilityFilterVar = makeVar(
24 | VisibilityFilters.SHOW_ALL
25 | )
26 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from '../containers/Header'
3 | import MainSection from '../containers/MainSection'
4 |
5 | const App = () => (
6 |
7 |
8 |
9 |
10 | )
11 |
12 | export default App
13 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Link from './Link'
4 | import { VisibilityFilters, VisibilityFilter } from '../models/VisibilityFilter';
5 |
6 | interface FooterProps {
7 | activeVisibilityFilter: VisibilityFilter;
8 | activeCount: number;
9 | completedCount: number;
10 | onClearCompleted: () => void;
11 | setVisibilityFilter: (filter: VisibilityFilter) => void;
12 | }
13 |
14 | const Footer = (props: FooterProps) => {
15 | const { activeCount, completedCount, onClearCompleted, activeVisibilityFilter, setVisibilityFilter } = props;
16 | const itemWord = activeCount === 1 ? "item" : "items";
17 | return (
18 |
38 | );
39 | };
40 |
41 | Footer.propTypes = {
42 | completedCount: PropTypes.number.isRequired,
43 | activeCount: PropTypes.number.isRequired,
44 | onClearCompleted: PropTypes.func.isRequired,
45 | setVisibilityFilter: PropTypes.func.isRequired
46 | }
47 |
48 | export default Footer
49 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoTextInput from './TodoTextInput'
4 |
5 | interface HeaderProps {
6 | addTodo: (text: string) => void;
7 | }
8 |
9 | const Header = ({ addTodo }: HeaderProps) => (
10 |
11 | todos
12 | {
15 | if (text.length !== 0) {
16 | addTodo(text);
17 | }
18 | }}
19 | placeholder="What needs to be done?"
20 | />
21 |
22 | );
23 |
24 | Header.propTypes = {
25 | addTodo: PropTypes.func.isRequired
26 | }
27 |
28 | export default Header
29 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | interface LinkProps {
6 | setFilter: () => any;
7 | active: boolean;
8 | children: any;
9 | }
10 |
11 | const Link = ({ active, children, setFilter }: LinkProps) =>
12 | (
13 | // eslint-disable-next-line jsx-a11y/anchor-is-valid
14 | setFilter()}
18 | >
19 | {children}
20 |
21 | )
22 |
23 |
24 | Link.propTypes = {
25 | active: PropTypes.bool.isRequired,
26 | children: PropTypes.node.isRequired,
27 | setFilter: PropTypes.func.isRequired
28 | }
29 |
30 | export default Link
31 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/components/MainSection.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Footer from './Footer'
4 | import VisibleTodoList from '../containers/VisibleTodoList'
5 | import { VisibilityFilter } from '../models/VisibilityFilter';
6 |
7 | interface MainSectionProps {
8 | activeVisibilityFilter: VisibilityFilter;
9 | todosCount: number;
10 | completedCount: number;
11 | actions: any;
12 | }
13 |
14 | /**
15 | * This is a view component. It doesn't define anything that
16 | * is responsible for querying or mutating, it just relies
17 | * on it from the upper layer component (namely, actions)
18 | */
19 |
20 | const MainSection = ({
21 | activeVisibilityFilter,
22 | todosCount,
23 | completedCount,
24 | actions,
25 | }: MainSectionProps) => (
26 |
49 | );
50 |
51 | MainSection.propTypes = {
52 | todosCount: PropTypes.number.isRequired,
53 | completedCount: PropTypes.number.isRequired,
54 | actions: PropTypes.object.isRequired
55 | }
56 |
57 | export default MainSection;
58 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/components/TodoItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 | import TodoTextInput from './TodoTextInput'
5 |
6 | type Props = any;
7 |
8 | export default class TodoItem extends Component {
9 | static propTypes = {
10 | todo: PropTypes.object.isRequired,
11 | editTodo: PropTypes.func.isRequired,
12 | deleteTodo: PropTypes.func.isRequired,
13 | completeTodo: PropTypes.func.isRequired
14 | }
15 |
16 | state = {
17 | editing: false
18 | }
19 |
20 | handleDoubleClick = () => {
21 | this.setState({ editing: true })
22 | }
23 |
24 | handleSave = (id: number, text: string) => {
25 | if (text.length === 0) {
26 | this.props.deleteTodo(id)
27 | } else {
28 | this.props.editTodo(id, text)
29 | }
30 | this.setState({ editing: false })
31 | }
32 |
33 | render() {
34 | const { todo, completeTodo, deleteTodo } = this.props
35 |
36 | let element
37 | if (this.state.editing) {
38 | element = (
39 | this.handleSave(todo.id, text)} />
42 | )
43 | } else {
44 | element = (
45 |
46 | {
50 | completeTodo(todo.id)
51 | }} />
52 |
55 |
58 | )
59 | }
60 |
61 | return (
62 |
66 | {element}
67 |
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/components/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoItem from './TodoItem'
4 | import { Todo } from '../models/Todos'
5 |
6 | const TodoList = ({ filteredTodos, actions }: any) => (
7 |
8 | {filteredTodos.map((todo: Todo) =>
9 |
10 | )}
11 |
12 | )
13 |
14 | TodoList.propTypes = {
15 | filteredTodos: PropTypes.arrayOf(PropTypes.shape({
16 | id: PropTypes.number.isRequired,
17 | completed: PropTypes.bool.isRequired,
18 | text: PropTypes.string.isRequired
19 | }).isRequired).isRequired,
20 | actions: PropTypes.object.isRequired
21 | }
22 |
23 | export default TodoList
24 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/components/TodoTextInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | export default class TodoTextInput extends Component {
6 | static propTypes = {
7 | onSave: PropTypes.func.isRequired,
8 | text: PropTypes.string,
9 | placeholder: PropTypes.string,
10 | editing: PropTypes.bool,
11 | newTodo: PropTypes.bool
12 | }
13 |
14 | state = {
15 | text: this.props.text || ''
16 | }
17 |
18 | handleSubmit = (e: any) => {
19 | const text = e.target.value.trim()
20 | if (e.which === 13) {
21 | this.props.onSave(text)
22 | if (this.props.newTodo) {
23 | this.setState({ text: '' })
24 | }
25 | }
26 | }
27 |
28 | handleChange = (e: any) => {
29 | this.setState({ text: e.target.value })
30 | }
31 |
32 | handleBlur = (e: any) => {
33 | if (!this.props.newTodo) {
34 | this.props.onSave(e.target.value)
35 | }
36 | }
37 |
38 | render() {
39 | return (
40 |
52 | )
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/containers/Header.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import Header from '../components/Header'
4 | import { useAddTodo } from '../operations/mutations/addTodo';
5 |
6 | export default function () {
7 | const { mutate } = useAddTodo();
8 | return mutate({ variables: { text } })} />;
9 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/containers/MainSection.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import MainSection from '../components/MainSection'
4 | import { useQuery, gql } from '@apollo/client'
5 | import { VisibilityFilter } from '../models/VisibilityFilter'
6 | import { GET_ALL_TODOS } from '../operations/queries/getAllTodos'
7 | import { GET_VISIBILITY_FILTER } from '../operations/queries/getVisibilityFilter'
8 | import { GetAllTodos } from '../operations/__generated__/GetAllTodos'
9 | import { useClearCompletedTodos } from '../operations/mutations/clearCompletedTodos'
10 | import { useCompleteAllTodos } from '../operations/mutations/completeAllTodos'
11 | import { setVisibilityFilter } from '../operations/mutations/setVisibilityFilter'
12 |
13 | export default function Main () {
14 | const { loading: isTodosLoading, data: todosConnection, error: todosError } = useQuery(GET_ALL_TODOS);
15 | const { data: visibilityFilter } = useQuery(GET_VISIBILITY_FILTER);
16 |
17 | const { mutate: clearCompletedTodos } = useClearCompletedTodos();
18 | const { mutate: completeAllTodos } = useCompleteAllTodos();
19 |
20 | if (isTodosLoading) return Loading...
21 | if (todosError) return An error occurred {JSON.stringify(todosError)}
22 | if (!todosConnection) return None
;
23 | const todos = todosConnection.todos.edges.map((t) => t?.node)
24 |
25 | return (
26 | t ? t.completed : false).length}
30 | actions={{
31 | completeAllTodos,
32 | setVisibilityFilter,
33 | clearCompletedTodos,
34 | }}
35 | />
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import App from './components/App'
4 | import { ApolloClient, ApolloProvider } from '@apollo/client';
5 | import 'todomvc-app-css/index.css'
6 | import { cache } from './cache';
7 |
8 | export const client = new ApolloClient({
9 | cache,
10 | uri: 'http://localhost:4000/',
11 | headers: {
12 | authorization: localStorage.getItem('token') || '',
13 | 'client-name': 'ac3-todos-backend',
14 | 'client-version': '1.0.0',
15 | },
16 | connectToDevTools: true,
17 | });
18 |
19 | render(
20 |
21 |
22 | ,
23 | document.getElementById('root')
24 | )
25 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/models/Todos.tsx:
--------------------------------------------------------------------------------
1 | import { GetAllTodos_todos_edges_node } from "../operations/__generated__/GetAllTodos";
2 |
3 | export type Todo = GetAllTodos_todos_edges_node;
4 |
5 | export type Todos = Todo[];
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/models/VisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | export type VisibilityFilter = {
3 | id: string;
4 | displayName: string;
5 | }
6 |
7 | export const VisibilityFilters: { [filter: string]: VisibilityFilter } = {
8 | SHOW_ALL: {
9 | id: "show_all",
10 | displayName: "All"
11 | },
12 | SHOW_COMPLETED: {
13 | id: "show_completed",
14 | displayName: "Completed"
15 | },
16 | SHOW_ACTIVE: {
17 | id: "show_active",
18 | displayName: "Active"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/__generated__/GetAllTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL query operation: GetAllTodos
7 | // ====================================================
8 |
9 | export interface GetAllTodos_todos_edges_node {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface GetAllTodos_todos_edges {
17 | __typename: "TodosEdge";
18 | node: GetAllTodos_todos_edges_node;
19 | }
20 |
21 | export interface GetAllTodos_todos {
22 | __typename: "TodosConnection";
23 | edges: (GetAllTodos_todos_edges | null)[];
24 | }
25 |
26 | export interface GetAllTodos {
27 | todos: GetAllTodos_todos;
28 | }
29 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/__generated__/AddTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: AddTodo
7 | // ====================================================
8 |
9 | export interface AddTodo_addTodo_todo {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface AddTodo_addTodo_error {
17 | __typename: "TodoValidationError";
18 | message: string;
19 | }
20 |
21 | export interface AddTodo_addTodo {
22 | __typename: "AddTodoResult";
23 | success: boolean;
24 | todo: AddTodo_addTodo_todo | null;
25 | error: AddTodo_addTodo_error | null;
26 | }
27 |
28 | export interface AddTodo {
29 | addTodo: AddTodo_addTodo;
30 | }
31 |
32 | export interface AddTodoVariables {
33 | text: string;
34 | }
35 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/__generated__/ClearCompletedTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: ClearCompletedTodos
7 | // ====================================================
8 |
9 | export interface ClearCompletedTodos_clearCompletedTodos_todos {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface ClearCompletedTodos_clearCompletedTodos {
17 | __typename: "ClearCompletedTodosResult";
18 | success: boolean;
19 | todos: (ClearCompletedTodos_clearCompletedTodos_todos | null)[];
20 | }
21 |
22 | export interface ClearCompletedTodos {
23 | clearCompletedTodos: ClearCompletedTodos_clearCompletedTodos;
24 | }
25 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/__generated__/CompleteAllTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: CompleteAllTodos
7 | // ====================================================
8 |
9 | export interface CompleteAllTodos_completeAllTodos_todos {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface CompleteAllTodos_completeAllTodos {
17 | __typename: "CompleteAllTodosResult";
18 | success: boolean;
19 | todos: (CompleteAllTodos_completeAllTodos_todos | null)[];
20 | }
21 |
22 | export interface CompleteAllTodos {
23 | completeAllTodos: CompleteAllTodos_completeAllTodos;
24 | }
25 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/__generated__/CompleteTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: CompleteTodo
7 | // ====================================================
8 |
9 | export interface CompleteTodo_completeTodo_todo {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface CompleteTodo_completeTodo_error_TodoNotFoundError {
17 | __typename: "TodoNotFoundError";
18 | message: string;
19 | }
20 |
21 | export interface CompleteTodo_completeTodo_error_TodoAlreadyCompletedError {
22 | __typename: "TodoAlreadyCompletedError";
23 | message: string;
24 | }
25 |
26 | export type CompleteTodo_completeTodo_error = CompleteTodo_completeTodo_error_TodoNotFoundError | CompleteTodo_completeTodo_error_TodoAlreadyCompletedError;
27 |
28 | export interface CompleteTodo_completeTodo {
29 | __typename: "CompleteTodoResult";
30 | success: boolean;
31 | todo: CompleteTodo_completeTodo_todo | null;
32 | error: CompleteTodo_completeTodo_error | null;
33 | }
34 |
35 | export interface CompleteTodo {
36 | completeTodo: CompleteTodo_completeTodo;
37 | }
38 |
39 | export interface CompleteTodoVariables {
40 | id: number;
41 | }
42 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/__generated__/DeleteTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: DeleteTodo
7 | // ====================================================
8 |
9 | export interface DeleteTodo_deleteTodo_todo {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface DeleteTodo_deleteTodo_error {
17 | __typename: "TodoNotFoundError";
18 | message: string;
19 | }
20 |
21 | export interface DeleteTodo_deleteTodo {
22 | __typename: "DeleteTodoResult";
23 | success: boolean;
24 | todo: DeleteTodo_deleteTodo_todo | null;
25 | error: DeleteTodo_deleteTodo_error | null;
26 | }
27 |
28 | export interface DeleteTodo {
29 | deleteTodo: DeleteTodo_deleteTodo;
30 | }
31 |
32 | export interface DeleteTodoVariables {
33 | id: number;
34 | }
35 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/__generated__/EditTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: EditTodo
7 | // ====================================================
8 |
9 | export interface EditTodo_editTodo_todo {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface EditTodo_editTodo_error_TodoNotFoundError {
17 | __typename: "TodoNotFoundError";
18 | message: string;
19 | }
20 |
21 | export interface EditTodo_editTodo_error_TodoValidationError {
22 | __typename: "TodoValidationError";
23 | message: string;
24 | }
25 |
26 | export type EditTodo_editTodo_error = EditTodo_editTodo_error_TodoNotFoundError | EditTodo_editTodo_error_TodoValidationError;
27 |
28 | export interface EditTodo_editTodo {
29 | __typename: "EditTodoResult";
30 | success: boolean;
31 | todo: EditTodo_editTodo_todo | null;
32 | error: EditTodo_editTodo_error | null;
33 | }
34 |
35 | export interface EditTodo {
36 | editTodo: EditTodo_editTodo;
37 | }
38 |
39 | export interface EditTodoVariables {
40 | id: number;
41 | text: string;
42 | }
43 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/addTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation, Reference } from "@apollo/client";
3 | import * as AddTodoTypes from './__generated__/AddTodo';
4 |
5 | export const ADD_TODO = gql`
6 | mutation AddTodo ($text: String!) {
7 | addTodo (text: $text) {
8 | success
9 | todo {
10 | id
11 | text
12 | completed
13 | }
14 | error {
15 | message
16 | }
17 | }
18 | }
19 | `
20 |
21 | export function useAddTodo () {
22 | const [mutate, { data, error }] = useMutation<
23 | AddTodoTypes.AddTodo,
24 | AddTodoTypes.AddTodoVariables
25 | >(
26 | ADD_TODO,
27 | {
28 | update (cache, { data }) {
29 | cache.modify({
30 | fields: {
31 | todos (existingTodos, { toReference }) {
32 | return {
33 | ...existingTodos,
34 | edges: [...existingTodos.edges,
35 | //@ts-ignore
36 | { __typename: 'TodosEdge', node: toReference(data.addTodo.todo) }
37 | ]
38 | }
39 | }
40 | }
41 | })
42 | }
43 | }
44 | )
45 | return { mutate, data, error };
46 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/clearCompletedTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as ClearCompletedTodosTypes from './__generated__/ClearCompletedTodos'
4 |
5 | export const CLEAR_COMPLETED_TODOS = gql`
6 | mutation ClearCompletedTodos {
7 | clearCompletedTodos {
8 | success
9 | todos {
10 | id
11 | text
12 | completed
13 | }
14 | }
15 | }
16 | `
17 |
18 | export function useClearCompletedTodos () {
19 | const [mutate, { data, error }] = useMutation<
20 | ClearCompletedTodosTypes.ClearCompletedTodos
21 | >(
22 | CLEAR_COMPLETED_TODOS,
23 | {
24 | update (cache) {
25 | let todoIdsToDelete: number[] = [];
26 |
27 | cache.modify({
28 | fields: {
29 | todos (existingTodosConnection, { readField }) {
30 |
31 | const completedTodosCleared = {
32 | ...existingTodosConnection,
33 | edges: existingTodosConnection.edges.filter((edge: any) => {
34 | const shouldDelete = readField('completed', edge.node);
35 |
36 | if (shouldDelete) {
37 | todoIdsToDelete.push(readField('id', edge.node) as number)
38 | }
39 |
40 | return !shouldDelete;
41 | })
42 | }
43 |
44 | return completedTodosCleared;
45 | }
46 | }
47 | })
48 |
49 | todoIdsToDelete?.forEach((todoId) => {
50 | cache.evict({ id: `Todo:${todoId}` })
51 | })
52 | }
53 | }
54 | )
55 |
56 | return { mutate, data, error };
57 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/completeAllTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as CompleteAllTodosTypes from './__generated__/CompleteAllTodos'
4 |
5 | export const COMPLETE_ALL_TODOS = gql`
6 | mutation CompleteAllTodos {
7 | completeAllTodos {
8 | success
9 | todos {
10 | id
11 | text
12 | completed
13 | }
14 | }
15 | }
16 | `
17 |
18 | export function useCompleteAllTodos () {
19 | const [mutate, { data, error }] = useMutation<
20 | CompleteAllTodosTypes.CompleteAllTodos
21 | >(
22 | COMPLETE_ALL_TODOS
23 | )
24 |
25 | return { mutate, data, error };
26 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/completeTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as CompleteTodoTypes from './__generated__/CompleteTodo'
4 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
5 |
6 | export const COMPLETE_TODO = gql`
7 | mutation CompleteTodo ($id: Int!) {
8 | completeTodo (id: $id) {
9 | success
10 | todo {
11 | id
12 | text
13 | completed
14 | }
15 | error {
16 | ... on TodoNotFoundError {
17 | message
18 | }
19 | ... on TodoAlreadyCompletedError {
20 | message
21 | }
22 | }
23 | }
24 | }
25 | `
26 |
27 | export function useCompleteTodo () {
28 | const [mutate, { data, error }] = useMutation<
29 | CompleteTodoTypes.CompleteTodo,
30 | CompleteTodoTypes.CompleteTodoVariables
31 | >(
32 | COMPLETE_TODO
33 | )
34 |
35 | return { mutate, data, error };
36 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/deleteTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as DeleteTodoTypes from './__generated__/DeleteTodo'
4 |
5 | export const DELETE_TODO = gql`
6 | mutation DeleteTodo ($id: Int!) {
7 | deleteTodo (id: $id) {
8 | success
9 | todo {
10 | id
11 | text
12 | completed
13 | }
14 | error {
15 | ... on TodoNotFoundError {
16 | message
17 | }
18 | }
19 | }
20 | }
21 | `
22 |
23 | export function useDeleteTodo () {
24 | const [mutate, { data, error }] = useMutation<
25 | DeleteTodoTypes.DeleteTodo,
26 | DeleteTodoTypes.DeleteTodoVariables
27 | >(
28 | DELETE_TODO,
29 | {
30 | update (cache, el) {
31 | const deletedId = el.data?.deleteTodo.todo?.id
32 |
33 | cache.modify({
34 | fields: {
35 | todos (existingTodos, { readField }) {
36 |
37 | const newTodos = {
38 | ...existingTodos,
39 | edges: existingTodos.edges.filter((edge: any) => {
40 | return deletedId !== readField('id', edge.node);
41 | })
42 | }
43 |
44 | return newTodos;
45 | }
46 | }
47 | });
48 |
49 | cache.evict({ id: `Todo:${deletedId}` })
50 | }
51 | }
52 | )
53 |
54 | return { mutate, data, error };
55 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/editTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | import { gql, useMutation } from "@apollo/client";
4 | import * as EditTodoTypes from './__generated__/EditTodo'
5 |
6 | export const EDIT_TODO = gql`
7 | mutation EditTodo ($id: Int!, $text: String!) {
8 | editTodo (id: $id, text: $text) {
9 | success
10 | todo {
11 | id
12 | text
13 | completed
14 | }
15 | error {
16 | ... on TodoNotFoundError {
17 | message
18 | }
19 | ... on TodoValidationError {
20 | message
21 | }
22 | }
23 | }
24 | }
25 | `
26 |
27 | /**
28 | * An update function is not needed for edits against single-item updates
29 | * because the cache will automatically update the cached version of the
30 | * item stored flat on the cache if we return the new item in the mutation
31 | * response.
32 | */
33 |
34 | export function useEditTodo () {
35 | const [mutate, { data, error }] = useMutation<
36 | EditTodoTypes.EditTodo,
37 | EditTodoTypes.EditTodoVariables
38 | >(
39 | EDIT_TODO
40 | )
41 |
42 | return { mutate, data, error };
43 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/setVisibilityFilter/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createSetVisibilityFilter from './setVisibilityFilter'
3 | import { visibilityFilterVar } from '../../../cache'
4 |
5 | export const setVisibilityFilter = createSetVisibilityFilter(visibilityFilterVar)
6 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/setVisibilityFilter/setVisibilityFilter.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createSetVisibilityFilter from './setVisibilityFilter'
3 | import { VisibilityFilters } from '../../../models/VisibilityFilter';
4 | import { mockVisibilityFilter } from '../../../tests/mocks/mockVisibilityFilter';
5 |
6 | const setVisibilityFilter = createSetVisibilityFilter(mockVisibilityFilter);
7 |
8 | describe('setVisibilityFilter hook', () => {
9 | beforeEach(() => mockVisibilityFilter(VisibilityFilters.SHOW_ALL));
10 |
11 | it('should change the visibility filter', () => {
12 | setVisibilityFilter(VisibilityFilters.SHOW_ALL);
13 |
14 | expect(
15 | mockVisibilityFilter().displayName
16 | ).toEqual("All")
17 |
18 | setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED);
19 |
20 | expect(
21 | mockVisibilityFilter().displayName
22 | ).toEqual("Completed")
23 | });
24 | })
25 |
26 |
27 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/mutations/setVisibilityFilter/setVisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { VisibilityFilter } from "../../../models/VisibilityFilter";
3 | import { ReactiveVar } from "@apollo/client";
4 |
5 | export default (visibilityFilterVar: ReactiveVar) => {
6 | return (filter: VisibilityFilter) => {
7 | visibilityFilterVar(filter);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/queries/__generated__/GetAllTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL query operation: GetAllTodos
7 | // ====================================================
8 |
9 | export interface GetAllTodos_todos_edges_node {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface GetAllTodos_todos_edges {
17 | __typename: "TodosEdge";
18 | node: GetAllTodos_todos_edges_node;
19 | }
20 |
21 | export interface GetAllTodos_todos {
22 | __typename: "TodosConnection";
23 | edges: (GetAllTodos_todos_edges | null)[];
24 | }
25 |
26 | export interface GetAllTodos {
27 | todos: GetAllTodos_todos;
28 | }
29 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/queries/getAllTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql } from "@apollo/client";
3 |
4 | export const GET_ALL_TODOS = gql`
5 | query GetAllTodos {
6 | todos {
7 | edges {
8 | node {
9 | id
10 | text
11 | completed
12 | }
13 | }
14 | }
15 | }
16 | `
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/operations/queries/getVisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql } from "@apollo/client";
3 |
4 | export const GET_VISIBILITY_FILTER = gql`
5 | query GetVisibilityFilter {
6 | visibilityFilter @client {
7 | id
8 | displayName
9 | }
10 | }
11 | `
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/tests/createMockReactiveVar.spec.ts:
--------------------------------------------------------------------------------
1 | import { mockTodosVar } from "./mocks/mockTodosVar";
2 |
3 | describe('Testing mock reactive variables', () => {
4 | beforeEach(() => mockTodosVar([]));
5 |
6 | it('should set the current value if provided', () => {
7 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }]);
8 | expect(mockTodosVar()?.length).toEqual(1);
9 | })
10 |
11 | it('should overwrite the current value', () => {
12 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }]);
13 | expect(mockTodosVar()?.length).toEqual(1);
14 |
15 | mockTodosVar([{ id: 1, text: 'Second todo', completed: false }]);
16 | expect(mockTodosVar()?.length).toEqual(1);
17 | });
18 |
19 | it('should not overwrite the current value if no value provided', () => {
20 | mockTodosVar([
21 | { id: 0, text: 'First todo', completed: false },
22 | { id: 1, text: 'Second todo', completed: false }
23 | ]);
24 | expect(mockTodosVar()?.length).toEqual(2);
25 |
26 | mockTodosVar();
27 | expect(mockTodosVar()?.length).toEqual(2);
28 | })
29 | })
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/tests/createMockReactiveVar.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ReactiveVar } from "@apollo/client";
3 |
4 | // This is how to create a mock reactive variable.
5 | // It's a good idea to do this because then we can test our
6 | // interaction logic.
7 |
8 | export function createMockReactiveVar (defaultValue?: T): ReactiveVar {
9 | let currentValue: T | undefined = defaultValue;
10 |
11 | return function mockReactiveVar (
12 | newValue?: T
13 | ) : T {
14 | if (newValue) {
15 | currentValue = newValue;
16 | }
17 | return currentValue as T;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/tests/mocks/mockTodosVar.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Todos } from "../../models/Todos";
3 | import { createMockReactiveVar } from "../createMockReactiveVar";
4 |
5 | export const mockTodosVar = createMockReactiveVar([]);
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/src/tests/mocks/mockVisibilityFilter.ts:
--------------------------------------------------------------------------------
1 |
2 | import { createMockReactiveVar } from "../createMockReactiveVar";
3 | import { VisibilityFilter, VisibilityFilters } from "../../models/VisibilityFilter";
4 |
5 | export const mockVisibilityFilter = createMockReactiveVar(
6 | VisibilityFilters.SHOW_ALL
7 | );
8 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/README.md:
--------------------------------------------------------------------------------
1 | # apollo-remote-state
2 |
3 | Summary: Hooking Apollo Client up to a remote GraphQL API, the client-side cache is smart enough to automatically update the cache after most mutations successfully complete. For mutations that perform interactions against arrays or have additional client-side side-effects, we can help the cache decide what to do next by writing our update logic in the `useMutation`'s `update` function.
4 |
5 | ## Getting started
6 |
7 | There is a `client/` and `server/` portion to this example.
8 |
9 | ### Running the client
10 |
11 | To start the project, run the following commands:
12 |
13 | ```
14 | cd client
15 | npm install && npm run start
16 | ```
17 |
18 | The app should start at http://localhost:3000/.
19 |
20 | ### Running the server
21 |
22 | To start the project, run the following commands:
23 |
24 | ```
25 | cd server
26 | npm install && npm run start
27 | ```
28 |
29 | The server should start at http://localhost:4000/.
30 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | service: {
3 | name: "ac3-todos-backend"
4 | }
5 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/codegen.yml:
--------------------------------------------------------------------------------
1 | overwrite: true
2 | schema: "http://localhost:4000"
3 | generates:
4 | ./src/generated/graphql.ts:
5 | plugins:
6 | - "typescript"
7 | - "typescript-resolvers"
8 | ./graphql.schema.json:
9 | plugins:
10 | - "introspection"
11 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src", "bin"],
3 | "ext": ".ts,.js",
4 | "ignore": [],
5 | "exec": "ts-node --transpile-only ./src/index.ts"
6 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon",
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "codegen": "graphql-codegen --config ./codegen.yml"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "apollo-server": "^2.11.0",
16 | "graphql": "^14.6.0"
17 | },
18 | "devDependencies": {
19 | "@graphql-codegen/cli": "^1.13.1",
20 | "@graphql-codegen/introspection": "1.13.0",
21 | "@graphql-codegen/typescript": "1.13.0",
22 | "@graphql-codegen/typescript-resolvers": "1.13.0",
23 | "nodemon": "^2.0.2",
24 | "ts-node": "^8.6.2",
25 | "typescript": "^3.8.3"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/src/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ApolloServer } from 'apollo-server'
3 | import { typeDefs } from './schema'
4 | import { resolvers } from './resolvers'
5 | import { todosRepo } from './modules/todos/repos';
6 | import { TodoRepo } from './modules/todos/repos/todoRepo';
7 |
8 | export type Context = { todosRepo: TodoRepo }
9 |
10 | const server = new ApolloServer({
11 | context: () => ({ todosRepo } as Context),
12 | typeDefs,
13 | resolvers
14 | });
15 |
16 | server.listen().then(({ url }) => {
17 | console.log(`🚀 Server ready at ${url}`);
18 | });
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/src/modules/todos/repos/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { InMemoryTodoRepo } from "./implementations/InMemoryTodoRepo";
3 |
4 | const todosRepo = new InMemoryTodoRepo();
5 |
6 | export { todosRepo };
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/src/modules/todos/repos/todoRepo.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Todo } from "../../../generated/graphql";
3 |
4 | export interface TodoRepo {
5 | addTodo(text: string): Promise;
6 | completeTodo (id: number): Promise;
7 | clearCompletedTodos(): Promise;
8 | completeAllTodos (): Promise;
9 | deleteTodo(id: number): Promise;
10 | editTodo(id: number, text: string): Promise;
11 | getAllTodos (): Promise;
12 | getTodoById (id: number): Promise;
13 | getLastTodo (): Promise;
14 | }
15 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/src/shared/mappers/todoMapper.ts:
--------------------------------------------------------------------------------
1 | import { Todo, TodosConnection, TodosEdge } from "../../generated/graphql";
2 |
3 | export class TodoMapper {
4 |
5 | public static toEdge (todo: Todo): TodosEdge {
6 | return {
7 | node: todo,
8 | cursor: ""
9 | };
10 | }
11 |
12 | public static toTodosConnection (todos: Todo[]) : TodosConnection {
13 | return {
14 | edges: todos.map(t => this.toEdge(t)),
15 | pageInfo: {
16 | hasPreviousPage: false,
17 | hasNextPage: false,
18 | startCursor: "",
19 | endCursor: ""
20 | }
21 | };
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/src/shared/utils/numberUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | export class NumberUtils {
3 | public static isANumber (num: any): boolean {
4 | return !isNaN(num);
5 | }
6 |
7 | public static isANonNegativeNumber (num?: any): boolean {
8 | return !isNaN(num) && num >= 0;
9 | }
10 | }
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/src/shared/utils/paginationUtils.ts:
--------------------------------------------------------------------------------
1 | import { Result } from "../core/result";
2 | import { NumberUtils } from "./numberUtils";
3 |
4 | interface Node {
5 | id: number
6 | }
7 |
8 | type GenericNodeList = Node[];
9 |
10 | export class PaginationUtils {
11 | public static filterByBeforeAndAfter (
12 | items: GenericNodeList,
13 | after?: string,
14 | before?: string,
15 | ) : GenericNodeList {
16 | const isAfterSet = !!after === true;
17 | const isBeforeSet = !!before === true;
18 |
19 | if (isAfterSet) {
20 | const afterIndex = items.findIndex((t) => t.id === Number(after))
21 | const afterIndexFound = afterIndex !== -1;
22 |
23 | if (afterIndexFound) {
24 | items = items.slice(afterIndex + 1)
25 | }
26 | }
27 |
28 | if (isBeforeSet) {
29 | const beforeIndex = items.findIndex((t) => t.id === Number(after))
30 | const beforeIndexFond = beforeIndex !== -1;
31 |
32 | if (beforeIndexFond) {
33 | items = items.slice(0, beforeIndex)
34 | }
35 | }
36 |
37 | return items;
38 | }
39 |
40 | public static limitByFirstAndLast(
41 | items: T[],
42 | first?: number,
43 | last?: number
44 | ): Result {
45 | const isFirstSet = NumberUtils.isANumber(first);
46 | const isLastSet = NumberUtils.isANumber(last);
47 |
48 | if (isFirstSet) {
49 | const isFirstAPositiveNumber = NumberUtils.isANonNegativeNumber(first);
50 |
51 | if (!isFirstAPositiveNumber) {
52 | return Result.fail("First has to be greater than 0");
53 | }
54 |
55 | if (items.length > first) {
56 | return Result.ok(items.slice(0, first));
57 | }
58 | }
59 |
60 | if (isLastSet) {
61 | const isLastAPositiveNumber = NumberUtils.isANonNegativeNumber(last);
62 |
63 | if (!isLastAPositiveNumber) {
64 | return Result.fail("Last has to be greater than 0");
65 | }
66 |
67 | if (items.length > last) {
68 | return Result.ok(items.slice(0, last));
69 | }
70 | }
71 |
72 | return Result.ok(items);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/apollo-remote-state-advanced-cache-apis/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "pretty": true,
7 | "sourceMap": true,
8 | "target": "es2017",
9 | "outDir": "./dist",
10 | "lib": ["es6"],
11 | "resolveJsonModule": true,
12 | "types": ["node"],
13 | "typeRoots" : ["./node_modules/@types", "./src/@types"],
14 | "experimentalDecorators": true,
15 | "emitDecoratorMetadata": true,
16 | "esModuleInterop": true
17 | },
18 | "include": [
19 | "src/**/*.ts",
20 | ],
21 | "exclude": [
22 | "node_modules",
23 | ]
24 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apollographql/ac3-state-management-examples/57ddd7aff0b86e12ea7f63db625c004add712ba4/apollo-remote-state-no-relay/.DS_Store
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/README.md:
--------------------------------------------------------------------------------
1 | # apollo-remote-state
2 |
3 | Summary: Hooking Apollo Client up to a remote GraphQL API, the client-side cache is smart enough to automatically update the cache after most mutations successfully complete. For mutations that perform interactions against arrays or have additional client-side side-effects, we can help the cache decide what to do next by writing our update logic in the `useMutation`'s `update` function.
4 |
5 | ## Getting started
6 |
7 | There is a `client/` and `server/` portion to this example.
8 |
9 | ### Running the client
10 |
11 | To start the project, run the following commands:
12 |
13 | ```
14 | cd client
15 | npm install && npm run start
16 | ```
17 |
18 | The app should start at http://localhost:3000/.
19 |
20 | ### Running the server
21 |
22 | To start the project, run the following commands:
23 |
24 | ```
25 | cd server
26 | npm install && npm run start
27 | ```
28 |
29 | The server should start at http://localhost:4000/.
30 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # production
7 | build
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log
12 | .env
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/NOTES.md:
--------------------------------------------------------------------------------
1 |
2 | ## How do I set initial data for something?
3 |
4 | Q: In Redux, we have the concept of initial state. What would you recommend for doing this?
5 |
6 | A: You can use makeVar and give it some default data.
7 |
8 | ```typescript
9 | import { InMemoryCache, Reference } from "@apollo/client";
10 |
11 | export const cache: InMemoryCache = new InMemoryCache({
12 | typePolicies: {
13 | Query: {
14 | fields: {
15 | todos () {
16 | return todosVar();
17 | },
18 | }
19 | }
20 | }
21 | });
22 |
23 | const todosInitialValue: Todos = [
24 | {
25 | id: 0,
26 | completed: false,
27 | text: "Use Apollo Client 3"
28 | }
29 | ]
30 |
31 | export const todosVar = cache.makeVar(todosInitialValue);
32 | ```
33 |
34 | ## If there is no remote GraphQL uri yet (because I'm doing everything locally), or if I never actually intend for there to be one at all, what can I do? It says I need to pass in a valid uri.
35 |
36 | A: If you just need to stub out a GraphQL endpoint for some reason and you don't ever really want to connect to a remote one, you can use
37 |
38 | ```typescript
39 | const client = new ApolloClient({
40 | cache,
41 | link: ApolloLink.empty()
42 | });
43 | ```
44 |
45 | This is nice because you can be explicit about _nothingness_.
46 |
47 | It's common for when:
48 |
49 | - We're building something on the client-side and don't want to try to reach out to a remote GraphQL server yet because (A: it's not ready, B: we will never need one).
50 |
51 | ## How to resolve data that is only on the client side
52 |
53 | Use the `@client` directive.
54 |
55 | ```typescript
56 | client
57 | .query({
58 | query: gql`
59 | {
60 | todos {
61 | id @client
62 | text @client
63 | completed @client
64 | }
65 | }
66 | `
67 | })
68 | .then(result => console.log(result));
69 | ```
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/__generated__/globalTypes.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | //==============================================================
6 | // START Enums and Input Objects
7 | //==============================================================
8 |
9 | //==============================================================
10 | // END Enums and Input Objects
11 | //==============================================================
12 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | client: {
3 | service: {
4 | name: 'ac3-todos-backend',
5 | url: `http://localhost:4000`,
6 | },
7 | },
8 | };
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todomvc-apollo",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "@types/classnames": "^2.2.9",
7 | "@types/react-test-renderer": "^16.9.2",
8 | "react-scripts": "^3.0.0",
9 | "react-test-renderer": "^16.8.6"
10 | },
11 | "dependencies": {
12 | "@apollo/client": "^3.0.1",
13 | "@types/jest": "^25.1.3",
14 | "@types/node": "^13.7.6",
15 | "@types/react": "^16.9.23",
16 | "@types/react-dom": "^16.9.5",
17 | "classnames": "^2.2.6",
18 | "graphql": "^14.6.0",
19 | "prop-types": "^15.7.2",
20 | "react": "^16.8.6",
21 | "react-dom": "^16.8.6",
22 | "reselect": "^4.0.0",
23 | "todomvc-app-css": "^2.2.0",
24 | "typescript": "^3.8.2"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "eject": "react-scripts eject",
30 | "test": "react-scripts test --env=node",
31 | "codegen": "apollo client:codegen --target typescript --excludes src/operations/queries/getVisibilityFilter.tsx"
32 | },
33 | "browserslist": [
34 | ">0.2%",
35 | "not dead",
36 | "not ie <= 11",
37 | "not op_mini all"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Apollo Client 3 Todo MVC Example
7 |
8 |
9 |
10 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/cache.spec.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Todos } from "./models/Todos";
3 | import { VisibilityFilter, VisibilityFilters } from "./models/VisibilityFilter";
4 |
5 | let currentTodosValue: Todos = [];
6 |
7 | export function mockTodosVar (newValue?: Todos | undefined) : Todos {
8 | if (newValue) {
9 | currentTodosValue = newValue;
10 | }
11 | return currentTodosValue;
12 | }
13 |
14 | let currentVisibilityFilter: VisibilityFilter = VisibilityFilters.SHOW_ALL;
15 |
16 | export function mockVisibilityFilter (newValue?: VisibilityFilter | undefined) : VisibilityFilter {
17 | if (newValue) {
18 | currentVisibilityFilter = newValue;
19 | }
20 | return currentVisibilityFilter;
21 | }
22 |
23 | describe('mockTodosVar', () => {
24 | beforeEach(() => mockTodosVar([]));
25 |
26 | it('should set the current value if provided', () => {
27 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }]);
28 | expect(mockTodosVar().length).toEqual(1);
29 | })
30 |
31 | it('should overwrite the current value', () => {
32 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }]);
33 | expect(mockTodosVar().length).toEqual(1);
34 |
35 | mockTodosVar([{ id: 1, text: 'Second todo', completed: false }]);
36 | expect(mockTodosVar().length).toEqual(1);
37 | });
38 |
39 | it('should not overwrite the current value if no value provided', () => {
40 | mockTodosVar([
41 | { id: 0, text: 'First todo', completed: false },
42 | { id: 1, text: 'Second todo', completed: false }
43 | ]);
44 | expect(mockTodosVar().length).toEqual(2);
45 |
46 | mockTodosVar();
47 | expect(mockTodosVar().length).toEqual(2);
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/cache.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { InMemoryCache, makeVar } from "@apollo/client";
3 | import { VisibilityFilters, VisibilityFilter } from "./models/VisibilityFilter";
4 |
5 | export const cache: InMemoryCache = new InMemoryCache({
6 | typePolicies: {
7 | Query: {
8 | fields: {
9 | visibilityFilter: {
10 | read () {
11 | return visibilityFilterVar();
12 | }
13 | }
14 | }
15 | }
16 | }
17 | });
18 |
19 | /**
20 | * Set initial values when we create cache variables.
21 | */
22 |
23 | export const visibilityFilterVar = makeVar(
24 | VisibilityFilters.SHOW_ALL
25 | )
26 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from '../containers/Header'
3 | import MainSection from '../containers/MainSection'
4 |
5 | const App = () => (
6 |
7 |
8 |
9 |
10 | )
11 |
12 | export default App
13 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Link from './Link'
4 | import { VisibilityFilters, VisibilityFilter } from '../models/VisibilityFilter';
5 |
6 | interface FooterProps {
7 | activeVisibilityFilter: VisibilityFilter;
8 | activeCount: number;
9 | completedCount: number;
10 | onClearCompleted: () => void;
11 | setVisibilityFilter: (filter: VisibilityFilter) => void;
12 | }
13 |
14 | const Footer = (props: FooterProps) => {
15 | const { activeCount, completedCount, onClearCompleted, activeVisibilityFilter, setVisibilityFilter } = props;
16 | const itemWord = activeCount === 1 ? "item" : "items";
17 | return (
18 |
38 | );
39 | };
40 |
41 | Footer.propTypes = {
42 | completedCount: PropTypes.number.isRequired,
43 | activeCount: PropTypes.number.isRequired,
44 | onClearCompleted: PropTypes.func.isRequired,
45 | setVisibilityFilter: PropTypes.func.isRequired
46 | }
47 |
48 | export default Footer
49 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoTextInput from './TodoTextInput'
4 |
5 | interface HeaderProps {
6 | addTodo: (text: string) => void;
7 | }
8 |
9 | const Header = ({ addTodo }: HeaderProps) => (
10 |
11 | todos
12 | {
15 | if (text.length !== 0) {
16 | addTodo(text);
17 | }
18 | }}
19 | placeholder="What needs to be done?"
20 | />
21 |
22 | );
23 |
24 | Header.propTypes = {
25 | addTodo: PropTypes.func.isRequired
26 | }
27 |
28 | export default Header
29 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | interface LinkProps {
6 | setFilter: () => any;
7 | active: boolean;
8 | children: any;
9 | }
10 |
11 | const Link = ({ active, children, setFilter }: LinkProps) =>
12 | (
13 | // eslint-disable-next-line jsx-a11y/anchor-is-valid
14 | setFilter()}
18 | >
19 | {children}
20 |
21 | )
22 |
23 |
24 | Link.propTypes = {
25 | active: PropTypes.bool.isRequired,
26 | children: PropTypes.node.isRequired,
27 | setFilter: PropTypes.func.isRequired
28 | }
29 |
30 | export default Link
31 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/components/MainSection.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Footer from './Footer'
4 | import VisibleTodoList from '../containers/VisibleTodoList'
5 | import { VisibilityFilter } from '../models/VisibilityFilter';
6 |
7 | interface MainSectionProps {
8 | activeVisibilityFilter: VisibilityFilter;
9 | todosCount: number;
10 | completedCount: number;
11 | actions: any;
12 | }
13 |
14 | /**
15 | * This is a view component. It doesn't define anything that
16 | * is responsible for querying or mutating, it just relies
17 | * on it from the upper layer component (namely, actions)
18 | */
19 |
20 | const MainSection = ({
21 | activeVisibilityFilter,
22 | todosCount,
23 | completedCount,
24 | actions,
25 | }: MainSectionProps) => (
26 |
49 | );
50 |
51 | MainSection.propTypes = {
52 | todosCount: PropTypes.number.isRequired,
53 | completedCount: PropTypes.number.isRequired,
54 | actions: PropTypes.object.isRequired
55 | }
56 |
57 | export default MainSection;
58 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/components/TodoItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 | import TodoTextInput from './TodoTextInput'
5 |
6 | type Props = any;
7 |
8 | export default class TodoItem extends Component {
9 | static propTypes = {
10 | todo: PropTypes.object.isRequired,
11 | editTodo: PropTypes.func.isRequired,
12 | deleteTodo: PropTypes.func.isRequired,
13 | completeTodo: PropTypes.func.isRequired
14 | }
15 |
16 | state = {
17 | editing: false
18 | }
19 |
20 | handleDoubleClick = () => {
21 | this.setState({ editing: true })
22 | }
23 |
24 | handleSave = (id: number, text: string) => {
25 | if (text.length === 0) {
26 | this.props.deleteTodo(id)
27 | } else {
28 | this.props.editTodo(id, text)
29 | }
30 | this.setState({ editing: false })
31 | }
32 |
33 | render() {
34 | const { todo, completeTodo, deleteTodo } = this.props
35 |
36 | let element
37 | if (this.state.editing) {
38 | element = (
39 | this.handleSave(todo.id, text)} />
42 | )
43 | } else {
44 | element = (
45 |
46 | {
50 | completeTodo(todo.id)
51 | }} />
52 |
55 |
58 | )
59 | }
60 |
61 | return (
62 |
66 | {element}
67 |
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/components/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoItem from './TodoItem'
4 | import { Todo } from '../models/Todos'
5 |
6 | const TodoList = ({ filteredTodos, actions }: any) => (
7 |
8 | {filteredTodos.map((todo: Todo) =>
9 |
10 | )}
11 |
12 | )
13 |
14 | TodoList.propTypes = {
15 | filteredTodos: PropTypes.arrayOf(PropTypes.shape({
16 | id: PropTypes.number.isRequired,
17 | completed: PropTypes.bool.isRequired,
18 | text: PropTypes.string.isRequired
19 | }).isRequired).isRequired,
20 | actions: PropTypes.object.isRequired
21 | }
22 |
23 | export default TodoList
24 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/components/TodoTextInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | export default class TodoTextInput extends Component {
6 | static propTypes = {
7 | onSave: PropTypes.func.isRequired,
8 | text: PropTypes.string,
9 | placeholder: PropTypes.string,
10 | editing: PropTypes.bool,
11 | newTodo: PropTypes.bool
12 | }
13 |
14 | state = {
15 | text: this.props.text || ''
16 | }
17 |
18 | handleSubmit = (e: any) => {
19 | const text = e.target.value.trim()
20 | if (e.which === 13) {
21 | this.props.onSave(text)
22 | if (this.props.newTodo) {
23 | this.setState({ text: '' })
24 | }
25 | }
26 | }
27 |
28 | handleChange = (e: any) => {
29 | this.setState({ text: e.target.value })
30 | }
31 |
32 | handleBlur = (e: any) => {
33 | if (!this.props.newTodo) {
34 | this.props.onSave(e.target.value)
35 | }
36 | }
37 |
38 | render() {
39 | return (
40 |
52 | )
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/containers/Header.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import Header from '../components/Header'
4 | import { useAddTodo } from '../operations/mutations/addTodo';
5 |
6 | export default function () {
7 | const { mutate } = useAddTodo();
8 | return mutate({ variables: { text } })} />;
9 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/containers/MainSection.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import MainSection from '../components/MainSection'
4 | import { useQuery } from '@apollo/client'
5 | import { VisibilityFilter } from '../models/VisibilityFilter'
6 | import { GET_ALL_TODOS } from '../operations/queries/getAllTodos'
7 | import { GET_VISIBILITY_FILTER } from '../operations/queries/getVisibilityFilter'
8 | import { GetAllTodos } from '../operations/queries/__generated__/GetAllTodos'
9 | import { useClearCompletedTodos } from '../operations/mutations/clearCompletedTodos'
10 | import { useCompleteAllTodos } from '../operations/mutations/completeAllTodos'
11 | import { setVisibilityFilter } from '../operations/mutations/setVisibilityFilter'
12 |
13 | export default function Main () {
14 | const {
15 | loading: isTodosLoading,
16 | data,
17 | error: todosError
18 | } = useQuery(GET_ALL_TODOS);
19 | const { data: visibilityFilter } = useQuery(GET_VISIBILITY_FILTER);
20 |
21 | const { mutate: clearCompletedTodos } = useClearCompletedTodos();
22 | const { mutate: completeAllTodos } = useCompleteAllTodos();
23 |
24 | if (isTodosLoading) return Loading...
25 | if (todosError) return An error occurred {JSON.stringify(todosError)}
26 | if (!data) return None
;
27 |
28 | return (
29 | t ? t.completed : false).length}
33 | actions={{
34 | completeAllTodos,
35 | setVisibilityFilter,
36 | clearCompletedTodos,
37 | }}
38 | />
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import App from './components/App'
4 | import { ApolloClient } from '@apollo/client';
5 | import { ApolloProvider } from '@apollo/client';
6 | import 'todomvc-app-css/index.css'
7 | import { cache } from './cache';
8 |
9 | export const client = new ApolloClient({
10 | cache,
11 | uri: 'http://localhost:4000/',
12 | headers: {
13 | authorization: localStorage.getItem('token') || '',
14 | 'client-name': 'ac3-todos-backend',
15 | 'client-version': '1.0.0',
16 | },
17 | connectToDevTools: true,
18 | });
19 |
20 | render(
21 |
22 |
23 | ,
24 | document.getElementById('root')
25 | )
26 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/models/Todos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { GetAllTodos_todos } from "../operations/queries/__generated__/GetAllTodos";
3 |
4 | export type Todo = GetAllTodos_todos;
5 |
6 | export type Todos = Todo[];
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/models/VisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | export type VisibilityFilter = {
3 | id: string;
4 | displayName: string;
5 | }
6 |
7 | export const VisibilityFilters: { [filter: string]: VisibilityFilter } = {
8 | SHOW_ALL: {
9 | id: "show_all",
10 | displayName: "All"
11 | },
12 | SHOW_COMPLETED: {
13 | id: "show_completed",
14 | displayName: "Completed"
15 | },
16 | SHOW_ACTIVE: {
17 | id: "show_active",
18 | displayName: "Active"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/__generated__/AddTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: AddTodo
7 | // ====================================================
8 |
9 | export interface AddTodo_addTodo_todo {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface AddTodo_addTodo_error {
17 | __typename: "TodoValidationError";
18 | message: string;
19 | }
20 |
21 | export interface AddTodo_addTodo {
22 | __typename: "AddTodoResult";
23 | success: boolean;
24 | todo: AddTodo_addTodo_todo | null;
25 | error: AddTodo_addTodo_error | null;
26 | }
27 |
28 | export interface AddTodo {
29 | addTodo: AddTodo_addTodo;
30 | }
31 |
32 | export interface AddTodoVariables {
33 | text: string;
34 | }
35 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/__generated__/ClearCompletedTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: ClearCompletedTodos
7 | // ====================================================
8 |
9 | export interface ClearCompletedTodos_clearCompletedTodos_todos {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface ClearCompletedTodos_clearCompletedTodos {
17 | __typename: "ClearCompletedTodosResult";
18 | success: boolean;
19 | todos: (ClearCompletedTodos_clearCompletedTodos_todos | null)[];
20 | }
21 |
22 | export interface ClearCompletedTodos {
23 | clearCompletedTodos: ClearCompletedTodos_clearCompletedTodos;
24 | }
25 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/__generated__/CompleteAllTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: CompleteAllTodos
7 | // ====================================================
8 |
9 | export interface CompleteAllTodos_completeAllTodos_todos {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface CompleteAllTodos_completeAllTodos {
17 | __typename: "CompleteAllTodosResult";
18 | success: boolean;
19 | todos: (CompleteAllTodos_completeAllTodos_todos | null)[];
20 | }
21 |
22 | export interface CompleteAllTodos {
23 | completeAllTodos: CompleteAllTodos_completeAllTodos;
24 | }
25 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/__generated__/CompleteTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: CompleteTodo
7 | // ====================================================
8 |
9 | export interface CompleteTodo_completeTodo_todo {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface CompleteTodo_completeTodo_error_TodoNotFoundError {
17 | __typename: "TodoNotFoundError";
18 | message: string;
19 | }
20 |
21 | export interface CompleteTodo_completeTodo_error_TodoAlreadyCompletedError {
22 | __typename: "TodoAlreadyCompletedError";
23 | message: string;
24 | }
25 |
26 | export type CompleteTodo_completeTodo_error = CompleteTodo_completeTodo_error_TodoNotFoundError | CompleteTodo_completeTodo_error_TodoAlreadyCompletedError;
27 |
28 | export interface CompleteTodo_completeTodo {
29 | __typename: "CompleteTodoResult";
30 | success: boolean;
31 | todo: CompleteTodo_completeTodo_todo | null;
32 | error: CompleteTodo_completeTodo_error | null;
33 | }
34 |
35 | export interface CompleteTodo {
36 | completeTodo: CompleteTodo_completeTodo;
37 | }
38 |
39 | export interface CompleteTodoVariables {
40 | id: number;
41 | }
42 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/__generated__/DeleteTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: DeleteTodo
7 | // ====================================================
8 |
9 | export interface DeleteTodo_deleteTodo_todo {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface DeleteTodo_deleteTodo_error {
17 | __typename: "TodoNotFoundError";
18 | message: string;
19 | }
20 |
21 | export interface DeleteTodo_deleteTodo {
22 | __typename: "DeleteTodoResult";
23 | success: boolean;
24 | todo: DeleteTodo_deleteTodo_todo | null;
25 | error: DeleteTodo_deleteTodo_error | null;
26 | }
27 |
28 | export interface DeleteTodo {
29 | deleteTodo: DeleteTodo_deleteTodo;
30 | }
31 |
32 | export interface DeleteTodoVariables {
33 | id: number;
34 | }
35 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/__generated__/EditTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL mutation operation: EditTodo
7 | // ====================================================
8 |
9 | export interface EditTodo_editTodo_todo {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface EditTodo_editTodo_error_TodoNotFoundError {
17 | __typename: "TodoNotFoundError";
18 | message: string;
19 | }
20 |
21 | export interface EditTodo_editTodo_error_TodoValidationError {
22 | __typename: "TodoValidationError";
23 | message: string;
24 | }
25 |
26 | export type EditTodo_editTodo_error = EditTodo_editTodo_error_TodoNotFoundError | EditTodo_editTodo_error_TodoValidationError;
27 |
28 | export interface EditTodo_editTodo {
29 | __typename: "EditTodoResult";
30 | success: boolean;
31 | todo: EditTodo_editTodo_todo | null;
32 | error: EditTodo_editTodo_error | null;
33 | }
34 |
35 | export interface EditTodo {
36 | editTodo: EditTodo_editTodo;
37 | }
38 |
39 | export interface EditTodoVariables {
40 | id: number;
41 | text: string;
42 | }
43 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/addTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as AddTodoTypes from './__generated__/AddTodo';
4 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
5 | import { GetAllTodos } from "../queries/__generated__/GetAllTodos";
6 |
7 | export const ADD_TODO = gql`
8 | mutation AddTodo ($text: String!) {
9 | addTodo (text: $text) {
10 | success
11 | todo {
12 | id
13 | text
14 | completed
15 | }
16 | error {
17 | message
18 | }
19 | }
20 | }
21 | `
22 |
23 | export function useAddTodo () {
24 | const [mutate, { data, error }] = useMutation<
25 | AddTodoTypes.AddTodo,
26 | AddTodoTypes.AddTodoVariables
27 | >(
28 | ADD_TODO,
29 | {
30 | update (cache, { data }) {
31 | const newTodoFromResponse = data?.addTodo.todo;
32 | const existingTodos = cache.readQuery({
33 | query: GET_ALL_TODOS,
34 | });
35 |
36 | if (existingTodos && newTodoFromResponse) {
37 | cache.writeQuery({
38 | query: GET_ALL_TODOS,
39 | data: {
40 | todos: [
41 | ...existingTodos?.todos,
42 | newTodoFromResponse,
43 | ],
44 | },
45 | });
46 | }
47 | }
48 | }
49 | )
50 | return { mutate, data, error };
51 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/clearCompletedTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as ClearCompletedTodosTypes from './__generated__/ClearCompletedTodos'
4 | import * as GetAllTodosTypes from '../queries/__generated__/GetAllTodos'
5 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
6 |
7 | export const CLEAR_COMPLETED_TODOS = gql`
8 | mutation ClearCompletedTodos {
9 | clearCompletedTodos {
10 | success
11 | todos {
12 | id
13 | text
14 | completed
15 | }
16 | }
17 | }
18 | `
19 |
20 | export function useClearCompletedTodos () {
21 | const [mutate, { data, error }] = useMutation<
22 | ClearCompletedTodosTypes.ClearCompletedTodos
23 | >(
24 | CLEAR_COMPLETED_TODOS,
25 | {
26 | refetchQueries: [{
27 | query: GET_ALL_TODOS
28 | }],
29 | update (cache) {
30 | const result = cache.readQuery({
31 | query: GET_ALL_TODOS
32 | });
33 | const todosToDelete = result?.todos;
34 |
35 | todosToDelete?.forEach((todo) => {
36 | cache.evict({ id: `Todo:${todo?.id}` })
37 | })
38 | }
39 | }
40 | )
41 |
42 | return { mutate, data, error };
43 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/completeAllTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as CompleteAllTodosTypes from './__generated__/CompleteAllTodos'
4 |
5 | export const COMPLETE_ALL_TODOS = gql`
6 | mutation CompleteAllTodos {
7 | completeAllTodos {
8 | success
9 | todos {
10 | id
11 | text
12 | completed
13 | }
14 | }
15 | }
16 | `
17 |
18 | export function useCompleteAllTodos () {
19 | const [mutate, { data, error }] = useMutation<
20 | CompleteAllTodosTypes.CompleteAllTodos
21 | >(
22 | COMPLETE_ALL_TODOS
23 | )
24 |
25 | return { mutate, data, error };
26 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/completeTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as CompleteTodoTypes from './__generated__/CompleteTodo'
4 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
5 |
6 | export const COMPLETE_TODO = gql`
7 | mutation CompleteTodo ($id: Int!) {
8 | completeTodo (id: $id) {
9 | success
10 | todo {
11 | id
12 | text
13 | completed
14 | }
15 | error {
16 | ... on TodoNotFoundError {
17 | message
18 | }
19 | ... on TodoAlreadyCompletedError {
20 | message
21 | }
22 | }
23 | }
24 | }
25 | `
26 |
27 | export function useCompleteTodo () {
28 | const [mutate, { data, error }] = useMutation<
29 | CompleteTodoTypes.CompleteTodo,
30 | CompleteTodoTypes.CompleteTodoVariables
31 | >(
32 | COMPLETE_TODO,
33 | {
34 | refetchQueries: [{
35 | query: GET_ALL_TODOS
36 | }]
37 | }
38 | )
39 |
40 | return { mutate, data, error };
41 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/deleteTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as DeleteTodoTypes from './__generated__/DeleteTodo'
4 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
5 | import { GetAllTodos } from "../queries/__generated__/GetAllTodos";
6 |
7 | export const DELETE_TODO = gql`
8 | mutation DeleteTodo ($id: Int!) {
9 | deleteTodo (id: $id) {
10 | success
11 | todo {
12 | id
13 | text
14 | completed
15 | }
16 | error {
17 | ... on TodoNotFoundError {
18 | message
19 | }
20 | }
21 | }
22 | }
23 | `
24 |
25 | export function useDeleteTodo () {
26 | const [mutate, { data, error }] = useMutation<
27 | DeleteTodoTypes.DeleteTodo,
28 | DeleteTodoTypes.DeleteTodoVariables
29 | >(
30 | DELETE_TODO,
31 | {
32 | update (cache, el) {
33 | const deletedId = el.data?.deleteTodo.todo?.id
34 | const allTodos = cache.readQuery({ query: GET_ALL_TODOS });
35 |
36 | cache.writeQuery({
37 | query: GET_ALL_TODOS,
38 | data: {
39 | todos: allTodos?.todos.filter((t) => t?.id !== deletedId)
40 | }
41 | });
42 |
43 | cache.evict({ id: `Todo:${deletedId}`})
44 | }
45 | }
46 | )
47 |
48 | return { mutate, data, error };
49 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/editTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | import { gql, useMutation } from "@apollo/client";
4 | import * as EditTodoTypes from './__generated__/EditTodo'
5 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
6 | import { GetAllTodos } from "../queries/__generated__/GetAllTodos";
7 |
8 | export const EDIT_TODO = gql`
9 | mutation EditTodo ($id: Int!, $text: String!) {
10 | editTodo (id: $id, text: $text) {
11 | success
12 | todo {
13 | id
14 | text
15 | completed
16 | }
17 | error {
18 | ... on TodoNotFoundError {
19 | message
20 | }
21 | ... on TodoValidationError {
22 | message
23 | }
24 | }
25 | }
26 | }
27 | `
28 |
29 | export function useEditTodo () {
30 | const [mutate, { data, error }] = useMutation<
31 | EditTodoTypes.EditTodo,
32 | EditTodoTypes.EditTodoVariables
33 | >(
34 | EDIT_TODO,
35 | {
36 | update: (cache) => {
37 | let allTodos = cache.readQuery({
38 | query: GET_ALL_TODOS
39 | });
40 |
41 | if (allTodos) {
42 | cache.writeQuery({
43 | query: GET_ALL_TODOS,
44 | data: {
45 | todos: allTodos?.todos.map(
46 | (t) => t?.id === data?.editTodo.todo?.id ? {
47 | ...t,
48 | node: {
49 | ...t,
50 | text: data?.editTodo.todo?.text
51 | }
52 | } : t)
53 | },
54 | });
55 | }
56 | }
57 | }
58 | )
59 |
60 | return { mutate, data, error };
61 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/setVisibilityFilter/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createSetVisibilityFilter from './setVisibilityFilter'
3 | import { visibilityFilterVar } from '../../../cache'
4 |
5 | export const setVisibilityFilter = createSetVisibilityFilter(visibilityFilterVar)
6 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/setVisibilityFilter/setVisibilityFilter.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createSetVisibilityFilter from './setVisibilityFilter'
3 | import { mockVisibilityFilter } from '../../../cache.spec'
4 | import { VisibilityFilters } from '../../../models/VisibilityFilter';
5 |
6 | const setVisibilityFilter = createSetVisibilityFilter(mockVisibilityFilter);
7 |
8 | describe('setVisibilityFilter hook', () => {
9 | beforeEach(() => mockVisibilityFilter(VisibilityFilters.SHOW_ALL));
10 |
11 | it('should change the visibility filter', () => {
12 | setVisibilityFilter(VisibilityFilters.SHOW_ALL);
13 |
14 | expect(
15 | mockVisibilityFilter().displayName
16 | ).toEqual("All")
17 |
18 | setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED);
19 |
20 | expect(
21 | mockVisibilityFilter().displayName
22 | ).toEqual("Completed")
23 | });
24 | })
25 |
26 |
27 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/mutations/setVisibilityFilter/setVisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { VisibilityFilter } from "../../../models/VisibilityFilter";
3 | import { ReactiveVar } from "@apollo/client";
4 |
5 | export default (visibilityFilterVar: ReactiveVar) => {
6 | return (filter: VisibilityFilter) => {
7 | visibilityFilterVar(filter);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/queries/__generated__/GetAllTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL query operation: GetAllTodos
7 | // ====================================================
8 |
9 | export interface GetAllTodos_todos {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface GetAllTodos {
17 | todos: (GetAllTodos_todos | null)[];
18 | }
19 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/queries/getAllTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql } from '@apollo/client'
3 |
4 | export const GET_ALL_TODOS = gql`
5 | query GetAllTodos {
6 | todos {
7 | id
8 | text
9 | completed
10 | }
11 | }
12 | `
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/operations/queries/getVisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql } from "@apollo/client";
3 |
4 | export const GET_VISIBILITY_FILTER = gql`
5 | query GetVisibilityFilter {
6 | visibilityFilter @client {
7 | id
8 | displayName
9 | }
10 | }
11 | `
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "lockfileVersion": 1
3 | }
4 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | service: {
3 | name: "ac3-todos-backend"
4 | }
5 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/codegen.yml:
--------------------------------------------------------------------------------
1 | overwrite: true
2 | schema: "http://localhost:4000"
3 | generates:
4 | ./src/generated/graphql.ts:
5 | plugins:
6 | - "typescript"
7 | - "typescript-resolvers"
8 | ./graphql.schema.json:
9 | plugins:
10 | - "introspection"
11 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src", "bin"],
3 | "ext": ".ts,.js",
4 | "ignore": [],
5 | "exec": "ts-node --transpile-only ./src/index.ts"
6 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon",
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "codegen": "graphql-codegen --config ./codegen.yml"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "apollo-server": "^2.11.0",
16 | "graphql": "^14.6.0"
17 | },
18 | "devDependencies": {
19 | "@graphql-codegen/cli": "^1.13.1",
20 | "@graphql-codegen/introspection": "1.13.0",
21 | "@graphql-codegen/typescript": "1.13.0",
22 | "@graphql-codegen/typescript-resolvers": "1.13.0",
23 | "nodemon": "^2.0.2",
24 | "ts-node": "^8.6.2",
25 | "typescript": "^3.8.3"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/src/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ApolloServer } from 'apollo-server'
3 | import { typeDefs } from './schema'
4 | import { resolvers } from './resolvers'
5 | import { todosRepo } from './modules/todos/repos';
6 | import { TodoRepo } from './modules/todos/repos/todoRepo';
7 |
8 | export type Context = { todosRepo: TodoRepo }
9 |
10 | const server = new ApolloServer({
11 | context: () => ({ todosRepo } as Context),
12 | typeDefs,
13 | resolvers
14 | });
15 |
16 | server.listen().then(({ url }) => {
17 | console.log(`🚀 Server ready at ${url}`);
18 | });
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/src/modules/todos/repos/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { InMemoryTodoRepo } from "./implementations/InMemoryTodoRepo";
3 |
4 | const todosRepo = new InMemoryTodoRepo();
5 |
6 | export { todosRepo };
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/src/modules/todos/repos/todoRepo.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Todo } from "../../../generated/graphql";
3 |
4 | export interface TodoRepo {
5 | addTodo(text: string): Promise;
6 | completeTodo (id: number): Promise;
7 | clearCompletedTodos(): Promise;
8 | completeAllTodos (): Promise;
9 | deleteTodo(id: number): Promise;
10 | editTodo(id: number, text: string): Promise;
11 | getAllTodos (): Promise;
12 | getTodoById (id: number): Promise;
13 | getLastTodo (): Promise;
14 | }
15 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/src/schema.ts:
--------------------------------------------------------------------------------
1 | import { gql } from "apollo-server";
2 |
3 | const typeDefs = gql`
4 | type PageInfo {
5 | hasPreviousPage: Boolean!
6 | hasNextPage: Boolean!
7 | startCursor: String!
8 | endCursor: String!
9 | }
10 |
11 | type Todo {
12 | id: Int!
13 | text: String!
14 | completed: Boolean!
15 | }
16 |
17 | type TodoNotFoundError {
18 | message: String!
19 | }
20 |
21 | union TodoResult = Todo | TodoNotFoundError
22 |
23 | type Query {
24 | todos (
25 | after: String,
26 | before: String,
27 | first: Int,
28 | last: Int
29 | ): [Todo]!
30 |
31 | todo (id: Int!): TodoResult!
32 | }
33 |
34 | type TodoAlreadyCompletedError {
35 | message: String!
36 | }
37 |
38 | union CompleteTodoError = TodoNotFoundError | TodoAlreadyCompletedError
39 |
40 | type CompleteTodoResult {
41 | success: Boolean!
42 | todo: Todo
43 | error: CompleteTodoError
44 | }
45 |
46 | type TodoValidationError {
47 | message: String!
48 | }
49 |
50 | type AddTodoResult {
51 | success: Boolean!
52 | todo: Todo
53 | error: TodoValidationError
54 | }
55 |
56 | type ClearCompletedTodosResult {
57 | success: Boolean!
58 | todos: [Todo]!
59 | }
60 |
61 | type CompleteAllTodosResult {
62 | success: Boolean!
63 | todos: [Todo]!
64 | }
65 |
66 | type DeleteTodoResult {
67 | success: Boolean!
68 | todo: Todo
69 | error: TodoNotFoundError
70 | }
71 |
72 | union EditTodoError = TodoNotFoundError | TodoValidationError
73 |
74 | type EditTodoResult {
75 | success: Boolean!
76 | todo: Todo
77 | error: EditTodoError
78 | }
79 |
80 | type Mutation {
81 | addTodo (text: String!): AddTodoResult!
82 | clearCompletedTodos: ClearCompletedTodosResult!
83 | completeTodo (id: Int!): CompleteTodoResult!
84 | completeAllTodos: CompleteAllTodosResult!
85 | deleteTodo (id: Int!): DeleteTodoResult!
86 | editTodo (id: Int!, text: String!): EditTodoResult!
87 | }
88 | `
89 |
90 | export { typeDefs }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/src/shared/mappers/todoMapper.ts:
--------------------------------------------------------------------------------
1 | import { Todo, TodosConnection, TodosEdge } from "../../generated/graphql";
2 |
3 | export class TodoMapper {
4 |
5 | public static toEdge (todo: Todo): TodosEdge {
6 | return {
7 | node: todo,
8 | cursor: ""
9 | };
10 | }
11 |
12 | public static toTodosConnection (todos: Todo[]) : TodosConnection {
13 | return {
14 | edges: todos.map(t => this.toEdge(t)),
15 | pageInfo: {
16 | hasPreviousPage: false,
17 | hasNextPage: false,
18 | startCursor: "",
19 | endCursor: ""
20 | }
21 | };
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/src/shared/utils/numberUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | export class NumberUtils {
3 | public static isANumber (num: any): boolean {
4 | return !isNaN(num);
5 | }
6 |
7 | public static isANonNegativeNumber (num?: any): boolean {
8 | return !isNaN(num) && num >= 0;
9 | }
10 | }
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/src/shared/utils/paginationUtils.ts:
--------------------------------------------------------------------------------
1 | import { Result } from "../core/result";
2 | import { NumberUtils } from "./numberUtils";
3 |
4 | interface Node {
5 | id: number
6 | }
7 |
8 | type GenericNodeList = Node[];
9 |
10 | export class PaginationUtils {
11 | public static filterByBeforeAndAfter (
12 | items: GenericNodeList,
13 | after?: string,
14 | before?: string,
15 | ) : GenericNodeList {
16 | const isAfterSet = !!after === true;
17 | const isBeforeSet = !!before === true;
18 |
19 | if (isAfterSet) {
20 | const afterIndex = items.findIndex((t) => t.id === Number(after))
21 | const afterIndexFound = afterIndex !== -1;
22 |
23 | if (afterIndexFound) {
24 | items = items.slice(afterIndex + 1)
25 | }
26 | }
27 |
28 | if (isBeforeSet) {
29 | const beforeIndex = items.findIndex((t) => t.id === Number(after))
30 | const beforeIndexFond = beforeIndex !== -1;
31 |
32 | if (beforeIndexFond) {
33 | items = items.slice(0, beforeIndex)
34 | }
35 | }
36 |
37 | return items;
38 | }
39 |
40 | public static limitByFirstAndLast(
41 | items: T[],
42 | first?: number,
43 | last?: number
44 | ): Result {
45 | const isFirstSet = NumberUtils.isANumber(first);
46 | const isLastSet = NumberUtils.isANumber(last);
47 |
48 | if (isFirstSet) {
49 | const isFirstAPositiveNumber = NumberUtils.isANonNegativeNumber(first);
50 |
51 | if (!isFirstAPositiveNumber) {
52 | return Result.fail("First has to be greater than 0");
53 | }
54 |
55 | if (items.length > first) {
56 | return Result.ok(items.slice(0, first));
57 | }
58 | }
59 |
60 | if (isLastSet) {
61 | const isLastAPositiveNumber = NumberUtils.isANonNegativeNumber(last);
62 |
63 | if (!isLastAPositiveNumber) {
64 | return Result.fail("Last has to be greater than 0");
65 | }
66 |
67 | if (items.length > last) {
68 | return Result.ok(items.slice(0, last));
69 | }
70 | }
71 |
72 | return Result.ok(items);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/apollo-remote-state-no-relay/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "pretty": true,
7 | "sourceMap": true,
8 | "target": "es2017",
9 | "outDir": "./dist",
10 | "lib": ["es6"],
11 | "resolveJsonModule": true,
12 | "types": ["node"],
13 | "typeRoots" : ["./node_modules/@types", "./src/@types"],
14 | "experimentalDecorators": true,
15 | "emitDecoratorMetadata": true,
16 | "esModuleInterop": true
17 | },
18 | "include": [
19 | "src/**/*.ts",
20 | ],
21 | "exclude": [
22 | "node_modules",
23 | ]
24 | }
--------------------------------------------------------------------------------
/apollo-remote-state/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apollographql/ac3-state-management-examples/57ddd7aff0b86e12ea7f63db625c004add712ba4/apollo-remote-state/.DS_Store
--------------------------------------------------------------------------------
/apollo-remote-state/README.md:
--------------------------------------------------------------------------------
1 | # apollo-remote-state
2 |
3 | Summary: Hooking Apollo Client up to a remote GraphQL API, the client-side cache is smart enough to automatically update the cache after most mutations successfully complete. For mutations that perform interactions against arrays (_cached collections_) or have additional client-side side-effects, we can help the cache decide what to do next by writing our update logic in the `useMutation`'s `update` function.
4 |
5 | ## Getting started
6 |
7 | There is a `client/` and `server/` portion to this example.
8 |
9 | ### Running the client
10 |
11 | To start the project, run the following commands:
12 |
13 | ```
14 | cd client
15 | npm install && npm run start
16 | ```
17 |
18 | The app should start at http://localhost:3000/.
19 |
20 | ### Running the server
21 |
22 | To start the project, run the following commands:
23 |
24 | ```
25 | cd server
26 | npm install && npm run start
27 | ```
28 |
29 | The server should start at http://localhost:4000/.
30 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # production
7 | build
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log
12 | .env
--------------------------------------------------------------------------------
/apollo-remote-state/client/__generated__/globalTypes.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | //==============================================================
7 | // START Enums and Input Objects
8 | //==============================================================
9 |
10 | //==============================================================
11 | // END Enums and Input Objects
12 | //==============================================================
13 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | client: {
3 | service: {
4 | name: 'ac3-todos-backend',
5 | url: `http://localhost:4000`,
6 | },
7 | },
8 | };
--------------------------------------------------------------------------------
/apollo-remote-state/client/local-schema.graphql:
--------------------------------------------------------------------------------
1 |
2 | type VisibilityFilter {
3 | id: String!
4 | displayName: String!
5 | }
6 |
7 | extend type Query {
8 | visibilityFilter: VisibilityFilter!
9 | }
--------------------------------------------------------------------------------
/apollo-remote-state/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todomvc-apollo",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "@types/classnames": "^2.2.9",
7 | "@types/react-test-renderer": "^16.9.2",
8 | "react-scripts": "^3.0.0",
9 | "react-test-renderer": "^16.8.6"
10 | },
11 | "dependencies": {
12 | "@apollo/client": "^3.0.1",
13 | "@types/graphql": "^14.5.0",
14 | "@types/jest": "^25.1.3",
15 | "@types/node": "^13.7.6",
16 | "@types/react": "^16.9.23",
17 | "@types/react-dom": "^16.9.5",
18 | "apollo": "^2.32.5",
19 | "classnames": "^2.2.6",
20 | "graphql": "^14.6.0",
21 | "prop-types": "^15.7.2",
22 | "react": "^16.8.6",
23 | "react-dom": "^16.8.6",
24 | "reselect": "^4.0.0",
25 | "todomvc-app-css": "^2.2.0",
26 | "typescript": "^3.8.2"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "eject": "react-scripts eject",
32 | "test": "react-scripts test --env=node",
33 | "download-schema": "apollo service:download --endpoint=http://localhost:4000/ graphql-schema.json",
34 | "generate": "npm run download-schema && apollo codegen:generate --localSchemaFile=graphql-schema.json,client-schema.graphql --target=typescript --tagName=gql"
35 | },
36 | "browserslist": [
37 | ">0.2%",
38 | "not dead",
39 | "not ie <= 11",
40 | "not op_mini all"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux TodoMVC Example
7 |
8 |
9 |
10 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/cache.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { InMemoryCache, makeVar } from "@apollo/client";
3 | import { VisibilityFilters, VisibilityFilter } from "./models/VisibilityFilter";
4 |
5 | export const cache: InMemoryCache = new InMemoryCache({
6 | typePolicies: {
7 | Query: {
8 | fields: {
9 | visibilityFilter: {
10 | read () {
11 | return visibilityFilterVar();
12 | }
13 | }
14 | }
15 | }
16 | }
17 | });
18 |
19 | /**
20 | * Set initial values when we create cache variables.
21 | */
22 |
23 | export const visibilityFilterVar = makeVar(
24 | VisibilityFilters.SHOW_ALL
25 | )
26 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from '../containers/Header'
3 | import MainSection from '../containers/MainSection'
4 |
5 | const App = () => (
6 |
7 |
8 |
9 |
10 | )
11 |
12 | export default App
13 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Link from './Link'
4 | import { VisibilityFilters, VisibilityFilter } from '../models/VisibilityFilter';
5 |
6 | interface FooterProps {
7 | activeVisibilityFilter: VisibilityFilter;
8 | activeCount: number;
9 | completedCount: number;
10 | onClearCompleted: () => void;
11 | setVisibilityFilter: (filter: VisibilityFilter) => void;
12 | }
13 |
14 | const Footer = (props: FooterProps) => {
15 | const { activeCount, completedCount, onClearCompleted, activeVisibilityFilter, setVisibilityFilter } = props;
16 | const itemWord = activeCount === 1 ? "item" : "items";
17 | return (
18 |
38 | );
39 | };
40 |
41 | Footer.propTypes = {
42 | completedCount: PropTypes.number.isRequired,
43 | activeCount: PropTypes.number.isRequired,
44 | onClearCompleted: PropTypes.func.isRequired,
45 | setVisibilityFilter: PropTypes.func.isRequired
46 | }
47 |
48 | export default Footer
49 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoTextInput from './TodoTextInput'
4 |
5 | interface HeaderProps {
6 | addTodo: (text: string) => void;
7 | }
8 |
9 | const Header = ({ addTodo }: HeaderProps) => (
10 |
11 | todos
12 | {
15 | if (text.length !== 0) {
16 | addTodo(text);
17 | }
18 | }}
19 | placeholder="What needs to be done?"
20 | />
21 |
22 | );
23 |
24 | Header.propTypes = {
25 | addTodo: PropTypes.func.isRequired
26 | }
27 |
28 | export default Header
29 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/components/Link.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | interface LinkProps {
6 | setFilter: () => any;
7 | active: boolean;
8 | children: any;
9 | }
10 |
11 | const Link = ({ active, children, setFilter }: LinkProps) =>
12 | (
13 | // eslint-disable-next-line jsx-a11y/anchor-is-valid
14 | setFilter()}
18 | >
19 | {children}
20 |
21 | )
22 |
23 |
24 | Link.propTypes = {
25 | active: PropTypes.bool.isRequired,
26 | children: PropTypes.node.isRequired,
27 | setFilter: PropTypes.func.isRequired
28 | }
29 |
30 | export default Link
31 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/components/MainSection.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Footer from './Footer'
4 | import VisibleTodoList from '../containers/VisibleTodoList'
5 | import { VisibilityFilter } from '../models/VisibilityFilter';
6 |
7 | interface MainSectionProps {
8 | activeVisibilityFilter: VisibilityFilter;
9 | todosCount: number;
10 | completedCount: number;
11 | actions: any;
12 | }
13 |
14 | /**
15 | * This is a view component. It doesn't define anything that
16 | * is responsible for querying or mutating, it just relies
17 | * on it from the upper layer component (namely, actions)
18 | */
19 |
20 | const MainSection = ({
21 | activeVisibilityFilter,
22 | todosCount,
23 | completedCount,
24 | actions,
25 | }: MainSectionProps) => (
26 |
49 | );
50 |
51 | MainSection.propTypes = {
52 | todosCount: PropTypes.number.isRequired,
53 | completedCount: PropTypes.number.isRequired,
54 | actions: PropTypes.object.isRequired
55 | }
56 |
57 | export default MainSection;
58 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/components/TodoItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 | import TodoTextInput from './TodoTextInput'
5 |
6 | type Props = any;
7 |
8 | export default class TodoItem extends Component {
9 | static propTypes = {
10 | todo: PropTypes.object.isRequired,
11 | editTodo: PropTypes.func.isRequired,
12 | deleteTodo: PropTypes.func.isRequired,
13 | completeTodo: PropTypes.func.isRequired
14 | }
15 |
16 | state = {
17 | editing: false
18 | }
19 |
20 | handleDoubleClick = () => {
21 | this.setState({ editing: true })
22 | }
23 |
24 | handleSave = (id: number, text: string) => {
25 | if (text.length === 0) {
26 | this.props.deleteTodo(id)
27 | } else {
28 | this.props.editTodo(id, text)
29 | }
30 | this.setState({ editing: false })
31 | }
32 |
33 | render() {
34 | const { todo, completeTodo, deleteTodo } = this.props
35 |
36 | let element
37 | if (this.state.editing) {
38 | element = (
39 | this.handleSave(todo.id, text)} />
42 | )
43 | } else {
44 | element = (
45 |
46 | {
50 | completeTodo(todo.id)
51 | }} />
52 |
55 |
58 | )
59 | }
60 |
61 | return (
62 |
66 | {element}
67 |
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/components/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoItem from './TodoItem'
4 | import { Todo } from '../models/Todos'
5 |
6 | const TodoList = ({ filteredTodos, actions }: any) => (
7 |
8 | {filteredTodos.map((todo: Todo) =>
9 |
10 | )}
11 |
12 | )
13 |
14 | TodoList.propTypes = {
15 | filteredTodos: PropTypes.arrayOf(PropTypes.shape({
16 | id: PropTypes.number.isRequired,
17 | completed: PropTypes.bool.isRequired,
18 | text: PropTypes.string.isRequired
19 | }).isRequired).isRequired,
20 | actions: PropTypes.object.isRequired
21 | }
22 |
23 | export default TodoList
24 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/components/TodoTextInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | export default class TodoTextInput extends Component {
6 | static propTypes = {
7 | onSave: PropTypes.func.isRequired,
8 | text: PropTypes.string,
9 | placeholder: PropTypes.string,
10 | editing: PropTypes.bool,
11 | newTodo: PropTypes.bool
12 | }
13 |
14 | state = {
15 | text: this.props.text || ''
16 | }
17 |
18 | handleSubmit = (e: any) => {
19 | const text = e.target.value.trim()
20 | if (e.which === 13) {
21 | this.props.onSave(text)
22 | if (this.props.newTodo) {
23 | this.setState({ text: '' })
24 | }
25 | }
26 | }
27 |
28 | handleChange = (e: any) => {
29 | this.setState({ text: e.target.value })
30 | }
31 |
32 | handleBlur = (e: any) => {
33 | if (!this.props.newTodo) {
34 | this.props.onSave(e.target.value)
35 | }
36 | }
37 |
38 | render() {
39 | return (
40 |
52 | )
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/containers/Header.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import Header from '../components/Header'
4 | import { useAddTodo } from '../operations/mutations/addTodo';
5 |
6 | export default function () {
7 | const { mutate } = useAddTodo();
8 | return mutate({ variables: { text } })} />;
9 | }
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/containers/MainSection.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import MainSection from '../components/MainSection'
4 | import { useQuery, gql } from '@apollo/client'
5 | import { VisibilityFilter } from '../models/VisibilityFilter'
6 | import { GET_ALL_TODOS } from '../operations/queries/getAllTodos'
7 | import { GET_VISIBILITY_FILTER } from '../operations/queries/getVisibilityFilter'
8 | import { GetAllTodos } from '../operations/__generated__/GetAllTodos'
9 | import { useClearCompletedTodos } from '../operations/mutations/clearCompletedTodos'
10 | import { useCompleteAllTodos } from '../operations/mutations/completeAllTodos'
11 | import setVisibilityFilter from '../operations/mutations/setVisibilityFilter/setVisibilityFilter'
12 | import { GetVisibilityFilter } from '../operations/queries/__generated__/GetVisibilityFilter'
13 |
14 | export default function Main () {
15 | const { loading: isTodosLoading, data: todosConnection, error: todosError } = useQuery(GET_ALL_TODOS);
16 | const { data: visibilityFilter } = useQuery(GET_VISIBILITY_FILTER);
17 |
18 | const { mutate: clearCompletedTodos } = useClearCompletedTodos();
19 | const { mutate: completeAllTodos } = useCompleteAllTodos();
20 |
21 | if (isTodosLoading) return Loading...
22 | if (todosError) return An error occurred {JSON.stringify(todosError)}
23 | if (!todosConnection) return None
;
24 | const todos = todosConnection.todos.edges.map((t) => t?.node)
25 |
26 | return (
27 | t ? t.completed : false).length}
31 | actions={{
32 | completeAllTodos,
33 | setVisibilityFilter,
34 | clearCompletedTodos,
35 | }}
36 | />
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/containers/__generated__/GetLastTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL query operation: GetLastTodos
8 | // ====================================================
9 |
10 | export interface GetLastTodos_todos_edges_node {
11 | __typename: "Todo";
12 | id: number;
13 | text: string;
14 | completed: boolean;
15 | }
16 |
17 | export interface GetLastTodos_todos_edges {
18 | __typename: "TodosEdge";
19 | node: GetLastTodos_todos_edges_node;
20 | }
21 |
22 | export interface GetLastTodos_todos {
23 | __typename: "TodosConnection";
24 | edges: (GetLastTodos_todos_edges | null)[];
25 | }
26 |
27 | export interface GetLastTodos {
28 | todos: GetLastTodos_todos;
29 | }
30 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import App from './components/App'
4 | import { ApolloClient, ApolloProvider } from '@apollo/client';
5 | import 'todomvc-app-css/index.css'
6 | import { cache } from './cache';
7 |
8 | export const client = new ApolloClient({
9 | cache,
10 | uri: 'http://localhost:4000/',
11 | headers: {
12 | authorization: localStorage.getItem('token') || '',
13 | 'client-name': 'ac3-todos-backend',
14 | 'client-version': '1.0.0',
15 | },
16 | connectToDevTools: true,
17 | });
18 |
19 | render(
20 |
21 |
22 | ,
23 | document.getElementById('root')
24 | )
25 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/models/Todos.tsx:
--------------------------------------------------------------------------------
1 | import { GetAllTodos_todos_edges_node } from "../operations/__generated__/GetAllTodos";
2 |
3 | export type Todo = GetAllTodos_todos_edges_node;
4 |
5 | export type Todos = Todo[];
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/models/VisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | export type VisibilityFilter = {
3 | id: string;
4 | displayName: string;
5 | }
6 |
7 | export const VisibilityFilters: { [filter: string]: VisibilityFilter } = {
8 | SHOW_ALL: {
9 | id: "show_all",
10 | displayName: "All"
11 | },
12 | SHOW_COMPLETED: {
13 | id: "show_completed",
14 | displayName: "Completed"
15 | },
16 | SHOW_ACTIVE: {
17 | id: "show_active",
18 | displayName: "Active"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/__generated__/GetAllTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // This file was automatically generated and should not be edited.
4 |
5 | // ====================================================
6 | // GraphQL query operation: GetAllTodos
7 | // ====================================================
8 |
9 | export interface GetAllTodos_todos_edges_node {
10 | __typename: "Todo";
11 | id: number;
12 | text: string;
13 | completed: boolean;
14 | }
15 |
16 | export interface GetAllTodos_todos_edges {
17 | __typename: "TodosEdge";
18 | node: GetAllTodos_todos_edges_node;
19 | }
20 |
21 | export interface GetAllTodos_todos {
22 | __typename: "TodosConnection";
23 | edges: (GetAllTodos_todos_edges | null)[];
24 | }
25 |
26 | export interface GetAllTodos {
27 | todos: GetAllTodos_todos;
28 | }
29 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/__generated__/AddTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: AddTodo
8 | // ====================================================
9 |
10 | export interface AddTodo_addTodo_todo {
11 | __typename: "Todo";
12 | id: number;
13 | text: string;
14 | completed: boolean;
15 | }
16 |
17 | export interface AddTodo_addTodo_error {
18 | __typename: "TodoValidationError";
19 | message: string;
20 | }
21 |
22 | export interface AddTodo_addTodo {
23 | __typename: "AddTodoResult";
24 | success: boolean;
25 | todo: AddTodo_addTodo_todo | null;
26 | error: AddTodo_addTodo_error | null;
27 | }
28 |
29 | export interface AddTodo {
30 | addTodo: AddTodo_addTodo;
31 | }
32 |
33 | export interface AddTodoVariables {
34 | text: string;
35 | }
36 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/__generated__/ClearCompletedTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: ClearCompletedTodos
8 | // ====================================================
9 |
10 | export interface ClearCompletedTodos_clearCompletedTodos_todos {
11 | __typename: "Todo";
12 | id: number;
13 | text: string;
14 | completed: boolean;
15 | }
16 |
17 | export interface ClearCompletedTodos_clearCompletedTodos {
18 | __typename: "ClearCompletedTodosResult";
19 | success: boolean;
20 | todos: (ClearCompletedTodos_clearCompletedTodos_todos | null)[];
21 | }
22 |
23 | export interface ClearCompletedTodos {
24 | clearCompletedTodos: ClearCompletedTodos_clearCompletedTodos;
25 | }
26 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/__generated__/CompleteAllTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: CompleteAllTodos
8 | // ====================================================
9 |
10 | export interface CompleteAllTodos_completeAllTodos_todos {
11 | __typename: "Todo";
12 | id: number;
13 | text: string;
14 | completed: boolean;
15 | }
16 |
17 | export interface CompleteAllTodos_completeAllTodos {
18 | __typename: "CompleteAllTodosResult";
19 | success: boolean;
20 | todos: (CompleteAllTodos_completeAllTodos_todos | null)[];
21 | }
22 |
23 | export interface CompleteAllTodos {
24 | completeAllTodos: CompleteAllTodos_completeAllTodos;
25 | }
26 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/__generated__/CompleteTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: CompleteTodo
8 | // ====================================================
9 |
10 | export interface CompleteTodo_completeTodo_todo {
11 | __typename: "Todo";
12 | id: number;
13 | text: string;
14 | completed: boolean;
15 | }
16 |
17 | export interface CompleteTodo_completeTodo_error_TodoNotFoundError {
18 | __typename: "TodoNotFoundError";
19 | message: string;
20 | }
21 |
22 | export interface CompleteTodo_completeTodo_error_TodoAlreadyCompletedError {
23 | __typename: "TodoAlreadyCompletedError";
24 | message: string;
25 | }
26 |
27 | export type CompleteTodo_completeTodo_error = CompleteTodo_completeTodo_error_TodoNotFoundError | CompleteTodo_completeTodo_error_TodoAlreadyCompletedError;
28 |
29 | export interface CompleteTodo_completeTodo {
30 | __typename: "CompleteTodoResult";
31 | success: boolean;
32 | todo: CompleteTodo_completeTodo_todo | null;
33 | error: CompleteTodo_completeTodo_error | null;
34 | }
35 |
36 | export interface CompleteTodo {
37 | completeTodo: CompleteTodo_completeTodo;
38 | }
39 |
40 | export interface CompleteTodoVariables {
41 | id: number;
42 | }
43 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/__generated__/DeleteTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: DeleteTodo
8 | // ====================================================
9 |
10 | export interface DeleteTodo_deleteTodo_todo {
11 | __typename: "Todo";
12 | id: number;
13 | text: string;
14 | completed: boolean;
15 | }
16 |
17 | export interface DeleteTodo_deleteTodo_error {
18 | __typename: "TodoNotFoundError";
19 | message: string;
20 | }
21 |
22 | export interface DeleteTodo_deleteTodo {
23 | __typename: "DeleteTodoResult";
24 | success: boolean;
25 | todo: DeleteTodo_deleteTodo_todo | null;
26 | error: DeleteTodo_deleteTodo_error | null;
27 | }
28 |
29 | export interface DeleteTodo {
30 | deleteTodo: DeleteTodo_deleteTodo;
31 | }
32 |
33 | export interface DeleteTodoVariables {
34 | id: number;
35 | }
36 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/__generated__/EditTodo.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL mutation operation: EditTodo
8 | // ====================================================
9 |
10 | export interface EditTodo_editTodo_todo {
11 | __typename: "Todo";
12 | id: number;
13 | text: string;
14 | completed: boolean;
15 | }
16 |
17 | export interface EditTodo_editTodo_error_TodoNotFoundError {
18 | __typename: "TodoNotFoundError";
19 | message: string;
20 | }
21 |
22 | export interface EditTodo_editTodo_error_TodoValidationError {
23 | __typename: "TodoValidationError";
24 | message: string;
25 | }
26 |
27 | export type EditTodo_editTodo_error = EditTodo_editTodo_error_TodoNotFoundError | EditTodo_editTodo_error_TodoValidationError;
28 |
29 | export interface EditTodo_editTodo {
30 | __typename: "EditTodoResult";
31 | success: boolean;
32 | todo: EditTodo_editTodo_todo | null;
33 | error: EditTodo_editTodo_error | null;
34 | }
35 |
36 | export interface EditTodo {
37 | editTodo: EditTodo_editTodo;
38 | }
39 |
40 | export interface EditTodoVariables {
41 | id: number;
42 | text: string;
43 | }
44 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/addTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as AddTodoTypes from './__generated__/AddTodo';
4 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
5 | import { GetAllTodos } from "../__generated__/GetAllTodos";
6 |
7 | export const ADD_TODO = gql`
8 | mutation AddTodo ($text: String!) {
9 | addTodo (text: $text) {
10 | success
11 | todo {
12 | id
13 | text
14 | completed
15 | }
16 | error {
17 | message
18 | }
19 | }
20 | }
21 | `
22 |
23 | export function useAddTodo () {
24 | const [mutate, { data, error }] = useMutation<
25 | AddTodoTypes.AddTodo,
26 | AddTodoTypes.AddTodoVariables
27 | >(
28 | ADD_TODO,
29 | {
30 | update (cache, { data }) {
31 | const newTodoFromResponse = data?.addTodo.todo;
32 | const existingTodos = cache.readQuery({
33 | query: GET_ALL_TODOS,
34 | });
35 |
36 | if (existingTodos && newTodoFromResponse) {
37 | cache.writeQuery({
38 | query: GET_ALL_TODOS,
39 | data: {
40 | todos: {
41 | edges: [
42 | ...existingTodos?.todos.edges,
43 | { __typename: 'TodosEdge', node: newTodoFromResponse },
44 | ],
45 | },
46 | },
47 | });
48 | }
49 | }
50 | }
51 | )
52 | return { mutate, data, error };
53 | }
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/clearCompletedTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as ClearCompletedTodosTypes from './__generated__/ClearCompletedTodos'
4 | import * as GetAllTodosTypes from '../queries/__generated__/GetAllTodos'
5 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
6 |
7 | export const CLEAR_COMPLETED_TODOS = gql`
8 | mutation ClearCompletedTodos {
9 | clearCompletedTodos {
10 | success
11 | todos {
12 | id
13 | text
14 | completed
15 | }
16 | }
17 | }
18 | `
19 |
20 | export function useClearCompletedTodos () {
21 | const [mutate, { data, error }] = useMutation<
22 | ClearCompletedTodosTypes.ClearCompletedTodos
23 | >(
24 | CLEAR_COMPLETED_TODOS,
25 | {
26 | update (cache) {
27 | const allTodos = cache.readQuery({
28 | query: GET_ALL_TODOS
29 | });
30 |
31 | cache.writeQuery({
32 | query: GET_ALL_TODOS,
33 | data: {
34 | todos: {
35 | edges: allTodos?.todos.edges
36 | .filter((t) => !t?.node.completed)
37 | },
38 | },
39 | });
40 | }
41 | }
42 | )
43 |
44 | return { mutate, data, error };
45 | }
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/completeAllTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as CompleteAllTodosTypes from './__generated__/CompleteAllTodos'
4 |
5 | export const COMPLETE_ALL_TODOS = gql`
6 | mutation CompleteAllTodos {
7 | completeAllTodos {
8 | success
9 | todos {
10 | id
11 | text
12 | completed
13 | }
14 | }
15 | }
16 | `
17 |
18 | export function useCompleteAllTodos () {
19 | const [mutate, { data, error }] = useMutation<
20 | CompleteAllTodosTypes.CompleteAllTodos
21 | >(
22 | COMPLETE_ALL_TODOS
23 | )
24 |
25 | return { mutate, data, error };
26 | }
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/completeTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as CompleteTodoTypes from './__generated__/CompleteTodo'
4 |
5 | export const COMPLETE_TODO = gql`
6 | mutation CompleteTodo ($id: Int!) {
7 | completeTodo (id: $id) {
8 | success
9 | todo {
10 | id
11 | text
12 | completed
13 | }
14 | error {
15 | ... on TodoNotFoundError {
16 | message
17 | }
18 | ... on TodoAlreadyCompletedError {
19 | message
20 | }
21 | }
22 | }
23 | }
24 | `
25 |
26 | export function useCompleteTodo () {
27 | const [mutate, { data, error }] = useMutation<
28 | CompleteTodoTypes.CompleteTodo,
29 | CompleteTodoTypes.CompleteTodoVariables
30 | >(
31 | COMPLETE_TODO
32 | )
33 |
34 | return { mutate, data, error };
35 | }
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/deleteTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql, useMutation } from "@apollo/client";
3 | import * as DeleteTodoTypes from './__generated__/DeleteTodo'
4 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
5 | import { GetAllTodos } from "../__generated__/GetAllTodos";
6 |
7 | export const DELETE_TODO = gql`
8 | mutation DeleteTodo ($id: Int!) {
9 | deleteTodo (id: $id) {
10 | success
11 | todo {
12 | id
13 | text
14 | completed
15 | }
16 | error {
17 | ... on TodoNotFoundError {
18 | message
19 | }
20 | }
21 | }
22 | }
23 | `
24 |
25 | export function useDeleteTodo () {
26 | const [mutate, { data, error }] = useMutation<
27 | DeleteTodoTypes.DeleteTodo,
28 | DeleteTodoTypes.DeleteTodoVariables
29 | >(
30 | DELETE_TODO,
31 | {
32 | update (cache, { data }) {
33 | const deletedTodoId = data?.deleteTodo.todo?.id;
34 | const allTodos = cache.readQuery({
35 | query: GET_ALL_TODOS
36 | });
37 |
38 | cache.writeQuery({
39 | query: GET_ALL_TODOS,
40 | data: {
41 | todos: {
42 | edges: allTodos?.todos.edges.filter((t) => t?.node.id !== deletedTodoId)
43 | },
44 | },
45 | });
46 | }
47 | }
48 | )
49 |
50 | return { mutate, data, error };
51 | }
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/editTodo.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | import { gql, useMutation } from "@apollo/client";
4 | import * as EditTodoTypes from './__generated__/EditTodo'
5 | import { GET_ALL_TODOS } from "../queries/getAllTodos";
6 | import { GetAllTodos } from "../__generated__/GetAllTodos";
7 |
8 | export const EDIT_TODO = gql`
9 | mutation EditTodo ($id: Int!, $text: String!) {
10 | editTodo (id: $id, text: $text) {
11 | success
12 | todo {
13 | id
14 | text
15 | completed
16 | }
17 | error {
18 | ... on TodoNotFoundError {
19 | message
20 | }
21 | ... on TodoValidationError {
22 | message
23 | }
24 | }
25 | }
26 | }
27 | `
28 |
29 | export function useEditTodo () {
30 | const [mutate, { data, error }] = useMutation<
31 | EditTodoTypes.EditTodo,
32 | EditTodoTypes.EditTodoVariables
33 | >(
34 | EDIT_TODO,
35 | {
36 | update: (cache) => {
37 | let allTodos = cache.readQuery({
38 | query: GET_ALL_TODOS
39 | });
40 |
41 | if (allTodos) {
42 | cache.writeQuery({
43 | query: GET_ALL_TODOS,
44 | data: {
45 | todos: {
46 | edges: allTodos?.todos.edges.map(
47 | (t) => t?.node.id === data?.editTodo.todo?.id ? {
48 | ...t,
49 | node: {
50 | ...t?.node,
51 | text: data?.editTodo.todo?.text
52 | }
53 | } : t)
54 | },
55 | },
56 | });
57 | }
58 | }
59 | }
60 | )
61 |
62 | return { mutate, data, error };
63 | }
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/setVisibilityFilter/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createSetVisibilityFilter from './setVisibilityFilter'
3 | import { visibilityFilterVar } from '../../../cache'
4 |
5 | export const setVisibilityFilter = createSetVisibilityFilter(visibilityFilterVar)
6 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/setVisibilityFilter/setVisibilityFilter.spec.tsx:
--------------------------------------------------------------------------------
1 |
2 | import createSetVisibilityFilter from './setVisibilityFilter'
3 | import { VisibilityFilters } from '../../../models/VisibilityFilter';
4 | import { mockVisibilityFilter } from '../../../tests/mocks/mockVisibilityFilter';
5 |
6 | const setVisibilityFilter = createSetVisibilityFilter(mockVisibilityFilter);
7 |
8 | describe('setVisibilityFilter hook', () => {
9 | beforeEach(() => mockVisibilityFilter(VisibilityFilters.SHOW_ALL));
10 |
11 | it('should change the visibility filter', () => {
12 | setVisibilityFilter(VisibilityFilters.SHOW_ALL);
13 |
14 | expect(
15 | mockVisibilityFilter().displayName
16 | ).toEqual("All")
17 |
18 | setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED);
19 |
20 | expect(
21 | mockVisibilityFilter().displayName
22 | ).toEqual("Completed")
23 | });
24 | })
25 |
26 |
27 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/mutations/setVisibilityFilter/setVisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { VisibilityFilter } from "../../../models/VisibilityFilter";
3 | import { ReactiveVar } from "@apollo/client";
4 |
5 | export default (visibilityFilterVar: ReactiveVar) => {
6 | return (filter: VisibilityFilter) => {
7 | visibilityFilterVar(filter);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/queries/__generated__/GetAllTodos.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL query operation: GetAllTodos
8 | // ====================================================
9 |
10 | export interface GetAllTodos_todos_edges_node {
11 | __typename: "Todo";
12 | id: number;
13 | text: string;
14 | completed: boolean;
15 | }
16 |
17 | export interface GetAllTodos_todos_edges {
18 | __typename: "TodosEdge";
19 | node: GetAllTodos_todos_edges_node;
20 | }
21 |
22 | export interface GetAllTodos_todos {
23 | __typename: "TodosConnection";
24 | edges: (GetAllTodos_todos_edges | null)[];
25 | }
26 |
27 | export interface GetAllTodos {
28 | todos: GetAllTodos_todos;
29 | }
30 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/queries/__generated__/GetVisibilityFilter.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | // @generated
4 | // This file was automatically generated and should not be edited.
5 |
6 | // ====================================================
7 | // GraphQL query operation: GetVisibilityFilter
8 | // ====================================================
9 |
10 | export interface GetVisibilityFilter_visibilityFilter {
11 | __typename: "VisibilityFilter";
12 | id: string;
13 | displayName: string;
14 | }
15 |
16 | export interface GetVisibilityFilter {
17 | visibilityFilter: GetVisibilityFilter_visibilityFilter;
18 | }
19 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/queries/getAllTodos.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql } from "@apollo/client";
3 |
4 | export const GET_ALL_TODOS = gql`
5 | query GetAllTodos {
6 | todos {
7 | edges {
8 | node {
9 | id
10 | text
11 | completed
12 | }
13 | }
14 | }
15 | }
16 | `
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/operations/queries/getVisibilityFilter.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { gql } from "@apollo/client";
3 |
4 | export const GET_VISIBILITY_FILTER = gql`
5 | query GetVisibilityFilter {
6 | visibilityFilter @client {
7 | id
8 | displayName
9 | }
10 | }
11 | `
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/tests/createMockReactiveVar.spec.ts:
--------------------------------------------------------------------------------
1 | import { mockTodosVar } from "./mocks/mockTodosVar";
2 |
3 | describe('Testing mock reactive variables', () => {
4 | beforeEach(() => mockTodosVar([]));
5 |
6 | it('should set the current value if provided', () => {
7 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }]);
8 | expect(mockTodosVar()?.length).toEqual(1);
9 | })
10 |
11 | it('should overwrite the current value', () => {
12 | mockTodosVar([{ id: 0, text: 'First todo', completed: false }]);
13 | expect(mockTodosVar()?.length).toEqual(1);
14 |
15 | mockTodosVar([{ id: 1, text: 'Second todo', completed: false }]);
16 | expect(mockTodosVar()?.length).toEqual(1);
17 | });
18 |
19 | it('should not overwrite the current value if no value provided', () => {
20 | mockTodosVar([
21 | { id: 0, text: 'First todo', completed: false },
22 | { id: 1, text: 'Second todo', completed: false }
23 | ]);
24 | expect(mockTodosVar()?.length).toEqual(2);
25 |
26 | mockTodosVar();
27 | expect(mockTodosVar()?.length).toEqual(2);
28 | })
29 | })
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/tests/createMockReactiveVar.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ReactiveVar } from "@apollo/client";
3 |
4 | // This is how to create a mock reactive variable.
5 | // It's a good idea to do this because then we can test our
6 | // interaction logic.
7 |
8 | export function createMockReactiveVar (defaultValue?: T): ReactiveVar {
9 | let currentValue: T | undefined = defaultValue;
10 |
11 | return function mockReactiveVar (
12 | newValue?: T
13 | ) : T {
14 | if (newValue) {
15 | currentValue = newValue;
16 | }
17 | return currentValue as T;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/tests/mocks/mockTodosVar.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Todos } from "../../models/Todos";
3 | import { createMockReactiveVar } from "../createMockReactiveVar";
4 |
5 | export const mockTodosVar = createMockReactiveVar([]);
--------------------------------------------------------------------------------
/apollo-remote-state/client/src/tests/mocks/mockVisibilityFilter.ts:
--------------------------------------------------------------------------------
1 |
2 | import { createMockReactiveVar } from "../createMockReactiveVar";
3 | import { VisibilityFilter, VisibilityFilters } from "../../models/VisibilityFilter";
4 |
5 | export const mockVisibilityFilter = createMockReactiveVar(
6 | VisibilityFilters.SHOW_ALL
7 | );
8 |
--------------------------------------------------------------------------------
/apollo-remote-state/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/apollo-remote-state/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "lockfileVersion": 1
3 | }
4 |
--------------------------------------------------------------------------------
/apollo-remote-state/server/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
--------------------------------------------------------------------------------
/apollo-remote-state/server/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | service: {
3 | name: "ac3-todos-backend"
4 | }
5 | }
--------------------------------------------------------------------------------
/apollo-remote-state/server/codegen.yml:
--------------------------------------------------------------------------------
1 | overwrite: true
2 | schema: "http://localhost:4000"
3 | generates:
4 | ./src/generated/graphql.ts:
5 | plugins:
6 | - "typescript"
7 | - "typescript-resolvers"
8 | ./graphql.schema.json:
9 | plugins:
10 | - "introspection"
11 |
--------------------------------------------------------------------------------
/apollo-remote-state/server/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src", "bin"],
3 | "ext": ".ts,.js",
4 | "ignore": [],
5 | "exec": "ts-node --transpile-only ./src/index.ts"
6 | }
--------------------------------------------------------------------------------
/apollo-remote-state/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon",
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "codegen": "graphql-codegen --config ./codegen.yml"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "apollo-server": "^2.11.0",
16 | "graphql": "^14.6.0"
17 | },
18 | "devDependencies": {
19 | "@graphql-codegen/cli": "^1.13.1",
20 | "@graphql-codegen/introspection": "1.13.0",
21 | "@graphql-codegen/typescript": "1.13.0",
22 | "@graphql-codegen/typescript-resolvers": "1.13.0",
23 | "nodemon": "^2.0.2",
24 | "ts-node": "^8.6.2",
25 | "typescript": "^3.8.3"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/apollo-remote-state/server/src/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { ApolloServer } from 'apollo-server'
3 | import { typeDefs } from './schema'
4 | import { resolvers } from './resolvers'
5 | import { todosRepo } from './modules/todos/repos';
6 | import { TodoRepo } from './modules/todos/repos/todoRepo';
7 |
8 | export type Context = { todosRepo: TodoRepo }
9 |
10 | const server = new ApolloServer({
11 | context: () => ({ todosRepo } as Context),
12 | typeDefs,
13 | resolvers
14 | });
15 |
16 | server.listen().then(({ url }) => {
17 | console.log(`🚀 Server ready at ${url}`);
18 | });
--------------------------------------------------------------------------------
/apollo-remote-state/server/src/modules/todos/repos/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import { InMemoryTodoRepo } from "./implementations/InMemoryTodoRepo";
3 |
4 | const todosRepo = new InMemoryTodoRepo();
5 |
6 | export { todosRepo };
--------------------------------------------------------------------------------
/apollo-remote-state/server/src/modules/todos/repos/todoRepo.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Todo } from "../../../generated/graphql";
3 |
4 | export interface TodoRepo {
5 | addTodo(text: string): Promise;
6 | completeTodo (id: number): Promise;
7 | clearCompletedTodos(): Promise;
8 | completeAllTodos (): Promise;
9 | deleteTodo(id: number): Promise;
10 | editTodo(id: number, text: string): Promise;
11 | getAllTodos (): Promise;
12 | getTodoById (id: number): Promise;
13 | getLastTodo (): Promise;
14 | }
15 |
--------------------------------------------------------------------------------
/apollo-remote-state/server/src/shared/mappers/todoMapper.ts:
--------------------------------------------------------------------------------
1 | import { Todo, TodosConnection, TodosEdge } from "../../generated/graphql";
2 |
3 | export class TodoMapper {
4 |
5 | public static toEdge (todo: Todo): TodosEdge {
6 | return {
7 | node: todo,
8 | cursor: ""
9 | };
10 | }
11 |
12 | public static toTodosConnection (todos: Todo[]) : TodosConnection {
13 | return {
14 | edges: todos.map(t => this.toEdge(t)),
15 | pageInfo: {
16 | hasPreviousPage: false,
17 | hasNextPage: false,
18 | startCursor: "",
19 | endCursor: ""
20 | }
21 | };
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/apollo-remote-state/server/src/shared/utils/numberUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | export class NumberUtils {
3 | public static isANumber (num: any): boolean {
4 | return !isNaN(num);
5 | }
6 |
7 | public static isANonNegativeNumber (num?: any): boolean {
8 | return !isNaN(num) && num >= 0;
9 | }
10 | }
--------------------------------------------------------------------------------
/apollo-remote-state/server/src/shared/utils/paginationUtils.ts:
--------------------------------------------------------------------------------
1 | import { Result } from "../core/result";
2 | import { NumberUtils } from "./numberUtils";
3 |
4 | interface Node {
5 | id: number
6 | }
7 |
8 | type GenericNodeList = Node[];
9 |
10 | export class PaginationUtils {
11 | public static filterByBeforeAndAfter (
12 | items: GenericNodeList,
13 | after?: string,
14 | before?: string,
15 | ) : GenericNodeList {
16 | const isAfterSet = !!after === true;
17 | const isBeforeSet = !!before === true;
18 |
19 | if (isAfterSet) {
20 | const afterIndex = items.findIndex((t) => t.id === Number(after))
21 | const afterIndexFound = afterIndex !== -1;
22 |
23 | if (afterIndexFound) {
24 | items = items.slice(afterIndex + 1)
25 | }
26 | }
27 |
28 | if (isBeforeSet) {
29 | const beforeIndex = items.findIndex((t) => t.id === Number(after))
30 | const beforeIndexFond = beforeIndex !== -1;
31 |
32 | if (beforeIndexFond) {
33 | items = items.slice(0, beforeIndex)
34 | }
35 | }
36 |
37 | return items;
38 | }
39 |
40 | public static limitByFirstAndLast(
41 | items: T[],
42 | first?: number,
43 | last?: number
44 | ): Result {
45 | const isFirstSet = NumberUtils.isANumber(first);
46 | const isLastSet = NumberUtils.isANumber(last);
47 |
48 | if (isFirstSet) {
49 | const isFirstAPositiveNumber = NumberUtils.isANonNegativeNumber(first);
50 |
51 | if (!isFirstAPositiveNumber) {
52 | return Result.fail("First has to be greater than 0");
53 | }
54 |
55 | if (items.length > first) {
56 | return Result.ok(items.slice(0, first));
57 | }
58 | }
59 |
60 | if (isLastSet) {
61 | const isLastAPositiveNumber = NumberUtils.isANonNegativeNumber(last);
62 |
63 | if (!isLastAPositiveNumber) {
64 | return Result.fail("Last has to be greater than 0");
65 | }
66 |
67 | if (items.length > last) {
68 | return Result.ok(items.slice(0, last));
69 | }
70 | }
71 |
72 | return Result.ok(items);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/apollo-remote-state/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "pretty": true,
7 | "sourceMap": true,
8 | "target": "es2017",
9 | "outDir": "./dist",
10 | "lib": ["es6"],
11 | "resolveJsonModule": true,
12 | "types": ["node"],
13 | "typeRoots" : ["./node_modules/@types", "./src/@types"],
14 | "experimentalDecorators": true,
15 | "emitDecoratorMetadata": true,
16 | "esModuleInterop": true
17 | },
18 | "include": [
19 | "src/**/*.ts",
20 | ],
21 | "exclude": [
22 | "node_modules",
23 | ]
24 | }
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "lockfileVersion": 1
3 | }
4 |
--------------------------------------------------------------------------------
/redux-local-state/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # production
7 | build
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log
12 |
--------------------------------------------------------------------------------
/redux-local-state/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todomvc-redux",
3 | "version": "0.0.1",
4 | "private": true,
5 | "devDependencies": {
6 | "react-scripts": "^3.0.0",
7 | "react-test-renderer": "^16.8.6"
8 | },
9 | "dependencies": {
10 | "classnames": "^2.2.6",
11 | "prop-types": "^15.7.2",
12 | "react": "^16.8.6",
13 | "react-dom": "^16.8.6",
14 | "react-redux": "^7.0.2",
15 | "redux": "^4.0.1",
16 | "reselect": "^4.0.0",
17 | "todomvc-app-css": "^2.2.0"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "eject": "react-scripts eject",
23 | "test": "react-scripts test --env=node"
24 | },
25 | "browserslist": [
26 | ">0.2%",
27 | "not dead",
28 | "not ie <= 11",
29 | "not op_mini all"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/redux-local-state/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Redux TodoMVC Example
7 |
8 |
9 |
10 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/redux-local-state/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/ActionTypes'
2 |
3 | export const addTodo = text => ({ type: types.ADD_TODO, text })
4 | export const deleteTodo = id => ({ type: types.DELETE_TODO, id })
5 | export const editTodo = (id, text) => ({ type: types.EDIT_TODO, id, text })
6 | export const completeTodo = id => ({ type: types.COMPLETE_TODO, id })
7 | export const completeAllTodos = () => ({ type: types.COMPLETE_ALL_TODOS })
8 | export const clearCompleted = () => ({ type: types.CLEAR_COMPLETED })
9 | export const setVisibilityFilter = filter => ({ type: types.SET_VISIBILITY_FILTER, filter})
10 |
--------------------------------------------------------------------------------
/redux-local-state/src/actions/index.spec.js:
--------------------------------------------------------------------------------
1 | import * as types from '../constants/ActionTypes'
2 | import * as actions from './index'
3 |
4 | describe('todo actions', () => {
5 | it('addTodo should create ADD_TODO action', () => {
6 | expect(actions.addTodo('Use Redux')).toEqual({
7 | type: types.ADD_TODO,
8 | text: 'Use Redux'
9 | })
10 | })
11 |
12 | it('deleteTodo should create DELETE_TODO action', () => {
13 | expect(actions.deleteTodo(1)).toEqual({
14 | type: types.DELETE_TODO,
15 | id: 1
16 | })
17 | })
18 |
19 | it('editTodo should create EDIT_TODO action', () => {
20 | expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({
21 | type: types.EDIT_TODO,
22 | id: 1,
23 | text: 'Use Redux everywhere'
24 | })
25 | })
26 |
27 | it('completeTodo should create COMPLETE_TODO action', () => {
28 | expect(actions.completeTodo(1)).toEqual({
29 | type: types.COMPLETE_TODO,
30 | id: 1
31 | })
32 | })
33 |
34 | it('completeAll should create COMPLETE_ALL action', () => {
35 | expect(actions.completeAllTodos()).toEqual({
36 | type: types.COMPLETE_ALL_TODOS
37 | })
38 | })
39 |
40 | it('clearCompleted should create CLEAR_COMPLETED action', () => {
41 | expect(actions.clearCompleted()).toEqual({
42 | type: types.CLEAR_COMPLETED
43 | })
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from '../containers/Header'
3 | import MainSection from '../containers/MainSection'
4 |
5 | const App = () => (
6 |
7 |
8 |
9 |
10 | )
11 |
12 | export default App
13 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/App.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createRenderer } from 'react-test-renderer/shallow'
3 | import App from './App'
4 | import Header from '../containers/Header'
5 | import MainSection from '../containers/MainSection'
6 |
7 | const setup = _propOverrides => {
8 | const renderer = createRenderer()
9 | renderer.render()
10 | const output = renderer.getRenderOutput()
11 | return output
12 | }
13 |
14 | describe('components', () => {
15 | describe('Header', () => {
16 | it('should render', () => {
17 | const output = setup()
18 | const [header] = output.props.children
19 | expect(header.type).toBe(Header)
20 | })
21 | })
22 |
23 | describe('Mainsection', () => {
24 | it('should render', () => {
25 | const output = setup()
26 | const [, mainSection] = output.props.children
27 | expect(mainSection.type).toBe(MainSection)
28 | })
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import FilterLink from '../containers/FilterLink'
4 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
5 |
6 | const FILTER_TITLES = {
7 | [SHOW_ALL]: 'All',
8 | [SHOW_ACTIVE]: 'Active',
9 | [SHOW_COMPLETED]: 'Completed'
10 | }
11 |
12 | const Footer = (props) => {
13 | const { activeCount, completedCount, onClearCompleted } = props
14 | const itemWord = activeCount === 1 ? 'item' : 'items'
15 | return (
16 |
38 | )
39 | }
40 |
41 | Footer.propTypes = {
42 | completedCount: PropTypes.number.isRequired,
43 | activeCount: PropTypes.number.isRequired,
44 | onClearCompleted: PropTypes.func.isRequired,
45 | }
46 |
47 | export default Footer
48 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoTextInput from './TodoTextInput'
4 |
5 | const Header = ({ addTodo }) => (
6 |
7 | todos
8 | {
11 | if (text.length !== 0) {
12 | addTodo(text)
13 | }
14 | }}
15 | placeholder="What needs to be done?"
16 | />
17 |
18 | )
19 |
20 | Header.propTypes = {
21 | addTodo: PropTypes.func.isRequired
22 | }
23 |
24 | export default Header
25 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/Header.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 | import Header from './Header'
4 | import TodoTextInput from '../components/TodoTextInput'
5 |
6 | const setup = () => {
7 | const props = {
8 | addTodo: jest.fn()
9 | }
10 |
11 | const renderer = createRenderer();
12 | renderer.render()
13 | const output = renderer.getRenderOutput()
14 |
15 | return {
16 | props: props,
17 | output: output,
18 | renderer: renderer
19 | }
20 | }
21 |
22 | describe('components', () => {
23 | describe('Header', () => {
24 | it('should render correctly', () => {
25 | const { output } = setup()
26 | expect(output.type).toBe('header')
27 | expect(output.props.className).toBe('header')
28 |
29 | const [ h1, input ] = output.props.children
30 | expect(h1.type).toBe('h1')
31 | expect(h1.props.children).toBe('todos')
32 | expect(input.type).toBe(TodoTextInput)
33 | expect(input.props.newTodo).toBe(true)
34 | expect(input.props.placeholder).toBe('What needs to be done?')
35 | })
36 |
37 | it('should call addTodo if length of text is greater than 0', () => {
38 | const { output, props } = setup()
39 | const input = output.props.children[1]
40 | input.props.onSave('')
41 | expect(props.addTodo).not.toBeCalled()
42 | input.props.onSave('Use Redux')
43 | expect(props.addTodo).toBeCalled()
44 | })
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/Link.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | const Link = ({ active, children, setFilter }) =>
6 | (
7 | // eslint-disable-next-line jsx-a11y/anchor-is-valid
8 | setFilter()}
12 | >
13 | {children}
14 |
15 | )
16 |
17 |
18 | Link.propTypes = {
19 | active: PropTypes.bool.isRequired,
20 | children: PropTypes.node.isRequired,
21 | setFilter: PropTypes.func.isRequired
22 | }
23 |
24 | export default Link
25 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/Link.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 | import Link from './Link'
4 |
5 | const setup = (propOverrides) => {
6 | const props = Object.assign({
7 | active: false,
8 | children: 'All',
9 | setFilter: jest.fn()
10 | }, propOverrides)
11 |
12 | const renderer = createRenderer();
13 | renderer.render()
14 | const output = renderer.getRenderOutput()
15 |
16 | return {
17 | props: props,
18 | output: output,
19 | }
20 | }
21 |
22 | describe('component', () => {
23 | describe('Link', () => {
24 | it('should render correctly', () => {
25 | const { output } = setup()
26 | expect(output.type).toBe('a')
27 | expect(output.props.style.cursor).toBe('pointer')
28 | expect(output.props.children).toBe('All')
29 | })
30 |
31 | it('should have class selected if active', () => {
32 | const { output } = setup({ active: true })
33 | expect(output.props.className).toBe('selected')
34 | })
35 |
36 | it('should call setFilter on click', () => {
37 | const { output, props } = setup()
38 | output.props.onClick()
39 | expect(props.setFilter).toBeCalled()
40 | })
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/MainSection.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Footer from './Footer'
4 | import VisibleTodoList from '../containers/VisibleTodoList'
5 |
6 | const MainSection = ({ todosCount, completedCount, actions }) =>
7 | (
8 |
31 | )
32 |
33 | MainSection.propTypes = {
34 | todosCount: PropTypes.number.isRequired,
35 | completedCount: PropTypes.number.isRequired,
36 | actions: PropTypes.object.isRequired
37 | }
38 |
39 | export default MainSection;
--------------------------------------------------------------------------------
/redux-local-state/src/components/TodoItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 | import TodoTextInput from './TodoTextInput'
5 |
6 | export default class TodoItem extends Component {
7 | static propTypes = {
8 | todo: PropTypes.object.isRequired,
9 | editTodo: PropTypes.func.isRequired,
10 | deleteTodo: PropTypes.func.isRequired,
11 | completeTodo: PropTypes.func.isRequired
12 | }
13 |
14 | state = {
15 | editing: false
16 | }
17 |
18 | handleDoubleClick = () => {
19 | this.setState({ editing: true })
20 | }
21 |
22 | handleSave = (id, text) => {
23 | if (text.length === 0) {
24 | this.props.deleteTodo(id)
25 | } else {
26 | this.props.editTodo(id, text)
27 | }
28 | this.setState({ editing: false })
29 | }
30 |
31 | render() {
32 | const { todo, completeTodo, deleteTodo } = this.props
33 |
34 | let element
35 | if (this.state.editing) {
36 | element = (
37 | this.handleSave(todo.id, text)} />
40 | )
41 | } else {
42 | element = (
43 |
44 | completeTodo(todo.id)} />
48 |
51 |
54 | )
55 | }
56 |
57 | return (
58 |
62 | {element}
63 |
64 | )
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/TodoList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import TodoItem from './TodoItem'
4 |
5 | const TodoList = ({ filteredTodos, actions }) => (
6 |
7 | {filteredTodos.map(todo =>
8 |
9 | )}
10 |
11 | )
12 |
13 | TodoList.propTypes = {
14 | filteredTodos: PropTypes.arrayOf(PropTypes.shape({
15 | id: PropTypes.number.isRequired,
16 | completed: PropTypes.bool.isRequired,
17 | text: PropTypes.string.isRequired
18 | }).isRequired).isRequired,
19 | actions: PropTypes.object.isRequired
20 | }
21 |
22 | export default TodoList
23 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/TodoList.spec.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createRenderer } from 'react-test-renderer/shallow';
3 | import TodoList from './TodoList'
4 | import TodoItem from './TodoItem'
5 |
6 | const setup = () => {
7 | const props = {
8 | filteredTodos: [
9 | {
10 | text: 'Use Redux',
11 | completed: false,
12 | id: 0
13 | }, {
14 | text: 'Run the tests',
15 | completed: true,
16 | id: 1
17 | }
18 | ],
19 | actions: {
20 | editTodo: jest.fn(),
21 | deleteTodo: jest.fn(),
22 | completeTodo: jest.fn(),
23 | completeAll: jest.fn(),
24 | clearCompleted: jest.fn()
25 | }
26 | }
27 |
28 | const renderer = createRenderer();
29 | renderer.render()
30 | const output = renderer.getRenderOutput()
31 |
32 | return {
33 | props: props,
34 | output: output
35 | }
36 | }
37 |
38 | describe('components', () => {
39 | describe('TodoList', () => {
40 | it('should render container', () => {
41 | const { output } = setup()
42 | expect(output.type).toBe('ul')
43 | expect(output.props.className).toBe('todo-list')
44 | })
45 |
46 | it('should render todos', () => {
47 | const { output, props } = setup()
48 | expect(output.props.children.length).toBe(2)
49 | output.props.children.forEach((todo, i) => {
50 | expect(todo.type).toBe(TodoItem)
51 | expect(Number(todo.key)).toBe(props.filteredTodos[i].id)
52 | expect(todo.props.todo).toBe(props.filteredTodos[i])
53 | })
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/redux-local-state/src/components/TodoTextInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import PropTypes from 'prop-types'
3 | import classnames from 'classnames'
4 |
5 | export default class TodoTextInput extends Component {
6 | static propTypes = {
7 | onSave: PropTypes.func.isRequired,
8 | text: PropTypes.string,
9 | placeholder: PropTypes.string,
10 | editing: PropTypes.bool,
11 | newTodo: PropTypes.bool
12 | }
13 |
14 | state = {
15 | text: this.props.text || ''
16 | }
17 |
18 | handleSubmit = e => {
19 | const text = e.target.value.trim()
20 | if (e.which === 13) {
21 | this.props.onSave(text)
22 | if (this.props.newTodo) {
23 | this.setState({ text: '' })
24 | }
25 | }
26 | }
27 |
28 | handleChange = e => {
29 | this.setState({ text: e.target.value })
30 | }
31 |
32 | handleBlur = e => {
33 | if (!this.props.newTodo) {
34 | this.props.onSave(e.target.value)
35 | }
36 | }
37 |
38 | render() {
39 | return (
40 |
52 | )
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/redux-local-state/src/constants/ActionTypes.js:
--------------------------------------------------------------------------------
1 | export const ADD_TODO = 'ADD_TODO'
2 | export const DELETE_TODO = 'DELETE_TODO'
3 | export const EDIT_TODO = 'EDIT_TODO'
4 | export const COMPLETE_TODO = 'COMPLETE_TODO'
5 | export const COMPLETE_ALL_TODOS = 'COMPLETE_ALL_TODOS'
6 | export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
7 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
8 |
--------------------------------------------------------------------------------
/redux-local-state/src/constants/TodoFilters.js:
--------------------------------------------------------------------------------
1 | export const SHOW_ALL = 'show_all'
2 | export const SHOW_COMPLETED = 'show_completed'
3 | export const SHOW_ACTIVE = 'show_active'
4 |
--------------------------------------------------------------------------------
/redux-local-state/src/containers/FilterLink.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { setVisibilityFilter } from '../actions'
3 | import Link from '../components/Link'
4 |
5 | const mapStateToProps = (state, ownProps) => ({
6 | active: ownProps.filter === state.visibilityFilter
7 | })
8 |
9 | const mapDispatchToProps = (dispatch, ownProps) => ({
10 | setFilter: () => {
11 | dispatch(setVisibilityFilter(ownProps.filter))
12 | }
13 | })
14 |
15 | export default connect(
16 | mapStateToProps,
17 | mapDispatchToProps
18 | )(Link)
19 |
--------------------------------------------------------------------------------
/redux-local-state/src/containers/Header.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import Header from '../components/Header'
3 | import { addTodo } from '../actions'
4 |
5 | export default connect(null, { addTodo })(Header)
6 |
--------------------------------------------------------------------------------
/redux-local-state/src/containers/MainSection.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import * as TodoActions from '../actions'
3 | import { bindActionCreators } from 'redux'
4 | import MainSection from '../components/MainSection'
5 | import { getCompletedTodoCount } from '../selectors'
6 |
7 |
8 | const mapStateToProps = state => ({
9 | todosCount: state.todos.length,
10 | completedCount: getCompletedTodoCount(state)
11 | })
12 |
13 |
14 | const mapDispatchToProps = dispatch => ({
15 | actions: bindActionCreators(TodoActions, dispatch)
16 | })
17 |
18 |
19 | export default connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(MainSection)
23 |
24 |
--------------------------------------------------------------------------------
/redux-local-state/src/containers/VisibleTodoList.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { bindActionCreators } from 'redux'
3 | import * as TodoActions from '../actions'
4 | import TodoList from '../components/TodoList'
5 | import { getVisibleTodos } from '../selectors'
6 |
7 | const mapStateToProps = state => ({
8 | filteredTodos: getVisibleTodos(state)
9 | })
10 |
11 | const mapDispatchToProps = dispatch => ({
12 | actions: bindActionCreators(TodoActions, dispatch)
13 | })
14 |
15 |
16 | const VisibleTodoList = connect(
17 | mapStateToProps,
18 | mapDispatchToProps
19 | )(TodoList)
20 |
21 | export default VisibleTodoList
22 |
--------------------------------------------------------------------------------
/redux-local-state/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { createStore } from 'redux'
4 | import { Provider } from 'react-redux'
5 | import App from './components/App'
6 | import reducer from './reducers'
7 | import 'todomvc-app-css/index.css'
8 |
9 | const store = createStore(reducer)
10 |
11 | render(
12 |
13 |
14 | ,
15 | document.getElementById('root')
16 | )
17 |
--------------------------------------------------------------------------------
/redux-local-state/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import todos from './todos'
3 | import visibilityFilter from './visibilityFilter'
4 |
5 | const rootReducer = combineReducers({
6 | todos,
7 | visibilityFilter
8 | })
9 |
10 | export default rootReducer
11 |
--------------------------------------------------------------------------------
/redux-local-state/src/reducers/todos.js:
--------------------------------------------------------------------------------
1 | import {
2 | ADD_TODO,
3 | DELETE_TODO,
4 | EDIT_TODO,
5 | COMPLETE_TODO,
6 | COMPLETE_ALL_TODOS,
7 | CLEAR_COMPLETED
8 | } from '../constants/ActionTypes'
9 |
10 | const initialState = [
11 | {
12 | text: 'Use Redux',
13 | completed: false,
14 | id: 0
15 | }
16 | ]
17 |
18 | export default function todos(state = initialState, action) {
19 | switch (action.type) {
20 | case ADD_TODO:
21 | return [
22 | ...state,
23 | {
24 | id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
25 | completed: false,
26 | text: action.text
27 | }
28 | ]
29 |
30 | case DELETE_TODO:
31 | return state.filter(todo =>
32 | todo.id !== action.id
33 | )
34 |
35 | case EDIT_TODO:
36 | return state.map(todo =>
37 | todo.id === action.id ?
38 | { ...todo, text: action.text } :
39 | todo
40 | )
41 |
42 | case COMPLETE_TODO:
43 | return state.map(todo =>
44 | todo.id === action.id ?
45 | { ...todo, completed: !todo.completed } :
46 | todo
47 | )
48 |
49 | case COMPLETE_ALL_TODOS:
50 | const areAllMarked = state.every(todo => todo.completed)
51 | return state.map(todo => ({
52 | ...todo,
53 | completed: !areAllMarked
54 | }))
55 |
56 | case CLEAR_COMPLETED:
57 | return state.filter(todo => todo.completed === false)
58 |
59 | default:
60 | return state
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/redux-local-state/src/reducers/visibilityFilter.js:
--------------------------------------------------------------------------------
1 | import { SET_VISIBILITY_FILTER } from '../constants/ActionTypes'
2 | import { SHOW_ALL } from '../constants/TodoFilters'
3 |
4 | const visibilityFilter = (state = SHOW_ALL, action) => {
5 | switch (action.type) {
6 | case SET_VISIBILITY_FILTER:
7 | return action.filter
8 | default:
9 | return state
10 | }
11 | }
12 |
13 | export default visibilityFilter
--------------------------------------------------------------------------------
/redux-local-state/src/selectors/index.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters'
3 |
4 | const getVisibilityFilter = state => state.visibilityFilter
5 | const getTodos = state => state.todos
6 |
7 | export const getVisibleTodos = createSelector(
8 | [getVisibilityFilter, getTodos],
9 | (visibilityFilter, todos) => {
10 | switch (visibilityFilter) {
11 | case SHOW_ALL:
12 | return todos
13 | case SHOW_COMPLETED:
14 | return todos.filter(t => t.completed)
15 | case SHOW_ACTIVE:
16 | return todos.filter(t => !t.completed)
17 | default:
18 | throw new Error('Unknown filter: ' + visibilityFilter)
19 | }
20 | }
21 | )
22 |
23 | export const getCompletedTodoCount = createSelector(
24 | [getTodos],
25 | todos => (
26 | todos.reduce((count, todo) =>
27 | todo.completed ? count + 1 : count,
28 | 0
29 | )
30 | )
31 | )
--------------------------------------------------------------------------------