├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── assets │ ├── PeggyPhoto.jpg │ ├── apollo-client-new.png │ ├── apollo-client-old.png │ ├── apollo.svg │ ├── boost.jpg │ ├── cache-diagram.png │ ├── curve.png │ ├── introspection.png │ ├── links.png │ ├── normalization-diagram.png │ ├── normalization.png │ ├── pupstagram.png │ ├── react-ecosystem-summary.png │ └── urql.png ├── code │ └── index.js ├── images.js ├── index.js ├── presentation.js └── registerServiceWorker.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Give Your Data a Boost! 🚀 (ReactFest 2018) 2 | 3 | ### [View the slides!](http://reactfest-apollo.surge.sh/#/) 💻 4 | ### [Watch the video!](https://youtu.be/jFULzAMS-jI) 📺 5 | 6 | ## Further exploration 7 | - [Zero-config GraphQL state management, Peggy Rayzis](https://dev-blog.apollodata.com/zero-config-graphql-state-management-27b1f1b3c2c3) 8 | - [Pupstagram example app](https://codesandbox.io/s/r5qp83z0yq) 9 | - [Thanks For The Feedback, Douglas Stone](https://books.google.co.uk/books?id=Da_KAQAAQBAJ&printsec=frontcover&dq=thanks+for+the+feedback&hl=en&sa=X&ved=0ahUKEwiZu4vhot_ZAhWbHsAKHTuGBcgQ6AEIJzAA#v=onepage&q=thanks%20for%20the%20feedback&f=false) 10 | - [The future of state management, Peggy Rayzis](https://dev-blog.apollodata.com/the-future-of-state-management-dd410864cae2) 11 | - [Apollo Link State tutorial, Sara Vieira](https://youtu.be/2RvRcnD8wHY) 12 | - [Syntax.fm GraphQL episode, Wes Bos & Scott Tolinski](https://syntax.fm/show/027/graphql-here-is-what-you-need-to-know) 13 | - [React Apollo v2 Query Component CodeSandbox, James Baxley](https://codesandbox.io/embed/71ro40qk16) 14 | - [npm State of JavaScript, Laurie Voss](https://www.npmjs.com/npm/the-state-of-javascript-frameworks-2017-part-2-the-react-ecosystem) 15 | 16 | ## Questions 17 | If you have any questions about the presentation or state management with GraphQL, please feel free to open an issue or start a conversation on [Twitter](https://twitter.com/peggyrayzis). 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactfest-apollo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "^16.2.0", 7 | "react-dom": "^16.2.0", 8 | "spectacle-scripts": "2.0.0" 9 | }, 10 | "scripts": { 11 | "deploy": "npm run build && surge -p build", 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test --env=jsdom", 15 | "eject": "react-scripts eject" 16 | }, 17 | "devDependencies": { 18 | "surge": "^0.19.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Give Your Data a Boost by @peggyrayzis - ReactFest 2018 24 | 25 | 26 | 49 | 50 | 51 | 52 | 55 |
56 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/assets/PeggyPhoto.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/PeggyPhoto.jpg -------------------------------------------------------------------------------- /src/assets/apollo-client-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/apollo-client-new.png -------------------------------------------------------------------------------- /src/assets/apollo-client-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/apollo-client-old.png -------------------------------------------------------------------------------- /src/assets/apollo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 11 | 12 | 13 | 16 | 20 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/assets/boost.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/boost.jpg -------------------------------------------------------------------------------- /src/assets/cache-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/cache-diagram.png -------------------------------------------------------------------------------- /src/assets/curve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/curve.png -------------------------------------------------------------------------------- /src/assets/introspection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/introspection.png -------------------------------------------------------------------------------- /src/assets/links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/links.png -------------------------------------------------------------------------------- /src/assets/normalization-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/normalization-diagram.png -------------------------------------------------------------------------------- /src/assets/normalization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/normalization.png -------------------------------------------------------------------------------- /src/assets/pupstagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/pupstagram.png -------------------------------------------------------------------------------- /src/assets/react-ecosystem-summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/react-ecosystem-summary.png -------------------------------------------------------------------------------- /src/assets/urql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peggyrayzis/reactfest-apollo/b0494fe5fa4991769fd7bc4dbaaa76ccbcb2c0ce/src/assets/urql.png -------------------------------------------------------------------------------- /src/code/index.js: -------------------------------------------------------------------------------- 1 | const typeDefs = `const typeDefs = \` 2 | type Todo { 3 | id: String 4 | message: String! 5 | } 6 | 7 | type Query { 8 | todo(id: String!): Todo 9 | } 10 | \`;`; 11 | 12 | const resolvers = `export const resolvers = { 13 | Image: { isLiked: () => false }, 14 | Mutation: { 15 | toggleLikedPhoto: (_, { id }, { cache, getCacheKey }) => { 16 | const fragment = gql\` 17 | fragment isLiked on Image { 18 | isLiked 19 | } 20 | \`; 21 | const fragmentId = getCacheKey({ id, __typename: "Image" }); 22 | const photo = cache.readFragment({ fragment, id: fragmentId }); 23 | 24 | cache.writeData({ 25 | id: fragmentId, 26 | data: { isLiked: !photo.isLiked } 27 | }); 28 | 29 | return null; 30 | } 31 | } 32 | };`; 33 | 34 | const stateMutation = `const LIKE_PHOTO = gql\` 35 | mutation toggleLikedPhoto($id: String!) { 36 | toggleLikedPhoto(id: $id) @client 37 | } 38 | \`;`; 39 | 40 | const stateMutation2 = `export const DogWithLikes = ({ url, id }) => { 41 | return ( 42 | 43 | {toggleLike => ( 44 | 45 | 46 | 47 | 48 | )} 49 | 50 | ); 51 | };`; 52 | 53 | const boostWithState = `import ApolloClient from 'apollo-boost'; 54 | 55 | const client = new ApolloClient({ 56 | uri: 'https://nx9zvp49q7.lp.gql.zone/graphql', 57 | clientState: { 58 | resolvers, 59 | defaults, 60 | typeDefs, 61 | }, 62 | }); 63 | `; 64 | 65 | const stateLink = `const stateLink = withClientState({ 66 | cache, 67 | defaults: { visibilityFilter: 'SHOW_ALL' }, 68 | resolvers: { 69 | Mutation: { 70 | visibilityFilter: (_, { filter }, { cache }) => { 71 | cache.writeData({ 72 | data: { visibilityFilter: filter } 73 | }); 74 | return null; 75 | }, 76 | }, 77 | }, 78 | });`; 79 | 80 | const clientQuery = `const GET_DOG = gql\` 81 | query getDogByBreed($breed: String!) { 82 | dog(breed: $breed) { 83 | images { 84 | url 85 | id 86 | isLiked @client 87 | } 88 | } 89 | } 90 | \`;`; 91 | 92 | const oldSetup = `yarn add graphql graphql-tag apollo-client apollo-cache-inmemory apollo-link-http react-apollo`; 93 | 94 | const newSetup = `yarn add graphql apollo-boost react-apollo`; 95 | 96 | const requestFn = `import ApolloClient from 'apollo-boost'; 97 | 98 | const client = new ApolloClient({ 99 | uri: 'https://nx9zvp49q7.lp.gql.zone/graphql', 100 | request: operation => { 101 | operation.setContext(context => ({ 102 | headers: { 103 | ...context.headers, 104 | authorization: localStorage.getItem('token') 105 | } 106 | })); 107 | } 108 | });`; 109 | 110 | const boostSetup = `import ApolloClient from 'apollo-boost'; 111 | 112 | const client = new ApolloClient({ 113 | uri: 'https://nx9zvp49q7.lp.gql.zone/graphql' 114 | }); 115 | `; 116 | 117 | const dogQuery = `import { gql } from 'apollo-boost'; 118 | 119 | const GET_DOGS = gql\` 120 | { 121 | dogs { 122 | id 123 | breed 124 | displayImage 125 | } 126 | } 127 | \`;`; 128 | 129 | const dogs = `const Dogs = () => ( 130 | 131 | {({ loading, error, data }) => { 132 | if (loading) return ; 133 | if (error) return ; 134 | 135 | return data.dogs.map(dog => ( 136 | 137 | )); 138 | }} 139 | 140 | ); 141 | `; 142 | 143 | const code = { 144 | setup: `import ApolloClient from 'apollo-client'; 145 | import { InMemoryCache } from 'apollo-cache-inmemory'; 146 | import { HttpLink } from 'apollo-link-http'; 147 | 148 | const client = new ApolloClient({ 149 | cache: new InMemoryCache(), 150 | link: new HttpLink({ 151 | uri: 'https://nx9zvp49q7.lp.gql.zone/graphql' 152 | }), 153 | });`, 154 | typeDefs, 155 | stateLink, 156 | oldSetup, 157 | newSetup, 158 | boostSetup, 159 | resolvers, 160 | requestFn, 161 | dogs, 162 | dogQuery, 163 | clientQuery, 164 | boostWithState, 165 | stateMutation, 166 | stateMutation2, 167 | }; 168 | 169 | export default code; 170 | -------------------------------------------------------------------------------- /src/images.js: -------------------------------------------------------------------------------- 1 | import apollo from './assets/apollo.svg'; 2 | import links from './assets/links.png'; 3 | import introspection from './assets/introspection.png'; 4 | import cacheDiagram from './assets/cache-diagram.png'; 5 | import normalizationDiagram from './assets/normalization-diagram.png'; 6 | import normalization from './assets/normalization.png'; 7 | import boost from './assets/boost.jpg'; 8 | import peggy from './assets/PeggyPhoto.jpg'; 9 | import ecosystem from './assets/react-ecosystem-summary.png'; 10 | import acOld from './assets/apollo-client-old.png'; 11 | import acNew from './assets/apollo-client-new.png'; 12 | import urql from './assets/urql.png'; 13 | import curve from './assets/curve.png'; 14 | import pupstagram from './assets/pupstagram.png'; 15 | 16 | export default { 17 | apollo, 18 | ecosystem, 19 | peggy, 20 | cacheDiagram, 21 | normalizationDiagram, 22 | normalization, 23 | links, 24 | acOld, 25 | acNew, 26 | introspection, 27 | urql, 28 | boost, 29 | curve, 30 | pupstagram, 31 | }; 32 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Presentation from './presentation'; 4 | import registerServiceWorker from './registerServiceWorker'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | registerServiceWorker(); 8 | -------------------------------------------------------------------------------- /src/presentation.js: -------------------------------------------------------------------------------- 1 | // Import React 2 | import React from 'react'; 3 | 4 | // Import Spectacle Core tags 5 | import { 6 | BlockQuote, 7 | Quote, 8 | Cite, 9 | Image, 10 | CodePane, 11 | Deck, 12 | Slide, 13 | Text, 14 | Notes, 15 | Appear, 16 | Link, 17 | List, 18 | ListItem, 19 | } from 'spectacle'; 20 | 21 | // Import theme 22 | import createTheme from 'spectacle/lib/themes/default'; 23 | 24 | // Images, Code samples 25 | import images from './images'; 26 | import code from './code'; 27 | 28 | // Require CSS 29 | require('normalize.css'); 30 | 31 | export const textColor = { 32 | primary: '#3a3c4b', 33 | secondary: '#f7f7f8', 34 | tertiary: '#8b86e3', 35 | quartenary: '#f5a0a4', 36 | }; 37 | 38 | const theme = createTheme(textColor, { 39 | primary: { name: 'Khula', googleFont: true, styles: ['300', '800'] }, 40 | secondary: 'Helvetica', 41 | }); 42 | 43 | export const textSize = { 44 | large: '5.5em', 45 | medium: '3.5em', 46 | small: '2em', 47 | extraSmall: '1.3em', 48 | }; 49 | 50 | export const slideProps = { 51 | maxWidth: 1300, 52 | transition: ['fade'], 53 | bgColor: 'primary', 54 | }; 55 | 56 | export const Title = ({ 57 | children, 58 | textColor = 'secondary', 59 | textSize = '3.5em', 60 | lineHeight = 1, 61 | style, 62 | }) => ( 63 | 71 | {children} 72 | 73 | ); 74 | 75 | export const Attribution = ({ children }) => ( 76 | 77 | {children} 78 | 79 | ); 80 | 81 | export default class Presentation extends React.Component { 82 | render() { 83 | return ( 84 | 92 | 93 | 94 | Give your data
a boost! 🚀 95 |
96 |
97 | 98 | 99 |

100 | My name is Peggy Rayzis and I'm an open source engineer for Apollo 101 | where I build tools to help more people discover the magic that is 102 | GraphQL. If you ever have questions about integrating GraphQL into 103 | your React apps, feel free to reach out to me on Twitter anytime. 104 |
105 | People always wonder what I actually do as an open source 106 | engineer. It changes every day! No two days are ever the same for 107 | me - sometimes I'm building a new feature for Apollo Client, 108 | reviewing pull requests, or evangelizing our tools on Twitter and 109 | Medium. 110 |

111 |
112 |
120 | 127 |
134 | 141 | @peggyrayzis 142 | 143 | 148 |
149 |
150 |
151 | 152 | 153 |

154 | One thing I've learned over the course of my time at Apollo is 155 | that developing open source software is all about giving and 156 | receiving feedback. I'm constantly giving feedback throughout my 157 | day whether I'm reviewing a coworker's blog post or a 158 | contributor's PR. Our team also receives a lot of feedback on 159 | Twitter, in Github issues, and Slack from the developers that use 160 | our tools. Not all of that feedback is positive. 161 |
162 |
163 | I think sometimes we as maintainers pour so much energy into our 164 | projects that sometimes, negative feedback is hard to swallow. We 165 | get defensive and try to justify why the person is wrong. Maybe we 166 | think they're not qualified to give us the feedback or maybe we 167 | think that the criticism isn't grounded in facts. 168 |
169 |
170 | I don't think this wrong-spotting is very productive. Instead, we 171 | should try to look at negative feedback as an opportunity to grow. 172 |

173 |
174 | Developing open source software is all about feedback. 175 |
176 | 177 | 178 |

179 | In order to learn from negative feedback, we have to first 180 | understand it. What data did the feedback giver observe to arrive 181 | at that conclusion? By trying to see things from the other 182 | person's perspective, you'll start to see shreds of truth hidden 183 | within negative feedback. 184 |

185 |
186 | How can we learn 187 | from negative feedback? 188 |
189 | 190 | 191 |

192 | We recently ran into this situation at Apollo. A couple weeks ago, 193 | we noticed some alternative GraphQL clients popping up in the 194 | ecosystem, like urql by Formidable Labs. These new clients did a 195 | great job at delivering an easy setup experience for users, which 196 | sparked discussion that Apollo was too difficult to set up, 197 | especially for beginners, or that we had lost our focus on the 198 | developer experience. 199 |

200 |
201 | 202 | 203 | "Introducing URQL" by @ken_wheeler 204 | 205 |
206 | 207 | 208 |

209 | Hearing feedback that we needed to improve our developer 210 | experience was tough. We easily could have dismissed this feedback 211 | by wrong spotting. However, that's not going to help our users or 212 | help us learn from our mistakes. Instead, we decided to take this 213 | as an opportunity to really think about the getting started 214 | experience with Apollo and what we could do to make GraphQL more 215 | approachable. 216 |
217 |
218 | Training yourself how to view negative feedback is not an easy 219 | task, but there's a book I really enjoy that's helped me change my 220 | mindset about how I give and receive feedback. It's called Thanks 221 | for the Feedback and I highly recommend that you check it out, 222 | especially if you're an open source maintainer. 223 |

224 |
225 |
226 | 236 | In the end, wrong spotting not only defeats wrong feedback, it 237 | defeats learning. 238 | 239 | 240 | Douglas Stone, "Thanks for the Feedback" 241 | 242 |
243 |
244 | 245 | 246 |

247 | One thing we were curious about was the timing of the feedback - 248 | our Apollo Client 2.0 API and setup process had been the same for 249 | over 6 months, so why were we just starting to see the feedback 250 | now? One thing we realized was that the rapid growth of Apollo 251 | over the past year was bringing a lot of newcomers into the 252 | project. If you look at the chart from Laurie Voss of npm, you can 253 | see that downloads of Apollo Client grew 350% last year, with a 254 | lot of that growth happening the second half of the year. 255 |

256 |
257 | 258 | 259 | 260 |
261 | 262 | 263 |

264 | Adoption of a particular technology comes in phases. If you look 265 | at the speed of adoption plotted as a bell curve, you can see that 266 | early growth comes from innovators and early adopters. These 267 | people are usually ok with a little uncertainty, maybe they don't 268 | mind reading the source code or diving in deep when they need to 269 | solve a particular problem. The transition from early adopters to 270 | early majority is pretty significant however as that's when an 271 | idea starts to become mainstream. The early majority doesn't want 272 | to spend their time digging in source code, they want an easy 273 | experience that works out of the box. 274 |
275 |
276 | That's precisely the situation we were facing. As Apollo grows in 277 | popularity, the less tolerance newcomers have for complicated 278 | setup. We need to put ourselves in their shoes to figure out how 279 | to make setup easier. 280 |

281 |
282 | 283 |
284 | 285 | 286 |

287 | Here's all of the packages you had to install before. Sara was 288 | joking with me at dinner last night that I should make a rap of 289 | all the packages. I'm going to spare you my rapping skills, but 290 | just look at this! You have to know about the Apollo cache and 291 | what a link is, it's a lot of mental energy to expend when you're 292 | just starting out. Now, it seems so obvious but when you work on 293 | an open-source project for a long time, it's easy to forget how it 294 | feels to be starting out for the first time. 295 |

296 |
297 | Before 😕 298 | 304 |
305 | 306 | 307 |

308 | Our setup for Apollo Boost, our new zero-config Apollo Client, cut 309 | those packages in half. Not too shabby! 310 |

311 |
312 | After 😍 313 | 319 |
320 | 321 | 322 |

323 | Here's what setting up Apollo Client looked like before. Having a 324 | pluggable cache and link chain is great for more advanced users, 325 | but the vast majority of people getting started with Apollo Client 326 | were using the same InMemoryCache and HttpLink. We needed to find 327 | a solution that worked for both power users and beginners alike. 328 |

329 |
330 | Before 😕 331 |
332 | 339 |
340 |
341 | 342 | 343 |

344 | So, we came up with Apollo Boost! Apollo Boost sets up our 345 | recommended defaults like the InMemoryCache and HttpLink for you. 346 | All you have to do is pass your GraphQL endpoint in to create your 347 | client. In yesterday's GraphQL workshop, we used Apollo Boost and 348 | had everything up and running in less than 10 minutes, which was 349 | super awesome to see! It's also important to mention that nothing 350 | is going away about the old setup. Apollo Boost is a wrapper on 351 | top of Apollo Client that includes all our recommended settings. 352 |

353 |
354 | After 😍 355 |
356 | 363 |
364 |
365 | 366 | 367 |

368 | We want Apollo Boost to transition beginners into creating their 369 | first production-ready apps, so we included some additional 370 | packages in Apollo Boost that reflect best practices. Including 371 | your GraphQL endpoint, there's a total of 6 configuration options, 372 | all optional, you can pass to Apollo Boost. Underneath the hood, 373 | these packages are Apollo Links for error link, client-side data, 374 | and HttpLink. The first config option is onError, which is a 375 | global error handling function that receives both graphql and 376 | network errors. The next one is clientState. clientState is an 377 | object with the same properties you would pass to 378 | apollo-link-state, which allows you to manage local data in Apollo 379 | Client alongside your remote data. We're going to learn more about 380 | that later. fetchOptions allow you to pass static fetch options to 381 | your http link. What about if we want to dynamically set fetch 382 | options like headers? 383 |

384 |
385 | 386 | Under the hood of Apollo Boost 387 | 388 | 389 | 390 | {[ 391 | 'onError: Global error handling function', 392 | 'clientState: Local state management with Apollo', 393 | 'fetchOptions: Pass to your HTTP request', 394 | ].map(item => ( 395 | 396 | {item} 397 | 398 | ))} 399 | 400 |
401 | 402 | 403 |

404 | That's where our new request function comes in! Before, you had to 405 | write your own Apollo Link and know about Observables in order to 406 | perform authentication. Now, you just pass a function to the 407 | request property that takes your graphql operation. From there, 408 | you can set headers dynamically on the context, which will be 409 | picked up by the http link. We're really excited about this 410 | feature because it abstracts all of the complexity of Apollo Link 411 | away without sacrificing any of the power. The request function 412 | can also return a promise in case you need to perform any async 413 | lookups. 414 |

415 |
416 | 417 | Simple auth with request 418 | 419 |
420 | 427 |
428 |
429 | 430 | 431 |

432 | Apollo Boost has only been out for a month, but we've heard a 433 | great response from the community so far. Not only are beginners 434 | loving it, we're also getting great feedback from instructors who 435 | teach workshops. For his new course on Advanced React and Apollo, 436 | Wes Bos was able to cut 100 LOC from his Apollo Client setup code 437 | by switching to Boost. If you're creating content, this is huge! 438 | That's 100 lines he doesn't have to explain, which means you can 439 | get to querying faster. 440 |

441 |
442 |
443 | 453 | Apollo Boost cut my config down by 100 LOC - thanks! 454 | 455 | 456 | Wes Bos, @wesbos 457 | 458 |
459 |
460 | 461 | 462 |

463 | Just because Apollo Boost is marketed as an easy way to get 464 | started doesn't mean it gives up any features. Apollo Boost brings 465 | you the same, consistent Apollo Client experience you know and 466 | love. That means declarative data fetching and being able to 467 | colocate your data requirements next to your UI. It also means 468 | zero-config caching, which we'll explain in detail soon. With 469 | apollo-link-state, you can combine local and server data in one 470 | query, which is something no other state management library has 471 | ever been able to offer. You also gain a painfree debugging 472 | experience with the Apollo Dev Tools and a soon to be released VS 473 | code extension. 474 |

475 |
476 | 477 | The Apollo Client experience: 478 | 479 | 480 | {[ 481 | 'Declarative data fetching', 482 | 'Intelligent zero-config caching', 483 | 'Combine local & server data', 484 | 'Excellent developer tools', 485 | ].map(item => ( 486 | 487 | 488 | {item} 489 | 490 | 491 | ))} 492 | 493 |
494 | 495 | 496 |

497 | What exactly do I mean by declarative data fetching? Let's look at 498 | a sample query for a list of dogs. With GraphQL, we can specify 499 | only the properties our component needs. 500 |

501 |
502 | 503 | Declarative data fetching 504 | 505 | 512 |
513 | 514 | 515 |

516 | Then, we bind that GraphQL query to our UI by creating a query 517 | component. This is our new React Apollo 2.1 API, which is 518 | currently in beta but is about to be released very soon. It uses 519 | render props, which makes it really easy to compose multiple 520 | queries in one component. Apollo Client takes care of fetching 521 | your query and updating your component when data comes back. You 522 | also don't have to worry about manually tracking error and loading 523 | states - Apollo does that for you too! We talked about caching 524 | briefly before, how does Apollo Client cache our data? 525 |

526 |
527 | 528 | Declarative data fetching 529 | 530 | 537 |
538 | 539 | 540 |

541 | The Apollo cache is awesome because it normalizes and stores your 542 | data for you. You don't have to mess around with normalizr or set 543 | any of this up yourself. This is a good thing because it turns out 544 | caching a graph is pretty hard. Luckily, we've already solved it 545 | with normalization. 546 |

547 |
548 | 549 | The Apollo cache<br /> normalizes<br />your data for you. 550 | 551 |
552 | 553 | 554 |

555 | Normalization is important because there are often multiple paths 556 | to access the same data with GraphQL. Here we have a sample UI for 557 | an article feed and an editor detail view. We have a query to get 558 | all of the articless for our feed. Let's say we want to make a 559 | change to our article's title with the updateArticle mutation on 560 | the detail page. Ideally, we want the data on the articles feed 561 | page to update with the new title as well. Keeping your data 562 | consistent and in sync is exactly why we normalize. 563 |

564 |
565 |
566 |
567 | 568 | 569 | UI from Ben Bate, dribbble 570 | 571 |
572 | 573 | How do<br />we keep our data consistent? 574 | 575 |
576 |
577 | 578 | 579 |

580 | So you understand what's going on under the hood, I'm going to try 581 | and demystify the Apollo cache's normalization process for you. 582 | First, we have our articles query. You'll also see we have our 583 | response payload, which comes back as the shape of the query, with 584 | the addition of one meta field. That meta field is called 585 | __typename and it's added automatically for you when the data 586 | comes back from the server. For each nested part of the query with 587 | a typename and id, we split them out into their own node in the 588 | cache in a flattened map. For the articles query, we can see that 589 | the cache value is a list of pointers. This is because the cache 590 | reconstructs each result from the flattened nodes in the cache. 591 |

592 |
593 | 594 | Normalization: 595 | 596 | 601 | 602 | Your cache key is __typename:id 603 | 604 |
605 | 606 | 607 |

608 | Don't worry if this sounds a little complicated, you can fully 609 | inspect your cache in Apollo DevTools to see what's going on. On 610 | the left, you can explore all of the nodes in the cache and see 611 | this flattened structure in action. 612 |

613 |
614 | 615 | Inspect the cache with Apollo DevTools 616 | 617 | 618 |
619 | 620 | 621 |

622 | One of the coolest features of Apollo Boost is that it includes 623 | one of our newest releases apollo-link-state, which allows you to 624 | manage your local data with Apollo Client and query it with 625 | GraphQL. link-state is truly pushing the boundaries of GraphQL 626 | outside the context of the server and it's super helpful if you're 627 | already using apollo for your remote data.. 628 |

629 |
630 | 631 | Query local data with<br />apollo-link-state! 🎉 632 | 633 |
634 | 635 | 636 |

637 | For you to understand just how exciting apollo-link-state is, we 638 | first must look at Apollo's past. Historically, users put all of 639 | their remote data into Apollo Client. This equated to about 80% of 640 | their data, but what about the other 20% for things like boolean 641 | flags, device API results. For that local data, users maintained a 642 | separate Redux store. Keeping this store in sync with changes in 643 | our remote data became tricky, especially as Apollo Client 2.0 644 | moved off of Redux last year. 645 |

646 |
647 | 648 | 2017: Separate stores, no cohesion 649 | 650 | 651 |
652 | 653 | 654 |

655 | With state link, we now have the ability to query all of our 656 | application's local and remote data with GraphQL. This has several 657 | advantages - first, GraphQL becomes a unified interface for all of 658 | our data. It doesn't matter if that data is coming from a REST 659 | endpoint or your local cache, you query all of it the exact same 660 | way. Also, the Apollo cache becomes our single source of truth. No 661 | more managing a separate store - now you can manage your local 662 | data the same way you manage your remote data. You can even add 663 | client only properties to objects you get back from the server. 664 | Let's see an example of how we would do that: 665 |

666 |
667 | 668 | 2018: One unified interface for all data 669 | 670 | 671 |
672 | 673 | 674 |

675 | In order to tell the Apollo Client network stack that we need to 676 | pull a field from the cache, we mark it with the @client 677 | directive. Here, you have a mixed query of both remote and local 678 | data. 679 |

680 |
681 | 682 | Mark local data with @client 683 | 684 | 691 |
692 | 693 | 694 |

695 | Now that we have our query, how do we resolve the data from the 696 | cache? That's where our resolvers come in. The API you use to 697 | create resolvers on the server with graphql-tools is the exact 698 | same API you use to create resolvers on the client with link 699 | state. You have your root value, any arguments, and also your 700 | context, which is where you pull the cache from in order to 701 | directly read and write to it. Another cool feature is that you 702 | can also return values from mutations, but you don't have to. This 703 | can be useful if you're creating a todo and want to get back the 704 | new todo's id for example. 705 |

706 |
707 | 714 |
715 | 716 | 717 |

718 | Next, you'll want to add your resolvers to Apollo Boost by passing 719 | them to the clientState config property. This is where all your 720 | configuration for apollo-link-state goes with the exception of the 721 | cache since we already pass that in for you. 722 |

723 |
724 | 725 | Add resolvers to Apollo Boost 726 | 727 | 734 |
735 | 736 | 737 |

738 | Now that our resolvers are all hooked up, how do we fire a local 739 | mutation in our app? Here, we're using the @client directive again 740 | to indicate that this mutation should resolve locally. 741 |

742 |
743 | 744 | Use the @client directive 745 | 746 | 753 |
754 | 755 | 756 |

757 | Once you have your GraphQL mutation, just include it in your 758 | mutation component as you normally would. The render prop for the 759 | mutation component exposes a mutate function as the first argument 760 | that you can use in your component to actually trigger the 761 | mutation. 762 |

763 |
764 | 765 | Update local data with a mutation 766 | 767 | 774 |
775 | 776 | 777 |

778 | I encourage you to experiment with client side state management in 779 | Apollo Boost. You may find that it simplifies your current Redux 780 | code considerably, as it did for Scott. It's still pretty early 781 | but we're really excited about the idea of managing our local data 782 | with GraphQL and can't wait to see how it evolves. Now we're going 783 | to talk about what that might look like... 784 |

785 |
786 |
787 | 797 | Switching from Redux to Apollo Link State simplified how I 798 | interact with my codebase significantly, by allowing me to 799 | integrate my local state with GraphQL & Apollo. 800 | 801 | 802 | Scott Tolinski, Level Up Tuts (@stolinski) 803 | 804 |
805 |
806 | 807 | 808 |

809 | You probably noticed I didn't mention client-side schemas at all 810 | when talking about state-link. That's because they are a tricky 811 | problem to solve. Since the graphql modules for validating a 812 | schema are extremely large, we can't just include them the way we 813 | would on the server. Recently, we got a working prototype of the 814 | client-side schema working. While this schema does not perform 815 | type validation, it does allow you to write a schema in schema 816 | definition language and perform introspection against its AST. 817 |

818 |
819 | 820 | Client-side schemas 821 | 822 | 828 |
829 | 830 | 831 |

832 | If we can perform introspection against a client-side schema, then 833 | we can integrate our client-side schema into our tooling!! We have 834 | an early prototype of client-side schema introspection stitched 835 | alongside our remote schema. This is huge! Now you can inspect 836 | your remote APIs alongside your client data without ever having to 837 | leave the devtools. This also brings a whole host of other 838 | features, such as autocomplete when running queries in GraphiQL 839 | and the ability to perform tracing analysis on our data. Right 840 | now, this feature is in alpha but we're hoping to release it very 841 | soon. 842 |
843 |
844 | If we can stitch together a client-schema with our remote-schema, 845 | we can stitch together anything. Imagine being able to explore 846 | your Gatsby schema for static data alongside your dynamic client 847 | data. GraphQL becomes a unified interface for accessing all of the 848 | data that flows through your application. 849 |

850 |
851 |
852 | 853 | 854 |

855 | Now that we have the ability to stitch together the schemas for 856 | multiple data sources, we'll be able to integrate introspection 857 | into a variety of different tools. The most obvious case is dev 858 | tools, but we also have a VS Code plugin in the works that will 859 | feature autocomplete as youre typing out your queries in your 860 | editor. We can also integrate our client data into Apollo Engine 861 | to analyze performance and tracing data. With GraphQL as our 862 | unified interface to all of our data, our tooling suddenly becomes 863 | more cohesive and unified across the stack. This is not only great 864 | for developer productivity, but it is the key in my opinion to 865 | reducing configuration fatigue. With GraphQL, you can teach 866 | someone how to build a resolver once and they'll automatically be 867 | able to apply their knowledge across the stack for both local and 868 | remote data. 869 |
870 |
871 | This is all so fresh and new, but I think we're on the cusp of 872 | something really exciting here that's revolutionizing how we 873 | interact with data in our React apps. I can't wait to see what the 874 | future holds for Apollo & GraphQL. 875 |

876 |
877 | 878 | With GraphQL, we can unify developer tooling across the stack. 879 | 880 |
881 | 882 | 883 |

884 | We're almost at the end so I wanted to mention that all the code 885 | samples I showed you today are featured in our newest example app 886 | called pupstagram. It's like instagram but for favoriting pictures 887 | of dogs, which is honestly all I use instagram for anyways. It's 888 | up on CodeSandbox and I'll tweet the link after my talk. 889 |

890 |
891 | 892 | 893 | 894 |
895 | 896 | 897 |

898 | Before I go, I want to leave you with one final message. 899 | Practicing empathy while building open source software is so so so 900 | important to make sure we're meeting the needs of a diverse base 901 | of users. I know it's not easy either. The longer you work on an 902 | open source project, the tougher it is to put yourselves in the 903 | shoes of someone who’s discovering the project for the first time. 904 | As library authors at Apollo, we want to work harder at this. Our 905 | main goal is to make GraphQL more approachable for everyone. For 906 | us, building Apollo Boost was an awesome chance for us to focus on 907 | empathy, and we’re really happy with the creative solutions that 908 | came from it. I challenge all of you to take a step back and 909 | practice empathy building your software too. 910 |

911 |
912 | 913 | Empathy builds<br />better software. 914 | 915 |
916 | 917 | 923 | @peggyrayzis 924 | 925 | 926 |
927 | ); 928 | } 929 | } 930 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | } else { 39 | // Is not local host. Just register service worker 40 | registerValidSW(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | --------------------------------------------------------------------------------