apollo-magic-refetch
Advanced tools
Comparing version 1.0.3 to 1.1.0
@@ -12,6 +12,20 @@ | ||
export default (async function refetch(client, typename, ids, idField = 'id') { | ||
function every(array, predicate) { | ||
for (let elem of array) { | ||
if (!predicate(elem)) return false; | ||
} | ||
return true; | ||
} | ||
export default (async function refetch(client, typenameOrTerms, ids, idField) { | ||
const types = await getSchemaTypes(client); | ||
const finalIds = ids != null ? normalizeIds(ids) : null; | ||
let terms; | ||
if (typeof typenameOrTerms === 'string') { | ||
terms = [[typenameOrTerms, ids, idField]]; | ||
} else if (Array.isArray(typenameOrTerms)) { | ||
terms = typenameOrTerms; | ||
} else { | ||
throw new Error(`invalid typename or terms: ${typenameOrTerms}`); | ||
} | ||
@@ -24,7 +38,6 @@ const { queryManager: { queries } } = client; | ||
let data; | ||
if (finalIds) { | ||
const currentResult = observableQuery.currentResult(); | ||
if (currentResult) data = currentResult.data; | ||
} | ||
if (doesQueryContain(document, types, typename, data, finalIds, idField)) { | ||
const currentResult = observableQuery.currentResult(); | ||
if (currentResult) data = currentResult.data; | ||
if (every(terms, ([typename, ids, idField]) => doesQueryContain(document, types, typename, data, ids != null ? normalizeIds(ids) : null, idField || 'id'))) { | ||
promises.push(observableQuery.refetch()); | ||
@@ -31,0 +44,0 @@ } |
179
index.js
@@ -11,5 +11,5 @@ 'use strict'; | ||
var _getIterator2 = require('babel-runtime/core-js/get-iterator'); | ||
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); | ||
var _getIterator3 = _interopRequireDefault(_getIterator2); | ||
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); | ||
@@ -20,2 +20,6 @@ var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); | ||
var _getIterator2 = require('babel-runtime/core-js/get-iterator'); | ||
var _getIterator3 = _interopRequireDefault(_getIterator2); | ||
var _set = require('babel-runtime/core-js/set'); | ||
@@ -41,8 +45,35 @@ | ||
function every(array, predicate) { | ||
var _iteratorNormalCompletion = true; | ||
var _didIteratorError = false; | ||
var _iteratorError = undefined; | ||
try { | ||
for (var _iterator = (0, _getIterator3.default)(array), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
var _elem = _step.value; | ||
if (!predicate(_elem)) return false; | ||
} | ||
} catch (err) { | ||
_didIteratorError = true; | ||
_iteratorError = err; | ||
} finally { | ||
try { | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
} | ||
} finally { | ||
if (_didIteratorError) { | ||
throw _iteratorError; | ||
} | ||
} | ||
} | ||
return true; | ||
} | ||
exports.default = function () { | ||
var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(client, typename, ids) { | ||
var idField = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'id'; | ||
var _ref = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee(client, typenameOrTerms, ids, idField) { | ||
var types, terms, queries, promises, _loop, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, query, _ret; | ||
var types, finalIds, queries, promises, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, query, document, observableQuery, data, currentResult; | ||
return _regenerator2.default.wrap(function _callee$(_context) { | ||
@@ -57,83 +88,117 @@ while (1) { | ||
types = _context.sent; | ||
finalIds = ids != null ? normalizeIds(ids) : null; | ||
queries = client.queryManager.queries; | ||
promises = []; | ||
_iteratorNormalCompletion = true; | ||
_didIteratorError = false; | ||
_iteratorError = undefined; | ||
_context.prev = 9; | ||
_iterator = (0, _getIterator3.default)(queries.values()); | ||
terms = void 0; | ||
case 11: | ||
if (_iteratorNormalCompletion = (_step = _iterator.next()).done) { | ||
_context.next = 22; | ||
if (!(typeof typenameOrTerms === 'string')) { | ||
_context.next = 8; | ||
break; | ||
} | ||
query = _step.value; | ||
document = query.document, observableQuery = query.observableQuery; | ||
terms = [[typenameOrTerms, ids, idField]]; | ||
_context.next = 13; | ||
break; | ||
if (observableQuery) { | ||
_context.next = 16; | ||
case 8: | ||
if (!Array.isArray(typenameOrTerms)) { | ||
_context.next = 12; | ||
break; | ||
} | ||
return _context.abrupt('continue', 19); | ||
terms = typenameOrTerms; | ||
_context.next = 13; | ||
break; | ||
case 16: | ||
data = void 0; | ||
case 12: | ||
throw new Error('invalid typename or terms: ' + typenameOrTerms); | ||
if (finalIds) { | ||
currentResult = observableQuery.currentResult(); | ||
case 13: | ||
queries = client.queryManager.queries; | ||
promises = []; | ||
_loop = function _loop(query) { | ||
var document = query.document, | ||
observableQuery = query.observableQuery; | ||
if (!observableQuery) return 'continue'; | ||
var data = void 0; | ||
var currentResult = observableQuery.currentResult(); | ||
if (currentResult) data = currentResult.data; | ||
if (every(terms, function (_ref2) { | ||
var _ref3 = (0, _slicedToArray3.default)(_ref2, 3), | ||
typename = _ref3[0], | ||
ids = _ref3[1], | ||
idField = _ref3[2]; | ||
return (0, _doesQueryContain2.default)(document, types, typename, data, ids != null ? normalizeIds(ids) : null, idField || 'id'); | ||
})) { | ||
promises.push(observableQuery.refetch()); | ||
} | ||
}; | ||
_iteratorNormalCompletion2 = true; | ||
_didIteratorError2 = false; | ||
_iteratorError2 = undefined; | ||
_context.prev = 19; | ||
_iterator2 = (0, _getIterator3.default)(queries.values()); | ||
case 21: | ||
if (_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done) { | ||
_context.next = 29; | ||
break; | ||
} | ||
if ((0, _doesQueryContain2.default)(document, types, typename, data, finalIds, idField)) { | ||
promises.push(observableQuery.refetch()); | ||
query = _step2.value; | ||
_ret = _loop(query); | ||
if (!(_ret === 'continue')) { | ||
_context.next = 26; | ||
break; | ||
} | ||
case 19: | ||
_iteratorNormalCompletion = true; | ||
_context.next = 11; | ||
return _context.abrupt('continue', 26); | ||
case 26: | ||
_iteratorNormalCompletion2 = true; | ||
_context.next = 21; | ||
break; | ||
case 22: | ||
_context.next = 28; | ||
case 29: | ||
_context.next = 35; | ||
break; | ||
case 24: | ||
_context.prev = 24; | ||
_context.t0 = _context['catch'](9); | ||
_didIteratorError = true; | ||
_iteratorError = _context.t0; | ||
case 31: | ||
_context.prev = 31; | ||
_context.t0 = _context['catch'](19); | ||
_didIteratorError2 = true; | ||
_iteratorError2 = _context.t0; | ||
case 28: | ||
_context.prev = 28; | ||
_context.prev = 29; | ||
case 35: | ||
_context.prev = 35; | ||
_context.prev = 36; | ||
if (!_iteratorNormalCompletion && _iterator.return) { | ||
_iterator.return(); | ||
if (!_iteratorNormalCompletion2 && _iterator2.return) { | ||
_iterator2.return(); | ||
} | ||
case 31: | ||
_context.prev = 31; | ||
case 38: | ||
_context.prev = 38; | ||
if (!_didIteratorError) { | ||
_context.next = 34; | ||
if (!_didIteratorError2) { | ||
_context.next = 41; | ||
break; | ||
} | ||
throw _iteratorError; | ||
throw _iteratorError2; | ||
case 34: | ||
return _context.finish(31); | ||
case 41: | ||
return _context.finish(38); | ||
case 35: | ||
return _context.finish(28); | ||
case 42: | ||
return _context.finish(35); | ||
case 36: | ||
_context.next = 38; | ||
case 43: | ||
_context.next = 45; | ||
return promises; | ||
case 38: | ||
case 45: | ||
case 'end': | ||
@@ -143,6 +208,6 @@ return _context.stop(); | ||
} | ||
}, _callee, this, [[9, 24, 28, 36], [29,, 31, 35]]); | ||
}, _callee, this, [[19, 31, 35, 43], [36,, 38, 42]]); | ||
})); | ||
function refetch(_x, _x2, _x3) { | ||
function refetch(_x, _x2, _x3, _x4) { | ||
return _ref.apply(this, arguments); | ||
@@ -149,0 +214,0 @@ } |
{ | ||
"name": "apollo-magic-refetch", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "magically refetches relevant queries after creates and deletes", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
177
README.md
@@ -8,3 +8,4 @@ # apollo-magic-refetch | ||
Handling Apollo cache updates after creating and deleting objects remains a | ||
Handling Apollo cache updates after creating and deleting objects, or | ||
associating and dissociating objects, remains a | ||
[poorly solved problem](https://github.com/apollographql/apollo-client/issues/899). | ||
@@ -20,2 +21,15 @@ `update` and `refetchQueries` props on `Mutation`s couple different areas of | ||
# Table of Contents | ||
* [How it works](#how-it-works) | ||
* [Current limitations](#current-limitations) | ||
* [ES environment requirements](#es-environment-requirements) | ||
* [Type metadata usage](#type-metadata-usage) | ||
* [Handling Deletions](#handling-deletions) | ||
* [Handling Creation](#handling-creation) | ||
* [Handling associations being broken](#handling-associations-being-broken) | ||
* [Handling associations being created](#handling-associations-being-created) | ||
* [API](#api) | ||
+ [`refetch(client, typenameOrTerms, [ids], [idField])`](#refetchclient-typenameorterms-ids-idfield) | ||
## How it works | ||
@@ -145,5 +159,153 @@ | ||
## Handling associations being broken | ||
In this example, a view shows a list of `Organization`s, each containing a | ||
sublist of `User`s. When one or more users is removed from an organization, | ||
it makes the following call: | ||
```js | ||
refetch(client, [ | ||
['User', userIds], | ||
['Organization', organizationId], | ||
]) | ||
``` | ||
Passing an array to `refetch` means to only refetch queries containing all of | ||
the conditions in the array. So the query below would be refetched, but a query | ||
containing only `Organizations` or a query containing only `User`s would not. | ||
```js | ||
import * as React from 'react' | ||
import gql from 'graphql-tag' | ||
import refetch from 'apollo-magic-refetch' | ||
import {Mutation, ApolloConsumer} from 'react-apollo' | ||
import OrganizationView from './OrganizationView' | ||
const query = gql` | ||
query { | ||
Organizations { | ||
id | ||
name | ||
Users { | ||
id | ||
username | ||
} | ||
} | ||
} | ||
` | ||
const mutation = gql` | ||
mutation removeUsersFromOrganization($organizationId: Int!, $userIds: [Int!]!) { | ||
result: removeUsersFromOrganization(organizationId: $organizationId, userIds: $userIds) { | ||
organizationId | ||
userIds | ||
} | ||
} | ||
` | ||
const OrganizationViewContainer = ({organization: {id, name, Users}}) => ( | ||
<ApolloConsumer> | ||
{client => ( | ||
<Mutation | ||
mutation={mutation} | ||
update={(cache, {data: {result: {organizationId, userIds}}}) => | ||
refetch(client, [ | ||
['User', userIds], | ||
['Organization', organizationId], | ||
]) | ||
} | ||
> | ||
{removeUsersFromOrganization => ( | ||
<OrganizationView | ||
organization={organization} | ||
onRemoveUsers={userIds => removeUsersFromOrganization({ | ||
variables: {organizationId, userIds}, | ||
})} | ||
/> | ||
)} | ||
</Mutation> | ||
)} | ||
</ApolloConsumer> | ||
) | ||
const OrganizationsViewContainer = () => ( | ||
<Query query={query}> | ||
{({data}) => { | ||
const {Organizations} = data || {} | ||
if (!Organizations) return <div /> | ||
return ( | ||
<div> | ||
<h1>Organizations</h1> | ||
{Organizations.map((organization) => ( | ||
<OrganizationViewContainer | ||
key={organization.id} | ||
organization={organization} | ||
/> | ||
)} | ||
</div> | ||
) | ||
}} | ||
</Query> | ||
) | ||
``` | ||
## Handling associations being created | ||
Assuming the same `Organization`s/`User`s schema as above, the example performs | ||
the necessary refetches when a user is created and added to an organization: | ||
```js | ||
refetch(client, [ | ||
['User'], | ||
['Organization', organizationId], | ||
]) | ||
``` | ||
In this case no `ids` are given for `User`, so any query containing the an | ||
`Organization` with the given `organizationId` in its results and selecting any | ||
`User`s would be refetched. (This doesn't perfectly exclude cases that fetch | ||
Users and Organizations separately, instead of one nested inside the other, but | ||
it's better than nothing). | ||
```js | ||
import * as React from 'react' | ||
import gql from 'graphql-tag' | ||
import refetch from 'apollo-magic-refetch' | ||
import {Mutation, ApolloConsumer} from 'react-apollo' | ||
import CreateUserForm from './CreateUserForm' | ||
const mutation = gql` | ||
mutation createUser($organizationId: Int!, $values: CreateUser!) { | ||
result: createUser(organizationId: $organizationId, values: $values) { | ||
organizationId | ||
id | ||
username | ||
} | ||
} | ||
` | ||
const CreateUserFormContainer = ({organizationId}) => ( | ||
<ApolloConsumer> | ||
{client => ( | ||
<Mutation | ||
mutation={mutation} | ||
update={() => | ||
refetch(client, [ | ||
['User'], | ||
['Organization', organizationId], | ||
]) | ||
} | ||
> | ||
{createUser => ( | ||
<CreateUserForm | ||
onSubmit={values => createUser({ | ||
variables: {organizationId, values}, | ||
})} | ||
/> | ||
)} | ||
</Mutation> | ||
)} | ||
</ApolloConsumer> | ||
) | ||
``` | ||
## API | ||
### `refetch(client, typename, [ids], [idField])` | ||
### `refetch(client, typenameOrTerms, [ids], [idField])` | ||
@@ -156,7 +318,10 @@ #### Arguments | ||
##### `typename: string` | ||
##### `typenameOrTerms: string | Array<Term>` | ||
The `__typename` of the GraphQL type that was created or deleted. | ||
The `__typename` of the GraphQL type that was created or deleted, or an array of | ||
`[typename, ids, idField]` tuples (`ids` and `idField` are optional). If an | ||
array is given, a query must match all of the conditions in the array to be | ||
refetched. | ||
##### `ids: any (*optional*)` | ||
##### `ids: any` (*optional*) | ||
@@ -167,4 +332,4 @@ A single id, an array of ids, or a `Set` of ids that were deleted. If given, | ||
##### `idField: string (*optional, default*: 'id')` | ||
##### `idField: string` (*optional, default*: `'id'`) | ||
The name of the id field in the type that was deleted. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
269244
688
331