UNPKG

graphql-hooks-with-get-support

Version:
561 lines (463 loc) 15.8 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = _interopDefault(require('react')); var extractFiles = require('extract-files'); var deepEqual = _interopDefault(require('dequal')); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } var ClientContext = React.createContext(); ClientContext.displayName = 'ClientContext'; var GraphQLClient = /*#__PURE__*/ function () { function GraphQLClient(config) { if (config === void 0) { config = {}; } // validate config if (!config.url) { throw new Error('GraphQLClient: config.url is required'); } if (config.fetch && typeof config.fetch !== 'function') { throw new Error('GraphQLClient: config.fetch must be a function'); } if (!config.fetch && !fetch) { throw new Error('GraphQLClient: fetch must be polyfilled or passed in new GraphQLClient({ fetch })'); } if (config.ssrMode && !config.cache) { throw new Error('GraphQLClient: config.cache is required when in ssrMode'); } this.cache = config.cache; this.headers = config.headers || {}; this.ssrMode = config.ssrMode; this.ssrPromises = []; this.url = config.url; this.fetch = config.fetch || fetch.bind(); this.fetchOptions = config.fetchOptions || {}; this.logErrors = config.logErrors !== undefined ? config.logErrors : true; this.onError = config.onError; } var _proto = GraphQLClient.prototype; _proto.setHeader = function setHeader(key, value) { this.headers[key] = value; return this; }; _proto.setHeaders = function setHeaders(headers) { this.headers = headers; return this; }; _proto.removeHeader = function removeHeader(key) { delete this.headers[key]; return this; } /* eslint-disable no-console */ ; _proto.logErrorResult = function logErrorResult(_ref) { var result = _ref.result, operation = _ref.operation; if (this.onError) { return this.onError({ result: result, operation: operation }); } console.error('GraphQL Hooks Error'); console.groupCollapsed('---> Full Error Details'); console.groupCollapsed('Operation:'); console.log(operation); console.groupEnd(); if (result.fetchError) { console.groupCollapsed('FETCH ERROR:'); console.log(result.fetchError); console.groupEnd(); } if (result.httpError) { console.groupCollapsed('HTTP ERROR:'); console.log(result.httpError); console.groupEnd(); } if (result.graphQLErrors && result.graphQLErrors.length > 0) { console.groupCollapsed('GRAPHQL ERROR:'); result.graphQLErrors.forEach(function (err) { return console.log(err); }); console.groupEnd(); } console.groupEnd(); } /* eslint-enable no-console */ ; _proto.generateResult = function generateResult(_ref2) { var fetchError = _ref2.fetchError, httpError = _ref2.httpError, graphQLErrors = _ref2.graphQLErrors, data = _ref2.data; var error = !!(graphQLErrors && graphQLErrors.length > 0 || fetchError || httpError); return { error: error, fetchError: fetchError, httpError: httpError, graphQLErrors: graphQLErrors, data: data }; }; _proto.getCacheKey = function getCacheKey(operation, options) { if (options === void 0) { options = {}; } var fetchOptions = _objectSpread2({}, this.fetchOptions, {}, options.fetchOptionsOverrides); return { operation: operation, fetchOptions: fetchOptions }; }; _proto.getCache = function getCache(cacheKey) { var cacheHit = this.cache ? this.cache.get(cacheKey) : null; if (cacheHit) { return cacheHit; } }; _proto.saveCache = function saveCache(cacheKey, value) { if (this.cache) { this.cache.set(cacheKey, value); } } // Kudos to Jayden Seric (@jaydenseric) for this piece of code. // See original source: https://github.com/jaydenseric/graphql-react/blob/82d576b5fe6664c4a01cd928d79f33ddc3f7bbfd/src/universal/graphqlFetchOptions.mjs. ; _proto.getFetchOptions = function getFetchOptions(operation, fetchOptionsOverrides) { if (fetchOptionsOverrides === void 0) { fetchOptionsOverrides = {}; } var fetchOptions = _objectSpread2({ method: 'POST', headers: _objectSpread2({}, this.headers) }, this.fetchOptions, {}, fetchOptionsOverrides); var _extractFiles = extractFiles.extractFiles(operation), clone = _extractFiles.clone, files = _extractFiles.files; var operationJSON = JSON.stringify(clone); if (files.size) { // See the GraphQL multipart request spec: // https://github.com/jaydenseric/graphql-multipart-request-spec var form = new FormData(); form.append('operations', operationJSON); var map = {}; var i = 0; files.forEach(function (paths) { map[++i] = paths; }); form.append('map', JSON.stringify(map)); i = 0; files.forEach(function (paths, file) { form.append("" + ++i, file, file.name); }); fetchOptions.body = form; } else { fetchOptions.headers['Content-Type'] = 'application/json'; if (fetchOptions.method == 'POST') { fetchOptions.body = operationJSON; } } return fetchOptions; }; _proto.request = function request(operation, options) { var _this = this; if (options === void 0) { options = {}; } var url = this.url; if (typeof options.fetchOptionsOverrides !== 'undefined' && options.fetchOptionsOverrides.method == 'GET') { var params = { query: operation.query, variables: JSON.stringify(operation.variables) }; var paramsQueryString = Object.entries(params).map(function (_ref3) { var k = _ref3[0], v = _ref3[1]; return k + "=" + encodeURIComponent(v); }).join('&'); url = url + '?' + paramsQueryString; } return this.fetch(url, this.getFetchOptions(operation, options.fetchOptionsOverrides)).then(function (response) { if (!response.ok) { return response.text().then(function (body) { var status = response.status, statusText = response.statusText; return _this.generateResult({ httpError: { status: status, statusText: statusText, body: body } }); }); } else { return response.json().then(function (_ref4) { var errors = _ref4.errors, data = _ref4.data; return _this.generateResult({ graphQLErrors: errors, data: data }); }); } }).catch(function (error) { return _this.generateResult({ fetchError: error }); }).then(function (result) { if (result.error && _this.logErrors) { _this.logErrorResult({ result: result, operation: operation }); } return result; }); }; return GraphQLClient; }(); var actionTypes = { RESET_STATE: 'RESET_STATE', LOADING: 'LOADING', CACHE_HIT: 'CACHE_HIT', REQUEST_RESULT: 'REQUEST_RESULT' }; function reducer(state, action) { switch (action.type) { case actionTypes.RESET_STATE: return action.initialState; case actionTypes.LOADING: if (state.loading) { return state; // saves a render cycle as state is the same } return _objectSpread2({}, state, { loading: true }); case actionTypes.CACHE_HIT: if (state.cacheHit) { // we can be sure this is the same cacheKey hit // because we dispatch RESET_STATE if it changes return state; } return _objectSpread2({}, action.result, { cacheHit: true, loading: false }); case actionTypes.REQUEST_RESULT: return _objectSpread2({}, action.result, { data: state.data && action.result.data && action.updateData ? action.updateData(state.data, action.result.data) : action.result.data, cacheHit: false, loading: false }); default: return state; } } function useDeepCompareCallback(callback, deps) { var ref = React.useRef(); if (!deepEqual(deps, ref.current)) { ref.current = deps; } return React.useCallback(callback, ref.current); } /* options include: opts.variables: Object opts.operationName: String opts.fetchOptionsOverrides: Object opts.skipCache: Boolean */ function useClientRequest(query, initialOpts) { if (initialOpts === void 0) { initialOpts = {}; } if (typeof query !== 'string') { throw new Error('Your query must be a string. If you are using the `gql` template literal from graphql-tag, remove it from your query.'); } var client = React.useContext(ClientContext); var isMounted = React.useRef(true); var activeCacheKey = React.useRef(null); var operation = { query: query, variables: initialOpts.variables, operationName: initialOpts.operationName }; var cacheKey = client.getCacheKey(operation, initialOpts); var isDeferred = initialOpts.isMutation || initialOpts.isManual; var initialCacheHit = initialOpts.skipCache || !client.cache ? null : client.cache.get(cacheKey); var initialState = _objectSpread2({}, initialCacheHit, { cacheHit: !!initialCacheHit, loading: isDeferred ? false : !initialCacheHit }); var _React$useReducer = React.useReducer(reducer, initialState), state = _React$useReducer[0], dispatch = _React$useReducer[1]; // NOTE: state from useReducer is only initialState on the first render // in subsequent renders the operation could have changed // if so the state would be invalid, this effect ensures we reset it back var stringifiedCacheKey = JSON.stringify(cacheKey); React.useEffect(function () { if (!initialOpts.updateData) { // if using updateData we can assume that the consumer cares about the previous data dispatch({ type: actionTypes.RESET_STATE, initialState: initialState }); } }, [stringifiedCacheKey]); // eslint-disable-line react-hooks/exhaustive-deps React.useEffect(function () { isMounted.current = true; return function () { isMounted.current = false; }; }, []); // arguments to fetchData override the useClientRequest arguments var fetchData = useDeepCompareCallback(function (newOpts) { if (!isMounted.current) return Promise.resolve(); var revisedOpts = _objectSpread2({}, initialOpts, {}, newOpts); var revisedOperation = _objectSpread2({}, operation, { variables: revisedOpts.variables, operationName: revisedOpts.operationName }); var revisedCacheKey = client.getCacheKey(revisedOperation, revisedOpts); // NOTE: There is a possibility of a race condition whereby // the second query could finish before the first one, dispatching an old result // see https://github.com/nearform/graphql-hooks/issues/150 activeCacheKey.current = revisedCacheKey; var cacheHit = revisedOpts.skipCache ? null : client.getCache(revisedCacheKey); if (cacheHit) { dispatch({ type: actionTypes.CACHE_HIT, result: cacheHit }); return Promise.resolve(cacheHit); } dispatch({ type: actionTypes.LOADING }); return client.request(revisedOperation, revisedOpts).then(function (result) { if (revisedOpts.updateData && typeof revisedOpts.updateData !== 'function') { throw new Error('options.updateData must be a function'); } var actionResult = _objectSpread2({}, result); if (revisedOpts.useCache) { actionResult.useCache = true; actionResult.cacheKey = revisedCacheKey; } if (isMounted.current && revisedCacheKey === activeCacheKey.current) { dispatch({ type: actionTypes.REQUEST_RESULT, updateData: revisedOpts.updateData, result: actionResult }); } return result; }); }, [client, initialOpts, operation]); // We perform caching after reducer update // To include the outcome of updateData React.useEffect(function () { if (state.useCache) { client.saveCache(state.cacheKey, state); } }, [client, state]); return [fetchData, state]; } var defaultOpts = { useCache: true }; function useQuery(query, opts) { if (opts === void 0) { opts = {}; } var allOpts = _objectSpread2({}, defaultOpts, {}, opts); var client = React.useContext(ClientContext); var _React$useState = React.useState(false), calledDuringSSR = _React$useState[0], setCalledDuringSSR = _React$useState[1]; var _useClientRequest = useClientRequest(query, allOpts), queryReq = _useClientRequest[0], state = _useClientRequest[1]; if (client.ssrMode && opts.ssr !== false && !calledDuringSSR) { // result may already be in the cache from previous SSR iterations if (!state.loading && !state.data && !state.error) { var p = queryReq(); client.ssrPromises.push(p); } setCalledDuringSSR(true); } var stringifiedAllOpts = JSON.stringify(allOpts); React.useEffect(function () { queryReq(); }, [query, stringifiedAllOpts]); // eslint-disable-line react-hooks/exhaustive-deps return _objectSpread2({}, state, { refetch: React.useCallback(function (options) { if (options === void 0) { options = {}; } return queryReq(_objectSpread2({ skipCache: true, // don't call the updateData that has been passed into useQuery here // reset to the default behaviour of returning the raw query result // this can be overridden in refetch options updateData: function updateData(_, data) { return data; } }, options)); }, [queryReq]) }); } var useManualQuery = function useManualQuery(query, options) { return useClientRequest(query, _objectSpread2({ useCache: true, isManual: true }, options)); }; var useMutation = function useMutation(query, options) { return useClientRequest(query, _objectSpread2({ isMutation: true }, options)); }; exports.ClientContext = ClientContext; exports.GraphQLClient = GraphQLClient; exports.useClientRequest = useClientRequest; exports.useManualQuery = useManualQuery; exports.useMutation = useMutation; exports.useQuery = useQuery;