Skip to content

Instantly share code, notes, and snippets.

@derek-duncan
Last active December 8, 2017 16:19
Show Gist options
  • Save derek-duncan/fde862206e96154af3c1e67c64788e04 to your computer and use it in GitHub Desktop.
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.
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