react-redux-provide
Version:
Build your UI with React. Manage application state with Redux providers. Persist and share application state with replication. Use pure functions everywhere.
1,186 lines (989 loc) • 31.9 kB
JavaScript
import shallowEqual from './shallowEqual';
import getRelevantKeys from './getRelevantKeys';
import createProviderStore from './createProviderStore';
import { pushOnReady, unshiftOnReady, unshiftMiddleware } from './keyConcats';
const isServerSide = typeof window === 'undefined';
const isTesting = typeof process !== 'undefined'
&& process.env
&& process.env.NODE_ENV === 'test';
const globalProviderInstances = {};
// TODO: we'll use this at some point to select only component propTypes
/*
function hasReducerKeys(providerInstance, getReducerKeys) {
if (!getReducerKeys) {
return true;
}
const { hasReducerKeys = {} } = providerInstance;
for (let reducerKey in getReducerKeys) {
if (!hasReducerKeys[reducerKey]) {
providerInstance.hasReducerKeys = {
...hasReducerKeys,
...getReducerKeys
};
return false;
}
}
return true;
}*/
/**
* Instantiates a provider with its own store.
*
* @param {Object} fauxInstance resembles { props, context }
* @param {Object} provider
* @param {String|Function} providerKey Optional
* @param {Function} readyCallback Optional
* @param {Object} createState Optional
* @param {Object} getReducerKeys Optional
* @param {Boolean} useCreator Optional
* @return {Object}
* @api public
*/
export default function instantiateProvider(
fauxInstance,
provider,
providerKey,
readyCallback,
createState,
getReducerKeys,
useCreator // TODO: clean this up
) {
if (arguments.length === 1) {
fauxInstance = arguments[0].fauxInstance;
provider = arguments[0].provider;
providerKey = arguments[0].providerKey;
readyCallback = arguments[0].readyCallback;
createState = arguments[0].createState;
getReducerKeys = arguments[0].getReducerKeys;
useCreator = arguments[0].useCreator;
if (!fauxInstance) {
provider = arguments[0];
fauxInstance = {};
}
}
if (!fauxInstance.props) {
fauxInstance.props = {};
}
if (typeof providerKey === 'undefined') {
providerKey = provider.key;
}
if (!provider.actions) {
provider.actions = {};
}
if (!provider.reducers) {
provider.reducers = {};
}
if (getReducerKeys === true) {
getReducerKeys = provider.reducers;
}
const providers = getProviders(fauxInstance);
const providerInstances = getProviderInstances(fauxInstance);
let providerInstance;
let isStatic = typeof providerKey !== 'function';
let storeKey;
let creator;
if (typeof provider.key === 'string') {
if (!providers[provider.key]) {
providers[provider.key] = provider;
}
} else if (provider.defaultKey) {
if (!providers[provider.defaultKey]) {
providers[provider.defaultKey] = provider;
}
} else if (!provider.key || !provider.key.toString) {
console.warn('Missing or invalid provider key!');
} else if (!providers[provider.key.toString()]) {
providers[provider.key.toString()] = provider;
}
if (!isStatic) {
// get actual `providerKey`
providerKey = providerKey(fauxInstance);
// if actual `providerKey` matches `key`, treat as static provider
isStatic = providerKey === provider.key;
}
if (providerKey === null) {
storeKey = null;
providerKey = provider.defaultKey;
isStatic = true;
}
providerInstance = provider.isGlobal
? globalProviderInstances[providerKey]
: providerInstances && providerInstances[providerKey];
if (fauxInstance.relevantProviders) {
fauxInstance.relevantProviders[providerKey] = true;
}
if (
createState
&& typeof createState === 'object'
&& provider.state
&& typeof provider.state === 'object'
) {
createState = { ...provider.state, ...createState };
}
// TODO: we'll use this at some point
//if (providerInstance && hasReducerKeys(providerInstance, getReducerKeys)) {
if (providerInstance) {
if (createState) {
if (useCreator) { // TODO: clean this up
creator = providerInstance;
} else {
providerInstances[providerKey] = providerInstance;
providerInstance.store.setState(createState, false, true);
if (readyCallback) {
if (providerInstance.ready) {
readyCallback(providerInstance);
} else {
pushOnReady({ providerInstance }, readyCallback);
}
}
return providerInstance;
}
} else {
providerInstances[providerKey] = providerInstance;
if (readyCallback) {
if (providerInstance.ready) {
readyCallback(providerInstance);
} else {
pushOnReady({ providerInstance }, readyCallback);
}
}
return providerInstance;
}
}
if (!provider.hasThunk) {
provider.hasThunk = true;
if (provider.wait && !Array.isArray(provider.wait)) {
provider.wait = [ provider.wait ];
}
if (provider.clear && !Array.isArray(provider.clear)) {
provider.clear = [ provider.clear ];
}
function findProvider(props) {
if (getRelevantKeys(provider.reducers, props).length) {
return provider;
}
for (let key in providers) {
if (getRelevantKeys(providers[key].reducers, props).length) {
return providers[key];
}
}
return provider;
}
function getResultInstances(result, callback) {
const resultInstances = [];
let semaphore = result && result.length;
function clear() {
if (--semaphore === 0) {
callback(resultInstances);
}
}
if (!semaphore) {
semaphore = 1;
clear();
return;
}
result.forEach((resultProps, index) => {
resultInstances[index] = null;
instantiateProvider({
fauxInstance: getTempFauxInstance(fauxInstance, resultProps),
provider: findProvider(resultProps),
readyCallback: resultInstance => {
resultInstances[index] = resultInstance;
clear();
}
});
});
}
function getInstance(props, callback, create, useCreator) {
let provider;
let providerKey;
if (typeof props === 'string') { // key is already known
if (providerInstances[props]) {
providerKey = props;
}
provider = providers[props] || providerInstances[props];
props = {};
} else {
provider = findProvider(props);
}
return instantiateProvider({
fauxInstance: getTempFauxInstance(fauxInstance, props),
provider,
providerKey,
readyCallback: callback,
createState: create ? props : null,
useCreator
});
}
function getInstances(propsArray, callback, create, useCreator) {
const instances = [];
let getCount = propsArray.length;
const clear = () => {
if (--getCount === 0) {
if (callback) {
callback(instances);
}
}
};
propsArray.forEach((props, index) => {
getInstance(props, instance => {
instances[index] = instance;
clear();
}, create);
});
return instances;
}
function createInstance(props, callback, useCreator) {
return getInstance(props, callback, true, useCreator);
}
function createInstances(propsArray, callback, useCreator) {
return getInstances(propsArray, callback, true, useCreator);
}
function setStates(states) {
const gettingInstances = [];
const settingStates = [];
let clientStates = null;
if (!isServerSide) {
if (!window.clientStates) {
window.clientStates = {};
}
clientStates = window.clientStates;
}
for (let providerKey in states) {
const state = states[providerKey];
const providerInstance = providerInstances[providerKey];
if (providerInstance) {
if (providerInstance.store.setState) {
settingStates.push(() => providerInstance.store.setState(state));
}
} else {
if (clientStates) {
clientStates[providerKey] = state;
}
gettingInstances.push(state);
}
}
// now that `clientStates` are cached...
while (gettingInstances.length) {
getInstance(gettingInstances.shift());
}
while (settingStates.length) {
settingStates.shift()();
}
}
function find(props, doInstantiate, callback) {
if (arguments.length === 2) {
callback = doInstantiate;
doInstantiate = false;
}
handleQueries(getTempFauxInstance(fauxInstance, props), () => {
if (!doInstantiate) {
callback(props.query ? props.result : props.results);
return;
}
if (props.query) {
getResultInstances(props.result, callback);
return;
}
const { results } = props;
const resultsInstances = {};
const resultsKeys = results && Object.keys(results);
let semaphore = resultsKeys && resultsKeys.length;
function clear() {
if (--semaphore === 0) {
callback(resultsInstances);
}
}
if (!semaphore) {
semaphore = 1;
clear();
}
resultsKeys.forEach(resultKey => {
resultsInstances[resultKey] = [];
getResultInstances(
results[resultKey],
resultInstances => {
resultsInstances[resultKey] = resultInstances;
clear();
}
);
});
});
}
const providerApi = {
getInstance,
getInstances,
createInstance,
createInstances,
setStates,
find
};
unshiftMiddleware({ provider }, ({ dispatch, getState }) => {
return next => action => {
if (typeof action !== 'function') {
return next(action);
}
if (provider.wait) {
provider.wait.forEach(fn => fn());
}
return action(action => {
const state = store.getState();
let storeChanged = false;
dispatch(action);
if (provider.clear) {
storeChanged = state !== store.getState();
provider.clear.forEach(fn => fn(storeChanged));
}
}, getState, providerApi);
};
});
}
if (provider.wait) {
provider.wait.forEach(fn => fn());
}
providerInstance = Object.create(provider);
providerInstance.providerKey = providerKey;
providerInstance.isStatic = isStatic;
const store = createProviderStore(
providerInstance,
storeKey,
createState,
createState ? (state) => {
const { onReady } = providerInstance;
providerInstance = instantiateProvider({
fauxInstance: getTempFauxInstance(fauxInstance, state),
provider,
readyCallback: createdInstance => {
if (Array.isArray(onReady)) {
onReady.forEach(fn => fn(createdInstance));
} else if (onReady) {
onReady(createdInstance);
}
}
});
} : null,
// TODO: we need a better way to create + replicate
creator && creator.store
);
const initialState = store.getState();
const { actions } = providerInstance;
const actionCreators = {};
const setKey = store.setKey;
if (setKey) {
store.setKey = (newKey, readyCallback) => {
if (provider.wait) {
provider.wait.forEach(fn => fn());
}
setKey(newKey, () => {
if (Array.isArray(providerInstance.onReady)) {
providerInstance.onReady.forEach(fn => fn(providerInstance));
} else if (providerInstance.onReady) {
providerInstance.onReady(providerInstance);
}
if (readyCallback) {
readyCallback();
}
if (provider.clear) {
provider.clear.forEach(fn => fn(true));
}
});
};
}
for (let actionKey in actions) {
actionCreators[actionKey] = function() {
return store.dispatch(actions[actionKey].apply(this, arguments));
};
}
providerInstance.store = store;
providerInstance.actionCreators = actionCreators;
if (!createState) {
if (provider.isGlobal) {
globalProviderInstances[providerKey] = providerInstance;
}
if (providerInstances) {
providerInstances[providerKey] = providerInstance;
}
if (!provider.instances) {
provider.instances = [];
}
provider.instances.push(providerInstance);
}
if (provider.subscribers) {
Object.keys(provider.subscribers).forEach(key => {
const handler = provider.subscribers[key];
const subProvider = providers[key];
const subKey = provider.defaultKey || (
typeof provider.key === 'function'
? provider.key({})
: String(provider.key)
);
const callHandler = () => {
const subProviderInstances = subProvider && subProvider.instances;
if (subProviderInstances) {
subProviderInstances.forEach(subProviderInstance => {
handler(providerInstance, subProviderInstance);
});
}
};
if (subProvider) {
if (!subProvider.subscribeTo) {
subProvider.subscribeTo = {};
}
if (!subProvider.subscribeTo[subKey]) {
subProvider.subscribeTo[subKey] = handler;
}
}
providerInstance.store.subscribe(callHandler);
callHandler();
});
}
if (provider.subscribeTo) {
Object.keys(provider.subscribeTo).forEach(key => {
const handler = provider.subscribeTo[key];
const supProvider = providers[key];
const supKey = provider.defaultKey || (
typeof provider.key === 'function'
? provider.key({})
: String(provider.key)
);
if (!supProvider) {
return;
}
if (!supProvider.subscribers) {
supProvider.subscribers = {};
}
if (!supProvider.subscribers[supKey]) {
supProvider.subscribers[supKey] = handler;
if (supProvider.instances) {
supProvider.instances.forEach(supProviderInstance => {
supProviderInstance.store.subscribe(() => {
provider.instances.forEach(providerInstance => {
handler(supProviderInstance, providerInstance);
});
});
});
}
}
if (supProvider.instances) {
supProvider.instances.forEach(supProviderInstance => {
handler(supProviderInstance, providerInstance);
});
}
});
}
if (!createState) {
if (Array.isArray(providerInstance.onInstantiated)) {
providerInstance.onInstantiated.forEach(fn => fn(providerInstance));
} else if (providerInstance.onInstantiated) {
providerInstance.onInstantiated(providerInstance);
}
}
unshiftOnReady({ providerInstance }, () => {
providerInstance.ready = true;
});
if (readyCallback) {
pushOnReady({ providerInstance }, readyCallback);
}
function done() {
if (Array.isArray(providerInstance.onReady)) {
providerInstance.onReady.forEach(fn => fn(providerInstance));
} else if (providerInstance.onReady) {
providerInstance.onReady(providerInstance);
}
if (provider.clear) {
const storeChanged = initialState !== providerInstance.store.getState();
provider.clear.forEach(fn => fn(storeChanged));
}
}
if (provider.replication && store.onReady && !store.initializedReplication) {
store.onReady(done);
} else {
done();
}
return providerInstance;
}
function getContext(fauxInstance) {
if (!fauxInstance.context) {
fauxInstance.context = {};
}
return fauxInstance.context;
}
export function getTempFauxInstance(fauxInstance, props) {
return {
props,
context: getContext(fauxInstance),
providers: getProviders(fauxInstance),
providerInstances: getProviderInstances(fauxInstance),
activeQueries: getActiveQueries(fauxInstance),
queryResults: getQueryResults(fauxInstance),
partialStates: getPartialStates(fauxInstance)
};
}
export function getFromContextOrProps(fauxInstance, key, defaultValue) {
if (typeof fauxInstance[key] === 'undefined') {
const { props } = fauxInstance;
const context = getContext(fauxInstance);
if (typeof props[key] !== 'undefined') {
fauxInstance[key] = props[key];
} else if (typeof context[key] !== 'undefined') {
fauxInstance[key] = context[key];
} else {
fauxInstance[key] = defaultValue;
}
}
return fauxInstance[key];
}
export function getProviders(fauxInstance) {
return getFromContextOrProps(fauxInstance, 'providers', {});
}
export function getProviderInstances(fauxInstance) {
return getFromContextOrProps(fauxInstance, 'providerInstances', {});
}
export function getActiveQueries(fauxInstance) {
return getFromContextOrProps(fauxInstance, 'activeQueries', {});
}
export function getQueryResults(fauxInstance) {
return getFromContextOrProps(fauxInstance, 'queryResults', {});
}
export function getPartialStates(fauxInstance) {
return getFromContextOrProps(fauxInstance, 'partialStates', {});
}
export function getFunctionOrObject(fauxInstance, key, defaultValue = null) {
if (typeof fauxInstance[key] !== 'undefined') {
return fauxInstance[key];
}
let value = fauxInstance.props[key];
if (typeof value === 'function') {
value = value(fauxInstance);
}
fauxInstance[key] = value || defaultValue;
return fauxInstance[key];
}
export function getQueries(fauxInstance) {
if (getQueries.disabled) {
return false;
}
if (typeof fauxInstance.queries !== 'undefined') {
return fauxInstance.queries;
}
const { props, relevantProviders } = fauxInstance;
const providers = getProviders(fauxInstance);
const query = getQuery(fauxInstance);
let queries = getFunctionOrObject(fauxInstance, 'queries');
let hasQueries = false;
if (query) {
// we need to map the query to relevant provider(s)
if (!queries) {
queries = {};
} else if (typeof props.queries !== 'function') {
queries = { ...queries };
}
for (let key in providers) {
let provider = providers[key];
let queryKeys = getRelevantKeys(provider.reducers, query);
if (queryKeys.length) {
// provider is relevant, so we map it within the queries object
if (!queries[key]) {
queries[key] = {};
}
for (let queryKey of queryKeys) {
queries[key][queryKey] = query[queryKey];
}
}
}
}
for (let key in queries) {
let query = queries[key];
if (typeof query === 'function') {
queries[key] = query(fauxInstance);
}
// make sure each provider is instantiated
instantiateProvider(fauxInstance, providers[key]);
hasQueries = true;
}
if (!hasQueries) {
queries = null;
if (props.query) {
props.result = null;
}
if (props.queries) {
props.results = {};
}
}
fauxInstance.queries = queries;
return queries;
}
export function getQuery(fauxInstance) {
return getFunctionOrObject(fauxInstance, 'query');
}
export function getQueryOptions(fauxInstance) {
return getFunctionOrObject(fauxInstance, 'queryOptions');
}
export function getQueriesOptions(fauxInstance) {
return getFunctionOrObject(fauxInstance, 'queriesOptions', {});
}
// gets all `handleQuery` functions within replicators
export function getQueryHandlers(provider) {
const queryHandlers = [];
let { replication } = provider;
if (replication) {
if (!Array.isArray(replication)) {
replication = [ replication ];
}
for (let {
replicator, reducerKeys, baseQuery, baseQueryOptions
} of replication) {
if (replicator) {
if (!Array.isArray(replicator)) {
replicator = [ replicator ];
}
for (let { handleQuery } of replicator) {
if (handleQuery) {
queryHandlers.push({
handleQuery,
reducerKeys: reducerKeys || Object.keys(provider.reducers),
baseQuery,
baseQueryOptions
});
}
}
}
}
}
return queryHandlers;
}
export function getMergedResult(mergedResult, result) {
if (Array.isArray(result)) {
return [ ...(mergedResult || []), ...result ];
} else if (
result && typeof result === 'object' && result.constructor === Object
) {
return { ...(mergedResult || {}), ...result };
} else if (typeof result !== 'undefined') {
return result;
} else {
return mergedResult;
}
}
export function resultsEqual(result, previousResult) {
if (result === previousResult) {
return true;
}
if (typeof result !== typeof previousResult) {
return false;
}
if (Array.isArray(result)) {
if (Array.isArray(previousResult)) {
let i = 0;
const length = result.length;
if (length !== previousResult.length) {
return false;
}
while (i < length) {
if (!shallowEqual(result[i], previousResult[i])) {
return false;
}
i++;
}
} else {
return false;
}
} else if (Array.isArray(previousResult)) {
return false;
}
return shallowEqual(result, previousResult);
}
// this is admittedly a mess... :(
// we're accounting for both synchronous and asynchronous query handling
// where asynchronous results will override the synchronous results
export function handleQueries(fauxInstance, callback, previousResults) {
let doUpdate = false;
const queries = getQueries(fauxInstance);
if (!queries) {
if (callback) {
callback(doUpdate);
}
return false;
}
const { props } = fauxInstance;
const context = getContext(fauxInstance);
const { result: originalResult, results: originalResults } = props;
let validQuery = false;
// for determining whether or not we should update
if (!previousResults) {
previousResults = { ...props.results };
}
// get what we need to handle the queries
const query = getQuery(fauxInstance);
const queryOptions = getQueryOptions(fauxInstance);
const queriesOptions = getQueriesOptions(fauxInstance);
const activeQueries = getActiveQueries(fauxInstance);
const queryResults = getQueryResults(fauxInstance);
const partialStates = getPartialStates(fauxInstance);
const providers = getProviders(fauxInstance);
const providerInstances = getProviderInstances(fauxInstance);
// TODO: we should probably do something better at some point
const setPartialStates = (provider, result) => {
if (!result || typeof result.map !== 'function' || !isServerSide) {
return;
}
for (let partialState of result) {
let providerKey = provider.key;
if (typeof providerKey === 'function') {
providerKey = providerKey({ props: partialState, context });
}
if (providerKey !== null && !providerInstances[providerKey]) {
partialStates[providerKey] = partialState;
}
}
};
// most queries should be async
let queryCount = Object.keys(queries).length;
const queryClear = () => {
if (--queryCount === 0) {
// at this point we have all our results
if (callback) {
callback(doUpdate);
}
}
};
// merge each result into `props.result` if using `props.query`
const setMergedResult = result => {
if (props.query) {
props.result = getMergedResult(props.result, result);
}
};
// go ahead and set null value if using `props.query`
if (props.query) {
props.result = null;
}
// results start out as an empty object
props.results = {};
// check each query
Object.keys(queries).forEach(key => {
const provider = providers[key];
const queryHandlers = getQueryHandlers(provider);
let handlerCount = queryHandlers.length;
// no handlers? Y U DO DIS?
if (!handlerCount) {
queryClear();
return;
}
validQuery = true;
// let the provider know we're waiting for all of the handlers to finish
if (Array.isArray(provider.wait)) {
provider.wait.forEach(fn => fn());
} else if (provider.wait) {
provider.wait();
}
// here we determine the `resultKey` used for caching the results
// in the current context
const query = queries[key];
const options = queryOptions || queriesOptions[key] || {};
const resultKey = JSON.stringify({ query, options });
const queryResult = queryResults[resultKey];
let queryResultExists = typeof queryResult !== 'undefined';
// subscribe to all of this provider's instances' stores for requeries
subscribeToAll(key, provider, fauxInstance, resultKey, query, callback);
// result handler for both sync and async queries
const setResult = result => {
if (!activeQueries[resultKey]) {
console.warn(
`setResult was called but the following query is no longer active:`,
{ query, options }
);
return;
}
const first = activeQueries[resultKey].values().next().value;
const leader = setResult === first;
const previousResult = queryResultExists
? queryResult
: previousResults[key];
const { asyncReset } = setResult;
// if new result, set `doUpdate` flag
if (!doUpdate && !resultsEqual(result, previousResult)) {
doUpdate = true;
}
// a special `asyncReset` flag is set if async handler is detected;
// we want async results to override sync
if (asyncReset) {
// this should only occur once, at the start of setting async results
setResult.asyncReset = false;
props.results = {};
if (props.query) {
props.result = null;
}
}
props.results[key] = result;
previousResults[key] = result;
queryResults[resultKey] = result;
setMergedResult(result);
// if this handler is the leader, we pass the result onto the others
if (leader && activeQueries[resultKey]) {
activeQueries[resultKey].forEach(otherSetResult => {
if (otherSetResult !== setResult) {
otherSetResult(result);
}
});
}
if (--handlerCount === 0) {
// handler is done, so remove self
activeQueries[resultKey].delete(setResult);
// if there are no handlers remaining, this query is no longer active
if (!activeQueries[resultKey].size) {
delete activeQueries[resultKey];
setPartialStates(provider, result);
}
// no more query handlers, so let the provider know we're done
if (Array.isArray(provider.clear)) {
provider.clear.forEach(fn => fn(doUpdate));
} else if (provider.clear) {
provider.clear(doUpdate);
}
// and this query is clear
queryClear();
// we want to remove the cached query results on the client/tests
// so that it will always update
if (!isServerSide || isTesting) {
delete queryResults[resultKey];
}
}
};
const setError = error => {
console.error(error);
};
// this query is currently taking place, make the handler follow the leader
if (activeQueries[resultKey]) {
activeQueries[resultKey].add(setResult);
return;
}
// this is a new query, so this handler is a leader;
// other handlers matching this `resultKey` will check
// if the query is active and become a follower
activeQueries[resultKey] = new Set();
activeQueries[resultKey].add(setResult);
// already have our query result cached?
// no point in calling any handlers; go ahead and set the result
if (queryResultExists) {
handlerCount = 1;
setResult(queryResult);
return;
}
// now we need to run the query through each `handleQuery` function,
// which may or may not be synchronous
queryHandlers.forEach(
({ handleQuery, reducerKeys, baseQuery, baseQueryOptions }) => {
// we can determine whether or not its synchronous by checking the
// `handlerCount` immediately after `handleQuery` is called
const handlerCountBefore = handlerCount;
// normalize the query + options so that people can be lazy
const normalizedQuery = { ...baseQuery, ...query };
const normalizedOptions = { ...baseQueryOptions, ...options };
if (typeof normalizedOptions.select === 'undefined') {
normalizedOptions.select = reducerKeys === true
? Object.keys(provider.reducers)
: reducerKeys;
} else if (!Array.isArray(normalizedOptions.select)) {
normalizedOptions.select = [ normalizedOptions.select ];
}
if (Array.isArray(normalizedOptions.select)) {
for (let reducerKey in normalizedQuery) {
if (normalizedOptions.select.indexOf(reducerKey) < 0) {
normalizedOptions.select.push(reducerKey);
}
}
}
handleQuery({
query: normalizedQuery,
options: normalizedOptions,
setResult,
setError
});
if (handlerCount === handlerCountBefore) {
// asynchronous query, so we set the `asyncReset` flags to true
// only if they haven't been set to false yet
activeQueries[resultKey].forEach(setResult => {
setResult.asyncReset = setResult.asyncReset !== false;
});
}
}
);
});
if (!validQuery) {
props.result = originalResult;
props.results = originalResults;
}
return validQuery;
}
function subscribeToAll(
key, provider, fauxInstance, resultKey, query, callback
) {
if (isServerSide || !fauxInstance.props.__wrapper) {
return;
}
fauxInstance.requeryCallback = callback;
if (!provider.subscribedFauxInstances) {
provider.subscribedFauxInstances = {};
}
if (provider.subscribedFauxInstances[resultKey]) {
provider.subscribedFauxInstances[resultKey].add(fauxInstance);
return;
}
const subscribedFauxInstances = new Set();
provider.subscribedFauxInstances[resultKey] = subscribedFauxInstances;
subscribedFauxInstances.add(fauxInstance);
let timeout;
const requery = providerInstance => {
clearTimeout(timeout);
timeout = setTimeout(() => {
for (let fauxInstance of subscribedFauxInstances) {
if (fauxInstance.props.__wrapper.unmounted) {
subscribedFauxInstances.delete(fauxInstance);
} else {
handleQueries(fauxInstance, fauxInstance.requeryCallback);
}
}
});
};
pushOnReady({ provider }, requery);
if (!provider.subscriber) {
provider.subscriber = {};
}
const subscriber = provider.subscriber[key];
provider.subscriber[key] = (providerInstance, providerInstance2) => {
if (subscriber) {
subscriber(providerInstance, providerInstance2);
}
if (shouldRequery(providerInstance, query)) {
requery(providerInstance);
}
};
if (provider.instances) {
provider.instances.forEach(providerInstance => {
providerInstance.store.subscribe(() => {
if (shouldRequery(providerInstance, query)) {
requery(providerInstance);
}
});
});
}
}
function shouldRequery(providerInstance, query) {
const currentState = providerInstance.store.getState();
const { lastQueriedState } = providerInstance;
providerInstance.lastQueriedState = currentState;
if (!lastQueriedState) {
return true;
}
if (currentState !== lastQueriedState) {
if (typeof query === 'object') {
for (let key in query) {
if (currentState[key] !== lastQueriedState[key]) {
return true;
}
}
} else {
return true;
}
}
return false;
}