Skip to content

Instantly share code, notes, and snippets.

@danielkcz
Last active March 7, 2018 13:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danielkcz/655d562e7fd72fd5fa791042fda008c7 to your computer and use it in GitHub Desktop.
Save danielkcz/655d562e7fd72fd5fa791042fda008c7 to your computer and use it in GitHub Desktop.
Apollo GraphQL with render props
import { ApolloQueryResult } from 'apollo-client'
import { DocumentNode } from 'graphql'
import React from 'react'
import { graphql } from 'react-apollo'
import { MutationFunc, MutationOpts } from 'react-apollo/types'
interface IQueryProps<TResult, TVariables = {}> {
render(
execute: (variables?: Partial<TVariables>) => Promise<TResult>,
executionOptions?: MutationOpts<TVariables>,
): ReactNode
variables?: Partial<TVariables>
}
type ChildProps<TResult> = {
mutate: MutationFunc<TResult>
}
export function buildMutation<TResult, TVariables = {}>(
document: DocumentNode,
getOptions: (
variables: Partial<TVariables>,
) => MutationOpts<TVariables> = () => ({}),
) {
const Mutation: React.SFC<
ChildProps<TResult> & IQueryProps<TResult, TVariables>
> = ({ mutate, render }) => {
async function execute(
variables: Partial<TVariables> = {},
executionOptions: MutationOpts<TVariables> = {},
) {
const result: ApolloQueryResult<TResult> = await mutate({
variables,
...executionOptions,
})
return result.data
}
return render(execute)
}
const options = (props: IQueryProps<TResult, TVariables>) => {
return {
variables: props.variables,
...getOptions(props.variables || {}),
}
}
const wrapper = graphql<TResult, IQueryProps<TResult, TVariables>>(document, {
options,
})
return wrapper(Mutation)
}
import { DocumentNode } from 'graphql'
import React from 'react'
import { graphql } from 'react-apollo'
import { QueryProps } from 'react-apollo/types'
import { Loading } from '../atoms/Loading'
interface IQueryProps<TResult, TVariables = {}> {
render?(data: TResult): ReactNode
renderList?(data: TResult): ReactNode[]
renderLoading?(): ReactNode
skip?: boolean
variables?: TVariables
}
type ChildProps<TResult> = {
data: QueryProps & TResult
}
export function buildQuery<TResult, TVariables = {}>(document: DocumentNode) {
const Query: React.SFC<
ChildProps<TResult> & IQueryProps<TResult, TVariables>
> = ({
data,
render,
renderList,
renderLoading = () => <Loading />,
skip = false,
}) => {
if (skip === true || !data) {
return null
}
if (data.loading) {
return renderLoading()
}
const result: TResult = data
if (renderList) {
return <>{renderList(result)}</>
}
if (render) {
return render(result)
}
return null
}
const options = (props: IQueryProps<TResult, TVariables>) => {
return {
skip: props.skip,
variables: props.variables,
}
}
const wrapper = graphql<TResult, IQueryProps<TResult, TVariables>>(document, {
options,
})
return wrapper(Query)
}
@danielkcz
Copy link
Author

danielkcz commented Mar 1, 2018

TypeScript is obviously optional here, but it helps a big time. For example, I have a file with a query like this.

import gql from 'graphql-tag'

import { GLogoutWidget } from '../../typings/types'
import { buildQuery } from '../graph/buildQuery'

export const LogoutWidgetQuery = buildQuery<
  GLogoutWidget.Query,
  GLogoutWidget.Variables
>(gql`
  query GLogoutWidget($id: ID!) {
    user(id: $id) {
      id
      firstName
      lastName
    }
  }
`)

I am using graphql-code-generator and resulting typescript definition may look like this. I just write a query first (with help of snippets), execute generator and then I have a fully typed experience when working with the data.

export namespace GLogoutWidget {
  export type Variables = {
    id: string
  }

  export type Query = {
    user: User
  }

  export type User = {
    id: string
    firstName?: string | null
    lastName?: string | null
  }
}

Finally I can use that Query component simply like this.

export const LogoutWidget: React.SFC = ({ userId }) => (
  <LogoutWidgetQuery
    variables={{ id: userId }}
    render={({ user }) => (
      <Link to="/logout">{`${user.firstName} ${user.lastName}`}</Link>
    )}
  />
)

@danielkcz
Copy link
Author

Another example of a use for mutations. I am aware it's still a bit simplified as I building it based on my needs, but it's working quite well.

function VisibilityToggleButton({ ware }: { ware: GWareList.Wares }) {
  return (
    <WareListVisibilityMutation
      variables={{
        wareId: ware.id,
      }}
      render={updateVisibility => (
        <VisibilityButton
          isVisible={ware.enabled}
          onClick={() => updateVisibility({
            visibility: !ware.enabled,
          })}
        />
      )}
    />
  )
}

Defining such mutation looks very similar to queries.

import { buildMutation } from '@graph/buildMutation'
import { GWareListVisibility } from '@typings/types'
import gql from 'graphql-tag'

export const WareListVisibilityMutation = buildMutation<
  GWareListVisibility.Mutation,
  GWareListVisibility.Variables
>(gql`
  mutation GWareListVisibility($wareId: ID!, $visibility: Boolean!) {
    updateWare(id: $wareId, ware: { enabled: $visibility }) {
      id
      enabled
    }
  }
`)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment