back class Project(): #: The project title title = fields. String( required =True ) projects?fields=title [{ "type": "project", "id": "1", "attributes ": { "title": "My Project " } }]
/ Apollo • Relay reconciles queries into optimal requests to a single GraphQL runtime • The GraphQL Runtime resolves queries by requesting to backend services
back type Project = { // the project title title: string } q.many(Project , { fields: { title : true, }, }) List [ Project { title: "My Project " }] Recall
{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
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
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} ) }, }), })
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
be found in src/core/api/resources.js Resources Exercise import {ProjectType} from './schema' export const Project = createApiResource( ProjectType, 'projects' );
({ 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
queries: { user: query.single(User, { params: vars => ({ id: vars.id, }), fields: { name: true, picture: { url: true, }, }, }), }, }); function MyComponent(props) { return <div>{props.user.name}</div>; } export default container(MyComponent); // example usage <ContainedComponent id="5" /> 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
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
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 { ... } }) }, });
queries: { user: query.single(User, { params: vars => ({ id: vars.id, }), fields: { name: true, picture: { url: true, }, }, }), }, }); function MyComponent(props) { return <div>{props.user.name}</div>; } export default container(MyComponent); // example usage <ContainedComponent id="5" /> 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
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.