Slide 1

Slide 1 text

1 Recall

Slide 2

Slide 2 text

2 Table of Contents 01 02 03 04 Imperative Data Fetching Data management Why? Declarative querying GraphQL JSON-API Recall What? How? Exercise! Internals Glance Schema Resources Queries Containers Tips and Tricks Reconciler Record Store Query Expander

Slide 3

Slide 3 text

3 https://github.com/kalohq/frontend/pull/2208 Check out the new docs!

Slide 4

Slide 4 text

4 Why?

Slide 5

Slide 5 text

5 Imperative Data Fetching :( Why?

Slide 6

Slide 6 text

6 Imperative data fetching

Slide 7

Slide 7 text

7 Data Management :( Why?

Slide 8

Slide 8 text

8 Caching Minimising Network Calls Persistance Overfetching Optimal Denormalisation UX Concerns

Slide 9

Slide 9 text

9 What?

Slide 10

Slide 10 text

10 Declarative Data Fetching What?

Slide 11

Slide 11 text

11 Imperative data fetching Declarative data fetching

Slide 12

Slide 12 text

12 GraphQL What?

Slide 13

Slide 13 text

13 “ GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. ” – The GraphQL Docs

Slide 14

Slide 14 text

14

Slide 15

Slide 15 text

15 BE Services GraphQL Runtime GraphQL Typical GraphQL Stack Relay / Apollo

Slide 16

Slide 16 text

16 Imperative data fetching Declarative data fetching

Slide 17

Slide 17 text

17 JSON-API What?

Slide 18

Slide 18 text

18 “ A specification for building APIs in JSON ” – The JSON-API Docs

Slide 19

Slide 19 text

19 Describe your resources Request what you want Get data back class Project(): #: The project title title = fields. String( required =True ) projects?fields=title [{ "type": "project", "id": "1", "attributes ": { "title": "My Project " } }]

Slide 20

Slide 20 text

20 Fields = Relationships + Attributes Explicit Shape Fetching = Sparse-fieldsets + Includes Parameters = Query Parameters Could be treated as a GraphQL synonym (without runtime)

Slide 21

Slide 21 text

21 Recall What?

Slide 22

Slide 22 text

22 “ Recall allows you to fetch data from anywhere in your React application in a declarative fashion ” – The Recall Docs

Slide 23

Slide 23 text

23 BE Services GraphQL Runtime GraphQL Typical GraphQL Stack Relay / Apollo ● Relay reconciles queries into optimal requests to a single GraphQL runtime ● The GraphQL Runtime resolves queries by requesting to backend services

Slide 24

Slide 24 text

24 BE Services GraphQL Runtime GraphQL Recall’s responsibilities overlaying the traditional GraphQL stack Recall Relay / Apollo

Slide 25

Slide 25 text

25 BE Services JSON-API Recall Recall is responsible for reconciling queries into numerous optimal requests a JSON-API backend.

Slide 26

Slide 26 text

26 Describe your resources Request what you want Get data back type Project = { // the project title title: string } q.many(Project , { fields: { title : true, }, }) List [ Project { title: "My Project " }] Recall

Slide 27

Slide 27 text

27 How? cd frontend && git checkout recall-workshop

Slide 28

Slide 28 text

28 Schema How?

Slide 29

Slide 29 text

29 import { GraphQLString, GraphQLBoolean, GraphQLObjectType, } from 'graphql/type'; import {connectionDefinition} from 'utils/schema/connections'; export const UserType = new GraphQLObjectType({ name: 'user', fields: () => ({ name: {type: GraphQLString}, picture: {type: connectionDefinition(FileType)}, is_verified: {type: GraphQLBoolean}, }), }); export const FileType = new GraphQLObjectType({ name: 'file', fields: () => ({ url: {type: GraphQLString}, }), }); We use the GraphQL type system powered by graphql-js library to write our schema. Every resource in our domain is described as an Object Type. The name is the resource type name. Fields can be any scalar type as well as connections (relationships) to other fields. You can have {many: true} connections. Schema

Slide 30

Slide 30 text

30 Expand on the schema found in src/core/api/schema.js and add definitions for Projects. This should match our lws schemas. It should at least define the “title”, “status”, “end date”, “number tasks” and “number of pending task” fields as well as relationships to freelancers and team members. Hint: Match up with the backend schemas https://github.com/kalohq/lws/blob/develop/lws/s ervices/projects/schemas.py Schema Exercise

Slide 31

Slide 31 text

31 Expand on the schema found in src/core/api/schema.js and add definitions for Projects. This should match our lws schemas. It should at least define the “title”, “status”, “end date”, “number tasks” and “number of pending task” fields as well as relationships to freelancers and team members. Hint: Match up with the backend schemas https://github.com/kalohq/lws/blob/develop/lws/s ervices/projects/schemas.py Schema Exercise export const ProjectType = new GraphQLObjectType({ name: 'project', fields: () => ({ title: {type: GraphQLString}, end_date: {type: GraphQLString}, status: {type: ProjectStatusEnum}, num_contributions: {type: GraphQLInt}, num_active_contributions: {type: GraphQLInt}, suppliers: { type: connectionDefinition( SupplierType, {many: true} ) }, users: { type: connectionDefinition( UserType, {many: true} ) }, }), })

Slide 32

Slide 32 text

32 Resources (as Records) How?

Slide 33

Slide 33 text

33 import {createApiResource} from 'api/utils'; import { UserType, FileType } from './schema'; export const User = createApiResource( UserType, 'Users' ); export const File = createApiResource( FileType, 'Files' ); // example usage const myUser = new User({ name: 'Pete', picture: new File({ url: 'http://kalohq.com/picture.gif', }), }) We convert our types into usable Immutable Records which we can use to represent data. These records also hold important metadata about our resources which is vital for recall to function. Most notably they store the root uri on the backend API for accessing this resource. These records will be used to construct the resultant values of your queries later as well as allowing you to create and store data in memory. Resources

Slide 34

Slide 34 text

34 Create resources for your newly defined types. These can be found in src/core/api/resources.js Resources Exercise

Slide 35

Slide 35 text

35 Create resources for your newly defined types. These can be found in src/core/api/resources.js Resources Exercise import {ProjectType} from './schema' export const Project = createApiResource( ProjectType, 'projects' );

Slide 36

Slide 36 text

36 Queries How?

Slide 37

Slide 37 text

37 import {query} from 'api/recall'; query.single(User, { params: vars => ({ id: vars.id, }), fields: { name: true, picture: { url: true, }, }, }) query.many(User, { params: () => ({ page: { offset: 0, limit: 5, }, }), fields: {}, }) To start fetching data we first need to write Query Declarations. These describe what resource to query, parameters refining how to fetch the resource as well as the exact fields we expect to be provided. Since our schema is a cyclic graph of our entire domain our fields can be a tree that dives as deeply as we need to for our current UI. Our parameters can describe pretty much anything as they get mapped directly to query parameters on our network requests. Queries

Slide 38

Slide 38 text

38 We want to render the below UI. Write a query in any new file which describes the data requirements. Queries Exercise

Slide 39

Slide 39 text

39 We want to render the below UI. Write a query which describes the data requirements. Queries Exercise q.many(Project , { params: vars => ({ filter : { archived : vars.query.archived , status : vars.query.status, supplier_id : vars.params.supplierId || vars.query.supplier , q_title : vars.debouncedQuery .q, combined_members : vars.query.members, }, sort: vars.query.sort, page: { limit : vars.query.page * PAGE_SIZE , offset : 0, }, }), fields: { id: true, status : true, title : true, end_date : true, users : { limit : 3, edges : { name : true, picture : { url : true, }, }, }, suppliers : { limit : 3, edges : { name : true, email : true, picture : { url : true, }, }, }, num_contributions : true,

Slide 40

Slide 40 text

40 Containers How?

Slide 41

Slide 41 text

41 import {query, createContainer} from 'api/recall'; const container = createContainer({ queries: { user: query.single(User, { params: vars => ({ id: vars.id, }), fields: { name: true, picture: { url: true, }, }, }), }, }); function MyComponent(props) { return
{props.user.name}
; } export default container(MyComponent); // example usage Recall is a React-first library. It provides a higher-order-component (createContainer) to allow you to define data requirements for components in-place. Note the query declaration we’ve used here is the same as before but we’ve put inside of a queries object and passed that to createContainer. The keys are important here as that is the name of the prop that your data will be passed to your component as. The vars of a query declaration in a container become the props being passed into the component. Containers

Slide 42

Slide 42 text

42 1. Using your previous query declaration, provide a container around the X component. Now when you visit your application you should see the your UI rendering with live data! 2. We have already hooked up various filters which store state in the client url (query parameters) and then pass them through via props to your container. Try hooking these filters up to your query parameters. 3. You have now used Recall to fulfil the requirements of this UI! Containers Exercise

Slide 43

Slide 43 text

43 1. Using your previous query declaration, provide a container around the X component. Now when you visit your application you should see the your UI rendering with live data! This lives in src/core/components/projects-index-page/p rojects-index-page.js 2. We have already hooked up various filters which store state in the client url (query parameters) and then pass them through via props to your container. Try hooking these filters up to your query parameters. 3. You have now used Recall to fulfil the requirements of this UI! Containers Exercise const contain = createContainer({ queries: { projects: q.many(Project, { params: props => ({ ... }), fields { ... } }) }, });

Slide 44

Slide 44 text

44 import {query, createContainer} from 'api/recall'; const container = createContainer({ queries: { user: query.single(User, { params: vars => ({ id: vars.id, }), fields: { name: true, picture: { url: true, }, }, }), }, }); function MyComponent(props) { return
{props.user.name}
; } export default container(MyComponent); // example usage Queries are re-evaluated as props input changes. If the query changes then recall does the work to fetch new data and pass that into your component. Ie. You only DESCRIBE the data requirements. Recall provides. A container will provide another prop called “queries” which describes the state of each of your queries. You have strong guarantees about the data being passed into your components. A few final notes

Slide 45

Slide 45 text

45 Tips, Tricks & Answers How?

Slide 46

Slide 46 text

46 “How can I optimise queries?” You can mark part of a query as “deferred” which tells Recall you don’t mind if it comes back later.

Slide 47

Slide 47 text

47 “How can I optimise containers?” You can mark queries as conditional by defining an “active” property on the query.

Slide 48

Slide 48 text

48 Where should my containers go? As high in the tree as you can put them, avoiding adding to many branches.

Slide 49

Slide 49 text

49 “How does Recall cache things?” You should not care. Recall will always give you the latest value possible. Know the states. Invalidate as you update. It’s either ready or it’s not. Can also be working.

Slide 50

Slide 50 text

50 Internals Glance

Slide 51

Slide 51 text

51 Internals Glance BE Services Network Container Reconciler Store Query Expander JSON-API Query expander acts as a facade between containers and the store. Pulls query state. Reconciler, running on a tick, yields optimised network requests from all live queries. Store responds to network updates, new query states are resolved and the reconciler may move to the next step of a query next tick.

Slide 52

Slide 52 text

52 The End https://github.com/kalohq/frontend/pull/2208 Don’t forget to check out and refer to the new docs!