Last active
December 8, 2017 16:19
-
-
Save derek-duncan/fde862206e96154af3c1e67c64788e04 to your computer and use it in GitHub Desktop.
Typescript example on how to strongly type your Apollo graphql containers. These containers are completely reusable for any component, with full TS typings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react"; | |
import * as ReactDOM from "react-dom"; | |
import gql from "graphql-tag"; | |
import { graphql } from "react-apollo"; | |
/** | |
* Using intersection types, we can allow our `graphql` injected props | |
* to be defined in our `Props` type. | |
*/ | |
type LightSwitchProps = { | |
defaultOn?: boolean; | |
activeColor: "yellow" | "white"; | |
} & WithDataInputProps & | |
WithDataInjectProps & | |
WithToggleInjectProps; | |
const dataQuery = gql` | |
query LightSwitch($id: String!) { | |
lightSwitch(id: $id) { | |
id | |
isActive | |
} | |
} | |
`; | |
type WithDataResponse = { | |
lightSwitch: { | |
id: string; | |
isActive: boolean; | |
}; | |
}; | |
/** | |
* Input props are available to graphql.options and ownProps. | |
*/ | |
type WithDataInputProps = { | |
id: string; | |
}; | |
/** | |
* Injected props are what's returned from graphql.props. | |
*/ | |
type WithDataInjectProps = { | |
data?: { | |
isActive: boolean; | |
}; | |
}; | |
/** | |
* Query Example. | |
* The `Props` generic represents the props from our Component. We want our | |
* `withData` wrapper to type check for both the input and injected props. | |
* So, it accepts a `Component` that requires the input and injected | |
* props, and it returns a `graphql` wrapped component that injects those | |
* props and passes the rest through. | |
*/ | |
function withData<Props extends WithDataInputProps>( | |
Component: React.ComponentType<Props & WithDataInjectProps> | |
): React.ComponentType<Props> { | |
return graphql<WithDataResponse, Props, Props & WithDataInjectProps>(dataQuery, { | |
options: ({ id }) => ({ | |
variables: { id } | |
}), | |
props: ({ data }) => { | |
if (!data) return; | |
return { | |
data: { | |
isActive: data.lightSwitch.isActive | |
} | |
}; | |
} | |
})(Component); | |
} | |
const toggleQuery = gql` | |
mutation Toggle($id: String!) { | |
toggleLightSwitch(id: $id) { | |
id | |
isActive | |
} | |
} | |
`; | |
type WithToggleResponse = { | |
toggleLightSwitch: { | |
id: string; | |
isActive: boolean; | |
}; | |
}; | |
type WithToggleInjectProps = { | |
toggle?: (id: string) => Promise<WithToggleResponse>; | |
}; | |
/** | |
* Mutation Example. | |
*/ | |
function withToggle<Props>( | |
Component: React.ComponentType<Props & WithToggleInjectProps> | |
): React.ComponentType<Props> { | |
return graphql<WithToggleResponse, Props, Props & WithToggleInjectProps>( | |
toggleQuery, | |
{ | |
props: ({ mutate }) => { | |
if (!mutate) return; | |
return { | |
toggle: id => | |
mutate({ variables: { id } }).then(response => response.data) | |
}; | |
} | |
} | |
)(Component); | |
} | |
class LightSwitch extends React.Component<LightSwitchProps> { | |
render() { | |
const { data, toggle, id } = this.props; | |
if (!data || !toggle) return null; | |
return ( | |
<div> | |
<button onClick={() => toggle(id)}> | |
{data.isActive ? "Turn off" : "Turn on"} | |
</button> | |
</div> | |
); | |
} | |
} | |
const WrappedLightSwitch = withData(withToggle(LightSwitch)); | |
ReactDOM.render( | |
/** | |
* Notice that the props in `WithDataInjectProps` and `WithToggleInjectProps` | |
* are not required. Our `withData` and `withToggle` took care of injecting | |
* them. The original props (`activeColor` & `defaultOn`) are still required | |
* though. | |
*/ | |
<WrappedLightSwitch activeColor="yellow" defaultOn={false} id="1" />, | |
document.querySelector("#root") | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment