--- tags: rage-report, v3 --- # Rage Report: Polling, Subscribing, urql, and Apollo ## Goals - Figure out if we can subscribe to events in the subgraph instead of polling - **IF** we can subscribe, does it work well? - Is it reliable? - What are the UX implications of seeing real-time updates from the subgraph (worth noting that real-time doesn't require subscribers) - urql is a simpler, lightweight, and more flexible package for GQL. - Is this true - Apollo was trouble, but once we had removed caching, JSX queries, and any sort of state, it became really reliable. Will urql be the same? Will urql make it so that we don't have to remove all state? - If we decide on real-time and/or having clients persist in state, how will this impact the architecture of the app and state management? ## Experiment Design (How will I answer the above question ) - Hack a static query fetch for proposals in the Rage Core App - Try to subscribe to subgraph events: - https://formidable.com/open-source/urql/docs/basics/core/#subscribing-to-results - If subscribers don't work, hack a universal block-time poller as suggested here: - https://thegraph.com/docs/en/developer/distributed-systems/#polling-for-updated-data - In short, this approach involves polling the graph at regular intervals, regardless of user action. - Whatever works best, try to break it. Report on where the challenges may be. - Report on ``urql``. Was it easy to use. Would it work well inside a library (it's only 20kb)? Is it as good or better than Apollo? - If real-time is feasible and reliable, talk to UX about it and see how that may change UX around the app (being able to see other users updates, seeing if we're in sync or not, etc) ## Expected Results - Either subscribers or block# polling will work and be reliable for use - Which means real-time will likely be a likely choice. - It could also remove a major friction point from the app (setting up polls, or dealing polls timing out) - urql will be easier to set up. --- ## Actual Results - A query took seconds to set up with urql. It's very intuitive. I'd recently set up Apollo up on another repo (Gatsby DAO poster template), and there were odd bugs with nonsensical error messages. - Subscriber was dead. Could not get the action to fire. My best guess is that it's not updating becuase we're not mutating the GQL server directly, which means we're never triggering an event. I can investigate on the Graph discord more. ![](https://i.imgur.com/RN0PY9f.png) Not holding my breath though ![](https://i.imgur.com/OSbR8EG.png) - Setting up a Poll the way that theGraph suggested in the docs was sort of awkward. https://thegraph.com/docs/en/developer/distributed-systems/#polling-for-updated-data - I had thought it was a way to diff by block, but that would be foolish as well. What they are saying essentially is that we should run a poll every estimated block interval, but that's pretty data intensive. Think of all the data our app pulls in. Now multiply that by how many users are on and pull it in every time there's a new block. This isn't really long-term scalable for . Would probably be great for smaller apps though. ## Next Steps ### My suggested solution. I propose that we poll on an interval of 10s for the most recent event transaction. On the app, we compare old event TX to new. If there's a different hash, we trigger refetches on queries that are currently open (currently part of the view) ```js export const APP_CLOCK = ` { eventTransactions(first: 1, orderBy: createdAt, orderDirection: desc) { id createdAt } } `; export const simpleFetch = async <T>({ setter, shouldUpdate, query, resolver, }: ReactThunk<T>) => { try { const client = createClient({ url: url as string, }); const result = await client.query(query).toPromise(); const resolved = resolver(result); if (resolved && shouldUpdate) { console.log('resolved', resolved); setter(resolved); } } catch (error) { console.error(error); } }; export const startAppClock = <T>({ setter, shouldUpdate, }: { setter: ReactSetter<T>; shouldUpdate: boolean; }) => { const pollID = setInterval(() => { console.log('tick'); simpleFetch({ setter, shouldUpdate, query: APP_CLOCK, resolver: (result: OperationResult) => result?.data?.eventTransactions[0]?.id, }); }, 10000); return { unsub: () => clearInterval(pollID) }; }; ``` This can be as simple as storing the most recent eventTransaction hash in global state and adding that variable to the dependency array of a useEffect. When the value changes, it refetches. ```js const [proposals, setProposals] = useState<null | []>(null); const [lastTX, setLastTX] = useState<string>(''); useEffect(() => { let shouldUpdate = true; const { unsub } = startAppClock({ setter: setLastTX, shouldUpdate, }); return () => { shouldUpdate = false; unsub(); }; }, []); useEffect(() => { let shouldUpdate = true; simpleFetch({ setter: setProposals, shouldUpdate, query: DAO_PROPOSALS, resolver: proposalResolver, }); return () => { shouldUpdate = false; }; }, [lastTX]); // PUTTING lastTX HERE IS HOW THE MAGIC HAPPENS ``` In doing so we can see if any new transactions have been initiated on the subgraph. If a TX updates data on a DAO, our data refetches. #### Pros and Reasons for - Pretty damn simple. - Can see updates from other users too - Can see results of TXs sent from outside our app. - It gets easier too. We can package each useEffect up into a hook that devs can use to abstract this entire process away. - This is scalable too. We can poll more than one subgraph at a time. #### Cons and reasons against - Long wait times (same-ish as v2). - Redux might make packaging it up into a hook a little but more difficult. - If subgraph is down, then it takes a long time to update. Users don't always know why their data didn't update. ### Consider not polling graph at all Keating mentioned that we should consider not polling the graph at all #### Pros and Reasons for - We get to use ether.js subscribers. They actually work too. - Subscriber updates when we need to. On other user updates to. - We don't actually need to get data from the subgraph. If the TX passed, then we know that the subgraph will pick it up eventually. - All of which is to say that we'd save 50% off the wait times. ### Cons and reasons against: - Biggest con for me is the optimistic state changes. For several reasons - Requires more ways to change state (overhead) - Requires us to try and replicate how the Graph changes data - I think some of the data the graph uses to index isn't available on the front end - Every action would now require us to build this stuff out. - Some TXs make changes across many domains. Redux allows us to make those changes accurately, but doing it accurately would be tough. - Data could exist, but then it could be gone if the user refreshes. - Doesn't really solve the subgraph problem if the outage is for a significant amount of time. (closes tab or refreshes data) Could even be more confusing.