graphql-react
Version:
A GraphQL client for React using modern context and hooks APIs that’s lightweight (< 4 kB) but powerful; the first Relay and Apollo alternative with server side rendering.
152 lines (142 loc) • 6.75 kB
JavaScript
const ERROR_CODE_FETCH_ERROR = "FETCH_ERROR";
const ERROR_CODE_RESPONSE_HTTP_STATUS = "RESPONSE_HTTP_STATUS";
const ERROR_CODE_RESPONSE_JSON_PARSE_ERROR = "RESPONSE_JSON_PARSE_ERROR";
const ERROR_CODE_RESPONSE_MALFORMED = "RESPONSE_MALFORMED";
/**
* Fetches a GraphQL operation, always resolving a
* [GraphQL result]{@link GraphQLResult} suitable for use as a
* [cache value]{@link CacheValue}, even if there are errors. Loading errors
* are added to the [GraphQL result]{@link GraphQLResult} `errors` property, and
* have an `extensions` property containing `client: true`, along with `code`
* and sometimes error-specific properties:
*
* | Error code | Reasons | Error specific properties |
* | :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- |
* | `FETCH_ERROR` | Fetch error, e.g. the `fetch` global isn’t defined, or the network is offline. | `fetchErrorMessage` (string). |
* | `RESPONSE_HTTP_STATUS` | Response HTTP status code is in the error range. | `statusCode` (number), `statusText` (string). |
* | `RESPONSE_JSON_PARSE_ERROR` | Response JSON parse error. | `jsonParseErrorMessage` (string). |
* | `RESPONSE_MALFORMED` | Response JSON isn’t an object, is missing an `errors` or `data` property, the `errors` property isn’t an array, or the `data` property isn’t an object or `null`. | |
* @kind function
* @name fetchGraphQL
* @param {string} fetchUri Fetch URI for the GraphQL API.
* @param {FetchOptions} fetchOptions Fetch options.
* @returns {Promise<GraphQLResult>} Resolves a result suitable for use as a [cache value]{@link CacheValue}. Shouldn’t reject.
* @example <caption>How to import.</caption>
* ```js
* import fetchGraphQL from "graphql-react/fetchGraphQL.mjs";
* ```
*/
export default function fetchGraphQL(fetchUri, fetchOptions) {
const result = { errors: [] };
const fetcher =
typeof fetch === "function"
? fetch
: () => Promise.reject(new TypeError("Global `fetch` API unavailable."));
return fetcher(fetchUri, fetchOptions)
.then(
// Fetch ok.
(response) => {
// Allow the response to be read in the cache value, but prevent it from
// serializing to JSON when sending SSR cache to the client for
// hydration.
Object.defineProperty(result, "response", { value: response });
if (!response.ok)
result.errors.push({
message: `HTTP ${response.status} status.`,
extensions: {
client: true,
code: ERROR_CODE_RESPONSE_HTTP_STATUS,
statusCode: response.status,
statusText: response.statusText,
},
});
return response.json().then(
// Response JSON parse ok.
(json) => {
// It’s not safe to assume that the response data format conforms to
// the GraphQL spec.
// https://spec.graphql.org/June2018/#sec-Response-Format
if (typeof json !== "object" || !json || Array.isArray(json))
result.errors.push({
message: "Response JSON isn’t an object.",
extensions: {
client: true,
code: ERROR_CODE_RESPONSE_MALFORMED,
},
});
else {
const hasErrors = "errors" in json;
const hasData = "data" in json;
if (!hasErrors && !hasData)
result.errors.push({
message:
"Response JSON is missing an `errors` or `data` property.",
extensions: {
client: true,
code: ERROR_CODE_RESPONSE_MALFORMED,
},
});
else {
// The `errors` field should be either an array, or not set.
// https://spec.graphql.org/June2018/#sec-Errors
if (hasErrors)
if (!Array.isArray(json.errors))
result.errors.push({
message:
"Response JSON `errors` property isn’t an array.",
extensions: {
client: true,
code: ERROR_CODE_RESPONSE_MALFORMED,
},
});
else result.errors.push(...json.errors);
// The `data` field should be either an object, null, or not set.
// https://spec.graphql.org/June2018/#sec-Data
if (hasData)
if (
// Note that `null` is an object.
typeof json.data !== "object" ||
Array.isArray(json.data)
)
result.errors.push({
message:
"Response JSON `data` property isn’t an object or null.",
extensions: {
client: true,
code: ERROR_CODE_RESPONSE_MALFORMED,
},
});
else result.data = json.data;
}
}
},
// Response JSON parse error.
({ message }) => {
result.errors.push({
message: "Response JSON parse error.",
extensions: {
client: true,
code: ERROR_CODE_RESPONSE_JSON_PARSE_ERROR,
jsonParseErrorMessage: message,
},
});
}
);
},
// Fetch error.
({ message }) => {
result.errors.push({
message: "Fetch error.",
extensions: {
client: true,
code: ERROR_CODE_FETCH_ERROR,
fetchErrorMessage: message,
},
});
}
)
.then(() => {
if (!result.errors.length) delete result.errors;
return result;
});
}