Skip to content

Instantly share code, notes, and snippets.

@danReynolds
Last active May 7, 2020 17:12
Show Gist options
  • Save danReynolds/5f3bb6dbb7aaae7a9417e44a3b44fef0 to your computer and use it in GitHub Desktop.
Save danReynolds/5f3bb6dbb7aaae7a9417e44a3b44fef0 to your computer and use it in GitHub Desktop.
## First update to a Component with useQuery
1. useBaseQuery
useBaseQuery is a React hook that sets up a QueryData object from which data is delivered from a query to the component. On first render, it memoizes
the options for the query, variables etc then calls execute on its internal QueryData object to start the process.
2. QueryData
`execute` receives the query parameters, variables etc from the useBaseQuery hook, and if not set up, sets up a watchQuery for the given query arguments in `initializeObservableQuery`.
3. QueryManager
`watchQuery` creates a new ObservableQuery (subclass of Zen Observable) with a back reference to the QueryManager itself and the options passed to watchQuery for the query to perform from QueryData. The QueryManager then gets the query by Id from its queryMap of query Ids
to QueryInfo objects. If no entry in the map yet exists for that query (if this is the first watcher of this query for example) then it creates a new QueryInfo and entry in the map. It then calls `init` on the QueryInfo, passing in the ObservableQuery.
4. QueryInfo
QueryInfo `init` is passed the ObservableQuery and adds it as a listener of the QueryInfo object. The QueryInfo will notify its listeners when it receives cache updates that dirty its particular query.
5. QueryData
QueryData continues its `execute` method after setting up the watchQuery and if first execution, then calls `getExecuteResult`->`getQueryResult` which basically returns a barebones result object because it is not yet subscribed to any query yet
on first execute. After getting this result, it then calls `startQuerySubscription` which subscribes to the ObservableQuery returned by QueryManager `watchQuery` in `startQuerySubscription` via ObservableQuery.subscribe, passing in an observer object that sets up the ObservableQuery's `next` to call the `onNewData` handler.
6. ObservableQuery
Subscribing to the ObservableQuery triggers its custom onSubscribe handler which adds the observer passed to `subscribe` to its internal list of observers. If this is the first observer added to that list, it calls `reobserve()`
which creates a singleton `Reobserver` via `getReobserver`->`newReobserver`, passing in its private observer which relays results to its list of observer, and also passing in a custom fetch method to be used by the Reobserver. The custom fetch
calls `fetchQueryObservable` on the QueryManager, for triggering the execution of the query.
7. Reobservable
The new reobserver is constructed with nothing much happening in the constructor.
8. ObservableQuery
Immediately after the Reobservable is constructed by the first subscriber, it calls `reobserve` on it.
9. Reobservable
Reobservable's `reobserve` calls the custom `fetch` passed to it from the ObservableQuery, calling the QueryManager's `fetchQueryObservable`.
10. QueryManager
The QueryManager's `fetchQueryObservable` gets the QueryInfo object by query Id in its keyed queryId map registered for the query. It then calls `fetchQueryHelper` with the QueryInfo instance and query options.
`fetchQueryHelper` initializes the QueryInfo by calling `init` on it, this time without an associated observabe query, so no new listener is added to the QueryInfo. It then calls `updateWatch`
on the QueryInfo.
11. QueryInfo
QueryInfo's `updateWatch` set's up its subscription to the `cache` via `cache.watch` so that it will be notified whenever the cache has written to its query.
12. QueryManager
fetchQueryHelper then switches on the `fetchPolicy` of the query to determine whether it needs to serve the query result from the cache, network or both.
If it should execute the query against the cache only or cache first, it gets the cache result via `resultsFromCache` which calls `cache.diff` for the query arguments and returns the data in an Observable wrapper.
If it should execute the query only or additionally against the network, it calls `getResultsFromLink` which sends the query to the links and wraps the result in an observable with a custom observer that will as a side-effect
of receiving data from the link, also update the query's QueryInfo object.
It then returns the wrapped observable from the cache and/or the network back to fetchQueryObservable which passes them in as an array of observables
to a new Concast, a higher order observable which concats and multicasts them, like RxJS concat of a list of observabes then passed to an RxJS BehaviorSubject. `fetchQueryObservable` returns that concast.
13. Reobserver
The reobserver receives this concast back from its passed fetch function, and adds its passed ObservableQuery observer as an observer of the concast.
14. ObservableQuery
When the concast of cache and/or network results pushes data, the ObservableQuery's observer subscribed to it via the reobserver relays the pushed values to each of its observers, triggering their next handlers.
15. QueryData
This triggers QueryData's subscription to the ObservableQuery, which checks to see if the data result pushed to it has changed and if so, calls onNewData. Note that it does not do anything with the data, onNewData is not for delivering
the data to the component, but just to signal it to ask for its updated data.
16. useBaseQuery
useBaseQuery's onNewData function calls `forceUpdate` on the component, which causes the component to re-render by incrementing a ticker setup with useReducer. The component's memoized options are invalidated by the
update to the ticker, causing it to once again call it's QueryData's `execute`.
17. QueryData
This time, since it is not the first time QueryData's execute is called, it does not need to setup the watchQuery, but instead just calls `getExecuteResult`->`getQueryResult` to read the current result from it's current ObservableQuery it has subscribed to via `getCurrentResult`.
18. ObservableQuery
ObservableQuery's getCurrentResult calls its getCurrentQueryResult function which reads the current data for the query from `cache.diff` and returns it if complete
or conditionally returns it if the query is allowed to return partial data. getCurrentResult checks if there are errors, and if not, returns
the data.
19. QueryData
QueryData's getQueryResult receives the query result and if it is marked complete returns it to useBaseQuery, if not complete and partialRefetch is supported, it returns a loading result and immediately
tries to fetch the query again by calling ObservableQuery's refetch. It stores the current result as its previousData.
20. useBaseQuery
useBaseQuery receives the result from getQueryResult and returns it as its return value to be used by components using useQuery.
## Subsequent updates via changes to useQuery options
### As a result of an active change in the component
1. useBaseQuery
When the component re-renders with new options that invalidate its execute memo, it calls its QueryData's `execute` with the updated options.
2. QueryData
QueryData's `execute` receives the updated options and calls `updateObservableQuery`, which calls `setOptions` with the new options on its ObservableQuery.
3. ObservableQuery
ObservableQuery's `setOptions` calls `reobserve` with the new options. At this point, it repeats steps 8-20, going through the reobservable to trigger its fetch, triggering QueryManager to resolve data from cache and/or network,
pushing those results to the concast in reobserver, pushing those results to ObservableQuery's observer, pushing those results to each of its observers in its list, which includes the QueryData for the component, calling its onNewData to let the component
once again know to fetch the current result.
### Subsequent updates via changes to Query useQuery is watching
1. Cache is written for query component cares about
2. QueryInfo
The query info object setup for the query by the QueryManager was setup to watch the cache via `updateWatch`. When written, it calls `setDiff` to see if the data has changed from its previous result. If so, it marks it as dirty and kicks
off a debounced timeout to notify its listeners. When the timeout expires, it calls `notify`, which iterates through its ObservableQuery listeners and calls `reobserve` on them.
At this point, it repeats steps 8-20 for the ObservableQuery the component is watching.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment