react-query
Version:
Hooks for managing, caching and syncing asynchronous and remote data in React
152 lines (127 loc) • 4.86 kB
JavaScript
import { difference, replaceAt } from './utils';
import { notifyManager } from './notifyManager';
import { QueryObserver } from './queryObserver';
import { Subscribable } from './subscribable';
export class QueriesObserver extends Subscribable {
constructor(client, queries) {
super();
this.client = client;
this.queries = [];
this.result = [];
this.observers = [];
this.observersMap = {};
if (queries) {
this.setQueries(queries);
}
}
onSubscribe() {
if (this.listeners.length === 1) {
this.observers.forEach(observer => {
observer.subscribe(result => {
this.onUpdate(observer, result);
});
});
}
}
onUnsubscribe() {
if (!this.listeners.length) {
this.destroy();
}
}
destroy() {
this.listeners = [];
this.observers.forEach(observer => {
observer.destroy();
});
}
setQueries(queries, notifyOptions) {
this.queries = queries;
notifyManager.batch(() => {
const prevObservers = this.observers;
const newObserverMatches = this.findMatchingObservers(this.queries); // set options for the new observers to notify of changes
newObserverMatches.forEach(match => match.observer.setOptions(match.defaultedQueryOptions, notifyOptions));
const newObservers = newObserverMatches.map(match => match.observer);
const newObserversMap = Object.fromEntries(newObservers.map(observer => [observer.options.queryHash, observer]));
const newResult = newObservers.map(observer => observer.getCurrentResult());
const hasIndexChange = newObservers.some((observer, index) => observer !== prevObservers[index]);
if (prevObservers.length === newObservers.length && !hasIndexChange) {
return;
}
this.observers = newObservers;
this.observersMap = newObserversMap;
this.result = newResult;
if (!this.hasListeners()) {
return;
}
difference(prevObservers, newObservers).forEach(observer => {
observer.destroy();
});
difference(newObservers, prevObservers).forEach(observer => {
observer.subscribe(result => {
this.onUpdate(observer, result);
});
});
this.notify();
});
}
getCurrentResult() {
return this.result;
}
getOptimisticResult(queries) {
return this.findMatchingObservers(queries).map(match => match.observer.getOptimisticResult(match.defaultedQueryOptions));
}
findMatchingObservers(queries) {
const prevObservers = this.observers;
const defaultedQueryOptions = queries.map(options => this.client.defaultQueryOptions(options));
const matchingObservers = defaultedQueryOptions.flatMap(defaultedOptions => {
const match = prevObservers.find(observer => observer.options.queryHash === defaultedOptions.queryHash);
if (match != null) {
return [{
defaultedQueryOptions: defaultedOptions,
observer: match
}];
}
return [];
});
const matchedQueryHashes = matchingObservers.map(match => match.defaultedQueryOptions.queryHash);
const unmatchedQueries = defaultedQueryOptions.filter(defaultedOptions => !matchedQueryHashes.includes(defaultedOptions.queryHash));
const unmatchedObservers = prevObservers.filter(prevObserver => !matchingObservers.some(match => match.observer === prevObserver));
const getObserver = options => {
const defaultedOptions = this.client.defaultQueryOptions(options);
const currentObserver = this.observersMap[defaultedOptions.queryHash];
return currentObserver != null ? currentObserver : new QueryObserver(this.client, defaultedOptions);
};
const newOrReusedObservers = unmatchedQueries.map((options, index) => {
if (options.keepPreviousData) {
// return previous data from one of the observers that no longer match
const previouslyUsedObserver = unmatchedObservers[index];
if (previouslyUsedObserver !== undefined) {
return {
defaultedQueryOptions: options,
observer: previouslyUsedObserver
};
}
}
return {
defaultedQueryOptions: options,
observer: getObserver(options)
};
});
const sortMatchesByOrderOfQueries = (a, b) => defaultedQueryOptions.indexOf(a.defaultedQueryOptions) - defaultedQueryOptions.indexOf(b.defaultedQueryOptions);
return matchingObservers.concat(newOrReusedObservers).sort(sortMatchesByOrderOfQueries);
}
onUpdate(observer, result) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.result = replaceAt(this.result, index, result);
this.notify();
}
}
notify() {
notifyManager.batch(() => {
this.listeners.forEach(listener => {
listener(this.result);
});
});
}
}