-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
rfc: Deterministic states & caching #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
|
Someone is attempting to deploy a commit to a Personal Account owned by @tmm on Vercel. @tmm first needs to authorize it. |
|
This looks great to me! Amazing work! |
|
Thank you for your rfc! As someone who is using One of the many strong points about this library is that its only dependency is ref: |
|
This proposal looks fantastic, great work! |
|
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/zoo/wagmi/HzcdRtWq1SUwncuomxPg54MZDmdG |
tmm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks great! Some details to chat about.
Consistent Defaults
Better to have opinionated defaults for specific hooks (i.e. useEnsAvatar default enabled, useFeeData default disabled, etc.) versus the same defaults?
Think it makes sense that “we” (the library authors) use our industry knowledge to help assist folks around cache duration/location, etc., but would it be too astonishing if defaults aren't consistent?
Testing
How do we want to handle tests for the increased surface API? Do we test config arguments that are React Query config? Probably doesn’t make sense to test all the options that are just passed through to React Query, but maybe some.
Dependency Type
Should React Query be a core or peer dependency? ethers.js is a peer dependency. Could see a case for either approach.
Additional Configuration
Moving forward, how do we decide what React Query features make it into wagmi or not? Should we support additional hook-level configuration (i.e. retry, refetchOnWindowFocus, etc)?
Do we want to expose query keys (or functions for getting array keys)? Definitely more pro, but could help folks tune things to their liking
|
|
||
| ## A note on hook naming | ||
|
|
||
| I feel like the naming of read hooks such as `useAccount`, `useBalance`, `useNetwork` are fine, however I reckon it would be nice to be more explicit with action hook naming to include intending verb (i.e. `useTransactionSend` instead of `useTransaction`). This also aligns with existing hooks such as `useContractWrite` and `useEnsLookup`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree about the naming change for useTransaction (and action hooks in general)
| }) | ||
| ``` | ||
|
|
||
| I feel like if we flip the logic, and introduce an `enabled` argument, this would make DX a bit easier to follow & reason about: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
100% - thought about doing this before and makes a lot more sense.
| - `isReloading`: A flag to indicate that the data is loading in the background, and showing cached success/error data in the meantime (meaining this flag can be truthy when isSuccess/isError is truthy). | ||
| - `isSuccess`: A flag to indicate the data has been fetched successfully. | ||
| - `isError`: A flag to indicate that an error was thrown upon fetching the account | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also expose status too? Think it's a useful prop for folks that don't like booleans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, great idea.
49eadcb to
4ce2969
Compare
Yeah absolutely. I guess the objective of this PR is to essentially make the UX of wallet & contract interaction better, and to achieve this, we need to enforce opinionated defaults. However, it could be a little confusing from a DX perspective if we have inconsistent defaults (some hooks are cached, while some are not), I guess there are a couple of alternatives at the top of my mind:
Yeah true, maybe we could just test the more "critical" functionality like the new state vars & ensuring caching works as intended?
If
Yeah, definitely see an advantage in exposing more React Query features, but didn't want the RFC to feel too overwhelming. Maybe we could extend the config arg to accept RQ config that make sense for this library?
Yeah, totally, I think it would be beneficial if consumers want to programmatically interact with the
Not sure if that should be in-scope for an MVP of this? Or should I extend this RFC to touch the surface of this? |
Yeah, the alternative of having separate entrypoints has definitely been a valid consideration! But I think the downsides of this approach outweighed the upsides. We need to keep in mind that some users wouldn't be using an async state lib anyway, so having the option of picking between using |
|
My 2 cents re: Consistent Defaults
|
Let's move forward with them. As long as they are clear in the docs, these can be a helpful assist. Can see how folks are tuning these once it's released and can adapt accordingly too.
Testing state vars and that caching is working as intended seems appropriate.
Agree with the trade-offs. Let's make it a core dependency.
Agree, can start with the basics and see what makes sense later. Can leave an advanced parts (i.e. exposing query keys) out of scope for now.
Definitely think multiple entrypoints for React Query, SWR, etc. would have been cool, but not sure how realistic it is to build and maintain. Right now, it seems like React Query has more energy around it from wagmi contributors. That said, keeping things lightweight resonates with me (why I suggested entrypoints) and I've also been in situations before where I was frustrated with decisions maintainers made for OSS projects. I'm not sure we can make everyone happy with this individual decision, but whatever direction we go is a step towards making wagmi the best React library for Ethereum. @shunkakinoki really appreciate your feedback and happy to chat about this further async or live if you want. |
|
Going to give the weekend for folks to share additional thoughts :) In the meantime, we can start chatting about implementation.* We'll need to update the hooks, tests, docs, and examples. I'm down to work on this and would be great if there were one or two others that were also interested. Could make sense for folks to work on specific hooks and update them across surfaces. As part of this work, I'm also down to expand the core team and give folks access to the repo. Anyone interested in being involved in implementation? What do we think about assigning hooks to work on versus doing things more horizontally across surfaces? *We probably also want to figure out the best way to message changes, but can do that later. |
|
@tmm I can give some hours to the project to help out with implementation, doc updates etc. Wont be available next week but I can start from the week after. |
Yeah, I'm defs down to help out with this. |
|
I'm also def down to help! Also would love for the opportunity to chat and contribute regarding this. cc: thank you to @jxom for the wonderful rfc! |
Speaking of, i do still stand by the above As of the moment (2/10/2022), npm downloads of react query vs swr ration is roughly about 2x As a |
|
Looking really good! It all makes sense and will make the API more consistent. I think the best thing about React Query is the way it introduces a standard structured object to represent data fetching, making it possible to compose functions / hooks together, as promises do already for simple async operations. Regarding using react-query vs. swr, I checked some data about the size of the library: As said in the RFC, react-query will add ~13kb to the library (which probably won’t benefit from tree shaking, see “composition” on the bundlephobia page). swr would add ~4.1kb, also not benefitting from tree shaking. wagmi is currently at 74.6kb and also requires Ethers (189kb currently) and react / react-dom (~42kb), making any dapp using wagmi ~306kb at the minimum (pre tree shaking / optimisations).This is much better than what used to be the standard not so long ago for dapps, but I think we still have a long way to go to get to a level that would be considered ideal. As an example, I decided to use swr for use-nft, to keep its size at the absolute minimum (even though I have a preference for React Query which I use in my apps). But use-nft is a small library (5kb), and even swr represents the biggest part of its size. I am not sure this is as relevant for wagmi, since either swr or react-query would both represent a small share of the total size of the library in its current state, and other changes like moving to BigInt (rather than BN.js / BigNumber) would have a much bigger impact. I think an important question for wagmi would be to consider React Query as an internal dependency that we could replace later (e.g. by moving to swr to optimize the library size further, after having picked the low hanging fruits), or as something that we want to expose externally, which would effectively lock it in the library. |
|
One idea that came up in conversation earlier was what if wagmi “core” was just some basic functions that wasn’t really tied to hooks or a particular data mgmt library, this would mean it could be used it any context. Then you could build some wrapper that would essentially be a thin abstraction around react-query or swr that simply consumed the core library as async functions. The one thing I havnt figured out is how you would share state as the library currently uses a context |
|
Yeah, just to give a bit of background, I guess I proposed However, thinking about it, I agree w/ @shunkakinoki & @bpierre that there should be an option to still consume this library at a complete minimal surface, as there could well be minimalistic consumers. I really think @sammdec has a valid point that we could extract the core functionality out of the hooks, and consumers can consume that in whatever async state lib they want (swr, react async, react loads, etc). Which is similar to having multiple entrypoints, but a little bit more explicit. But if we were to move this to be more agnostic, and moved out of hooks into functions, I think we would need to put a bit of thought into how we deal with reactions & events (such as switching wallets, chains, etc). Perhaps there could be:
Having a Could be something like so for import { fetchAccount, watchAccount } from 'wagmi/core';
import useSWR from 'swr';
function Example() {
const { data, mutate } = useSWR('account', fetchAccount);
useEffect(() => {
// If an event is emitted to change the account, then broadcast a revalidation.
const unwatch = watchAccount({ onChange: mutate });
return () => unwatch();
}, []);
...
}Or even another async state lib, like import { fetchAccount, watchAccount } from 'wagmi/core';
import { useAsync } from 'react-async';
function Example() {
const { data, reload } = useAsync({ promiseFn: fetchAccount });
useEffect(() => {
// If an event is emitted to change the account, then reload the data.
const unwatch = watchAccount({ onChange: reload });
return () => unwatch();
}, []);
...
}I guess it could also be nice to have a react hook to make import { fetchAccount, useWatchAccount } from 'wagmi/core';
import useSWR from 'swr';
function Example() {
const { data, mutate } = useSWR('account', fetchAccount);
useWatchAccount({ onChange: mutate });
...
}We could even use the core lib in another framework, like Svelte: <script>
import { fetchAccount, watchAccount } from 'wagmi/core';
// with svelte-query
import { useQuery } from '@sveltestack/svelte-query';
const accountRecord = useQuery('account', fetchAccount);
watchAccount({ onChange: $accountRecord.refetch });
// with svelte swr
import { useSWR } from 'sswr';
const { data, mutate } = useSWR('account', fetchAccount);
watchAccount({ onChange: mutate });
</script>
<div>
...
</div>@tmm might have better thoughts on this, |
|
What about using something like zustand to contain the state and events. https://github.com/pmndrs/zustand#using-zustand-without-react |
I completely agree with the idea of extracting the core functionality out of the hooks, this is the main idea behind this proposal I made the other day: #140 (alongside replacing the error prone hybrid functions like On the npm modules & exports strategyI think having a core version of the library ( I think wagmi could be opinionated about having first class support for React, and keep providing the React + core exports of the library under the main module ( Note: if we take care of having the library fully ready for tree shaking (I can help with this), and for consumers using a bundler with tree shaking enabled, then using the core exports from On exposing React Query featuresOtherwise I agree with @jxom, I think it makes sense to use React Query especially since the size won’t be impacted much by it (vs. swr) in the current state of things. But do we want to expose specific React Query features, or only a subset of its result object? e.g. the proposed API for storage persistence is coming straight from React Query, which means that any change in React Query would require wagmi to follow and break its own API. Another possibility would be to expose these features but express them in a more “wagmi (i.e. simplified) way”: import { Provider } from 'wagmi'
const App = () => (
<Provider persist> // uses localStorage.window if `true`
…
</Provider>
)Which should be easy to keep maintaining even if React Query completely changes the way persistence is expressed in their API. Full access to React Query?What would you guys think about (optionally) letting users gain as much access as possible to react-query? Users with advanced cases could define their own const [{ data, error, loading }] = useBalance({
addressOrName: name || address,
formatUnits: 'gwei',
enabled: Boolean(name || address),
reactQuery: {
refetchOnReconnect: false, // advanced option requiring to pass `reactQuery`
enabled: false, // takes priority over `enabled` above
}
})And we could do the same in the returned values: const [{ data, error, loading, reactQueryResult }] = useBalance({
addressOrName: address,
})
// reactQueryResult would contain the full object as returned by `useQuery()`I just wanted to put this here to discuss this possibility, but I am not sure how I feel about it personally. It would certainly give users great flexibility over the react-query behavior, but it would make parts of the wagmi API dependent on the evolution of the React Query API. It would also make the implementation less flexible, e.g. moving from I feel a bit differently about letting users define their own import { Provider as Wagmi, QueryClient } from 'wagmi'
const queryClient = new QueryClient({ /* … */ })
const App = () => (
<Wagmi queryClient={queryClient}>
…
</Wagmi>
)…but an issue is that it makes little sense to expose React Query as a peer dependency?By requiring users to install react-query themselves, it would have the following advantages:
But it would also bring some issues:
So here too, my preferred approach would be to not do this for now and keep React Query as an internal dependency, while keeping this as a possibility for later (after the refactoring). |
|
@jxom Apologize for the late reply! Completely agree with the below!
right now, my code for fetching ens integrated with swr looks like the below would be very nice if we could extract the Just as a sidenote, if there is ever a need of a Again, thank you guys for your wonderful ideas |
|
thank you for this great proposal @jxom, we recently started integrating wagmi to handle wallet connections & signer / provider management into all of our projects @ thirdweb, we also are heavy users of react-query so this would be the perfect solution for us. (when I first saw wagmi the api interface reminded me a lot of react-query, to the point that I was actually surprised it was not built on top of it.) needless to say we'd be excited to lend a hand helping to implement these changes, contributing fixes and improvements & long-term maintenance since we would likely have ended up forking wagmi eventually instead. so yay for react-query 👍 couple of loose (weekend) thoughts (coming from a perspective of our own use-case)
thanks again for the work on this so far, also was helpful to read the discussion above, we'll be watching this closely to see when and where and how we can help out! |
|
Response to @bpierre:
Yep, agree with this. I think a set of scoped packages will be nice. So essentially, we could have the following:
* = build this now
Yeah, all this sounds good! Just to clarify, the proposed API for storage persistance is already "wagmi simplified" (I don't expose // By default, `persist` uses the local storage persistor
const App = () => (
<Provider persist>
<YourRoutes />
</Provider>
)
///////////////////////////////////////////////////
// Override `persist` with a custom persistor (session storage)
import { Provider, createPersistor } from 'wagmi';
const sessionStoragePersistor = createPersistor({ storage: window.sessionStorage })
const App = () => (
<Provider persist={sessionStoragePersistor}>
<YourRoutes />
</Provider>
)
Sounds good to me, an escape-hatch to allow a user to specify any React Query argument or return value sounds good. I guess we have to keep in mind that if we expose a
Yeah agree with all the trade-offs there. I am definitely for having React Query being an internal dependency – main reason is because it won't put such a reliance burden on this lib (if React Query upgrades to v4 - which we may not support straight away - then consumers will be waiting for us to migrate to RQ v4 before they can upgrade RQ to v4). |
|
@jxom |
|
@jxom Is it worth spinning up a new branch that creates the basic folder, build structure for wagmi/core, wagmi/react, hopefully that should lower the setup complexity around contributing to this new setup |
|
Quick thoughts (more coming soon): |
|
I've been following this discussion for the past few days, looks very exciting. There hasn't been any update in the past 5 days, just making sure if this is still happening? I'm available and would be more than happy to help buidl! Cheers. |
419ca6c to
0961e10
Compare
|
The This will also make it much easier for someone to add |
|
Going to merge this in for now. We now have an active development branch for the new Right now, we are working on migrating the If anyone wants to help with a swr adaptor, or even a svelte or vue client for wagmi, feel free to reach out! With the new foundation of |
See formatted