An official Apollo React guide illustrates an example on how to load a query and show data this way:
import gql from "graphql-tag";
import { Query } from "react-apollo";
const GET_DOGS = gql`
{
dogs {
id
breed
}
}
`;
const Dogs = () => (
<Query query={GET_DOGS}>
{({ loading, error, data }) => {
if (loading) return "Loading...";
if (error) return `Error! ${error.message}`;
return (
<select name="dog" onChange={onDogSelected}>
{data.dogs.map(dog => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
</select>
);
}}
</Query>
);
This is nice for the start, but I don't want to copy over all the handling of errors each time in each usage of Query, also I want to write more interesting loader that just text, so having this duplicated code in all the places seems as boilerplate.
So I DRY it with a QueryComponent, so the above example would become:
class Dogs extends QueryComponent {
query(){
return `
{
dogs {
id
breed
}
}
`
}
content(){
const data = this.state.data
return (
<select name="dog">
{data.dogs.map(dog => (
<option key={dog.id} value={dog.breed}>
{dog.breed}
</option>
))}
</select>
);
}
}
Now I can configure nice loader, and have it same across the site, and make handling errors centralized, and I don't need to import gql
every time.
Code for QueryCompoment would be such:
import React from "react";
import PropTypes from "prop-types";
import gql from "graphql-tag";
import Loader from "./Loader";
class QueryComponent extends React.Component {
constructor(props) {
super(props);
this.state = { loading: true, error: null };
}
componentDidMount() {
this.loadQuery({ variables: this.queryVariables() });
}
query() {
throw "query() should be redefined on extended component";
}
queryVariables() {
return {};
}
loadQuery({ variables }) {
const client = this.context.client;
const query = gql(this.query());
try {
client
.watchQuery({ query, variables }) // performs query as well
.subscribe(
({ data, loading, error, errors, networkStatus }) => {
// window.console.log("query subscribe fired")
if (loading) return;
if (error) {
this.queryFailed({ error, errors, networkStatus });
} else {
this.queryLoaded(data);
}
if (this.state.loading) {
this.setState({ loading: false });
}
},
error => {
console.log("Query Failed");
console.dir(error);
this.queryFailed({ error });
}
);
} catch (error) {
console.log("query error");
console.log(error);
this.setState({
loading: false,
error
});
}
}
loading() {
return <Loader />;
}
queryLoaded(data) {
this.setState(data);
}
queryFailed({ error }) {
this.setState({
loading: false,
error
});
}
handleError(error) {
const message = error.message;
return <div>{message}</div>;
}
render() {
const { loading, error } = this.state;
if (loading) return this.loading();
if (error) return this.handleError(error);
return this.content();
}
}
QueryComponent.contextTypes = {
client: PropTypes.object
};
export default QueryComponent;
I simplified code a bit for the sake of clarity.
You can check more advanced working example, that also passed query variables, in CodeSandbox:
Top comments (1)
Why u dont use hooks?