With Relay
Using pg_grapqhl with Relay.
pg_graphql implements the GraphQL Global Object Identification Specification (Node
interface) and the GraphQL Cursor Connections Specification to be compatible with Relay.
Relay Setup
Pre-requisites
Follow the Relay Installation Guide.
Configuring the Relay Compiler
Modify your relay.config.js
file to reflect the following:
1234567891011121314151617181920module.exports = { // standard relay config options src: './src', language: 'typescript', schema: './data/schema.graphql', exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**'], // pg_graphql specific options schemaConfig: { nodeInterfaceIdField: 'nodeId', nodeInterfaceIdVariableName: 'nodeId', }, customScalarTypes: { UUID: 'string', Datetime: 'string', JSON: 'string', BigInt: 'string', BigFloat: 'string', Opaque: 'any', },}
schemaConfig
tells the Relay compiler where to find thenodeId
field on thenode
interfacecustomScalarTypes
will improve Relay's type emission
For Relay versions older than v16.2.0, it should be named customScalars
instead.
Configuring your Relay Environment
This example uses Supabase for the GraphQL server, but pg_graphql can be used independently.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354import { Environment, FetchFunction, Network, RecordSource, Store,} from 'relay-runtime'import supabase, { SUPABASE_ANON_KEY, SUPABASE_URL } from './supabase'const fetchQuery: FetchFunction = async (operation, variables) => { const { data: { session }, } = await supabase.auth.getSession() const response = await fetch(`${SUPABASE_URL}/graphql/v1`, { method: 'POST', headers: { 'Content-Type': 'application/json', apikey: SUPABASE_ANON_KEY, Authorization: `Bearer ${session?.access_token ?? SUPABASE_ANON_KEY}`, }, body: JSON.stringify({ query: operation.text, variables, }), }) return await response.json()}const network = Network.create(fetchQuery)const store = new Store(new RecordSource())const environment = new Environment({ network, store, getDataID: (node) => node.nodeId, missingFieldHandlers: [ { handle(field, _record, argValues) { if (field.name === 'node' && 'nodeId' in argValues) { // If field is node(nodeId: $nodeId), look up the record by the value of $nodeId return argValues.nodeId } return undefined }, kind: 'linked', }, ],})export default environment
getDataID
is the most important option to add, as it tells Relay how to store data correctly in the cache.missingFieldHandlers
is optional in this example but helps with Rendering Partially Cached Data.
Pagination
Say you are working on a Todo app and want to add pagination. You can use @connection
and @prependNode
to do this.
Fragment passed to usePaginationFragment()
123456789101112131415161718192021fragment TodoList_query on Query@argumentDefinitions( cursor: { type: "Cursor" } count: { type: "Int", defaultValue: 20 })@refetchable(queryName: "TodoListPaginationQuery") { todosCollection(after: $cursor, first: $count) @connection(key: "TodoList_query_todosCollection") { pageInfo { hasNextPage endCursor } edges { cursor node { nodeId ...TodoItem_todos } } }}
Mutation to create a new Todo
12345678mutation TodoCreateMutation($input: TodosInsertInput!, $connections: [ID!]!) { insertIntoTodosCollection(objects: [$input]) { affectedCount records @prependNode(connections: $connections, edgeTypeName: "TodosEdge") { ...TodoItem_todos } }}
Code to call the mutation
1234567891011121314151617181920import { ConnectionHandler, graphql, useMutation } from 'react-relay'// inside a React componentconst [todoCreateMutate, isMutationInFlight] = useMutation<TodoCreateMutation>(CreateTodoMutation)// inside your create todo functionconst connectionID = ConnectionHandler.getConnectionID( 'root', 'TodoList_query_todosCollection')todoCreateMutate({ variables: { input: { // ...new todo data }, connections: [connectionID], },})