-
-
Notifications
You must be signed in to change notification settings - Fork 6
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
RFC: New Proxy API & SWR 2.0 #19
Comments
Hey thanks for writing all of this!
import { createTRPCClient } from '@trpc/client';
import { createSWRProxyHooks } from 'trpc-swr';
export const client = createTRPCClient<AppRouter>({
url: '/api/trpc',
transformer: {
// ...
},
});
export const trpc = createSWRProxyHooks(client); // New API
// Use the client instance directly
// Here createSWRProxyHooks internally creates a new TRPCProxy client
// This is for usage outside of React
const cat = await trpc.client.cats.byId.query({id: 1});
// Use the hooks
const {data: cat} = trpc.cats.byId.useSWR({id: 1}); This is nice, but we can't do this because it breaks SSR. With this code example, I like
// We are inside a React component here
const {client} = trpc.useContext(); // Native tRPC client for mutation
const {mutate} = useSWRConfig();
function doSomethingThenMutate(){
// Do something
const newCat = client.cats.byId.mutate({id: 1}, {
id: 1,
name: 'Mr. Garfield',
});
// Mutate the cache
// All hooks using this key will now have the new data
await mutate(trpc.cats.byId.getKey({id: 1}), newCat, false);
} I would still prefer if we had some way of using the proxy API here instead.
Again, instead of This also matches the
Yes, would love to have support this. The API will probably work the same as Again, thanks for outlining this! I don't have much info on what is idiomatic SWR as I don't use it that much myself, so your help is much appreciated. |
Got it! Getting started as I have a bit more time on my hands :) |
While building this I've met a slight problem. Essentially what we want is to only use a single TRPCClient instance, however, we cannot follow the API used by These are not alternatives, both can be used. // Option 1
export const trpc = createSWRProxyHooks<AppRouter>(config); // Similar to current API, generates the client
// Option 2 - If you need the native client for whatever reason
const client = createTRPCClient<AppRouter>(config); // From @trpc/client
export const trpc = createSWRProxyHooks(
null,
client
); // Will use the provided client.
// Use the native client proxy for whatever reason (custom requests, custom mutations etc.), example: Init the @trpc/client with this
export const trpcNative = createTRPCClientProxy(client); // imported from @trpc/swr Both of the options currently only use a single instance of the native TRPCClient. We do have the option of leveraging trpc.createClient() and injecting it through context, however it would require some modifications so We can also only leave option 1 on the table and provide a |
What's the difference between that and prefetching in the top level |
No effects will run before the App is mounted & hydrated, in the case of SSR. For large apps this can be quite a bit. Secondly We can build a custom hook, but this ends up not reflecting how SWR's preload API works. If we are making it compatible with SWR v2 either some global reference of the client (when I did come up with another method however. Let me know what you think :) // Option 1
export const trpc = createSWRProxyHooks<AppRouter>(config); // Similar to current API, generates the client
// Option 2 - If you need the native client for whatever reason
const client = createTRPCClient<AppRouter>(config); // From @trpc/client
export const trpc = createSWRProxyHooks.withClient(client); |
I do see what you mean by the SSR sharing of the client (in standard nodejs ssr not lambdas). This could be a problem. In that case I propose a different solution: Preload keeps its own trpc clientWe keep the API as similar to @trpc/react meaning the client will be injected through context.
// Client creator API
// lib/trpc.ts
export const trpc = createSWRProxyHooks<AppRouter>({
links: [
httpBatchLink({
url: getApiUrl(),
}),
]
})
// In _app.tsx
function MyApp({ Component, pageProps }) {
const [client] = useState(() => trpc.createClient()); // Notice no config here as its provided when creating the hooks
return (
<trpc.Provider client={client} >
<Component {...pageProps} />
</trpc.Provider>
)
} Minimal implementation: // As preload is a browser only function, request sharing will not matter on SSR.
let client: TRPCClient<AnyRouter>;
const preload = (pathAndInput, ...args) => {
if(typeof window === "undefined") return Promise.resolve();
return _SWRPreload(pathAndInput, () => {
if(!client) {
client = createClient();
}
return client.query(...pathAndInput, ...args);
});
} With this approach we can keep the API as similar as possible to the react one. The client is not shared accross SSR requests and preload is a noop on SSR. Once the If however, preload is used before React, it will create its own client and dispatch the request. UpdateYou are right. This API which would be alternative API 2 is the best path imo. I've updated my fork here: https://github.com/sannajammeh/trpc-swr I will get started on SWRInfinite, however as this is a codesplit package, how do you propose we use it? Similar to the current API? export const infinite = createSWRInfiniteProxy(trpc)
infinite.cats.useSWRInfinite(...args)
// or since infinite is already presumed
infinite.cats.use(...args) |
Sorry for the late response. I'm a bit confused, which API did you decide to go with for
I like |
I went with tRPC/react's api with a few modifications.
The only difference between this and tRPC/react'a api is that the config has to go inside the createHooks method and not create client. It's a necessary compromise to make preload work. This also lets us build a SSR utility which I'll propose a bit later. |
Update:useSWRInfinite has been successfully implemented. All that remains now is typing the non-proxy hooks as we also export their methods. 97.7% Test coverage. Some quirks on infinite I have to iron out. New infinite API's
|
Thanks for the update! That looks awesome |
UpdateWas waiting for tRPC v10 to release and got caught with work. I have some time on my hands to finalize this RFC. New /next & /ssg entry points with SSG/SSR helpersWe now export Next.js helpers, this includes a
As we are using SWR, there is no point in making a fetch call, thus we use the
UsageIn any server util file // server/ssg
export const createSSG = () => {
return createProxySSGHelpers({
router: appRouter,
ctx: {},
transformer: SuperJSON,
})
} In next _app // _app.tsx
const App = ({ Component, pageProps }: AppProps) => {
const [client] = useState(() => trpc.createClient())
return (
<trpc.Provider client={client}>
<Component {...pageProps} />
</trpc.Provider>
)
}
export default withTRPCSWR({
transformer: SuperJSON,
})(App) In any SSG/SSR route export const getStaticProps: GetStaticProps = async () => {
const trpc = createSSG()
trpc.user.byId.fetch({ id: 1 })
trpc.user.byId.fetch({ id: 2 })
// Can decide to await or not. This is useful if the user wants parallell execution for faster builds/ssr
return {
props: {
swr: await trpc.dehydrate(), // Must be awaited
},
}
} And thats it, the hooks will have hydrated state. Online documentationI'm currently working on documenting everything in this RFC as an online documentation using @sachinraja how does this look? We can most likely make something similar to |
@sannajammeh Amazing! This RFC looks awesome. |
That looks great @sannajammeh! Would you be interested in taking over development of trpc-swr? No pressure, but I don't have the bandwidth to manage this project on top of my other ones and I myself don't use SWR. I'd be happy to transfer the GitHub repo and/or the npm package if you're interested. Thanks! |
I definitely don't mind taking over maintenance of trpc-swr! Would you need the npmjs username or email? |
Thank you so much! I just need your username to transfer ownership. |
Username is: chiefkoshi |
Invited you! |
I've accepted the npmjs code. Would you mind transferring the github repo as well? |
Can't transfer the repo since your fork has the same name: https://github.com/sannajammeh/trpc-swr |
Makes sense. Renamed it! |
I think you might actually have to delete your fork according to these docs? In any case I've invited you as a collaborator so feel free to push your branches here. |
Hello @sachinraja I've publish the first release candidate of this package on npm. My fork is now deleted and changes have been pushed here. Would you mind transferring the repo over to me so I can deploy the documentation on Vercel? |
@sachinraja Any possibility for this? |
@sannajammeh I think I sent you an invite to transfer the repo a day after you asked |
Done, sent another transfer request. |
@sannajammeh Any updates about this RFC? |
It's currently in RC mode! I'm testing it in my dev environments and trying to collect any bugs before I release the V1. There's also a bug in the Next.js rust compiler preventing SWR use in the new App directory. vercel/swr#2632 As a hot fix we had to duplicate SWR's hashing function, which isn't ideal. I'm confident we can release the v1 once this is fixed. |
Hi everyone! I've successfully tested this library in most environments now. All thats missing is a an E2E test with Next 13. v1 will release by the end of this week :) |
UpdateHit some weird dependency resolution issues with both TypeScript and Next 13. For some reason the very same resolution method works in SWR, but not here. If I'm unable to resolve this by tomorrow, I'll be switching to a more safe packaging method instead, similar to tRPC. Example:
Essentially the problem is as follows: When testing inside of the monorepo, internal packages of trpc-swr like This only appears to happen in the monorepo and not external Next.js projects. Edit Apr 4.Looks like [email protected] has resolved this issue and will respect the Edit Apr 11.After a week of testing it appears that the issue remains and is due to Webpack's handling of symlinks, causing Next to import ESM
Once merged the |
All updates will now arrive in vercel/next.js#40 |
Thank you for your work! |
New Proxy API & SWR 2.0
This RFC outlines the implementation of tRPC's v10 proxy api in conjunction with SWR hooks, along with some following features:
fallbackData
&SWRConfig
Motivation
As tRPC v10 is nearing completion (main proxy API is stable), SWR hooks is a "must have" for many developers like myself.
The changes
Below are the changes I propose for
trpc-swr
(v1.0?).New proxy API
trpc-swr
will take advantage of the newly constructed Proxy technique used in tRPC V10. Examples:SWR 2.0
trpc-swr
will take advantage of the newest SWR release. This allows for optimisticUpdate support, and other features. With this RFCswr: ^2
will become a peer dependency rather thanswr: ^1
Example
New client creator API
Being able to access the raw tRPC proxy client is incredibly useful during calls outside of React. Previously, the API would've required you to create a new client instance separate from
trpc-swr
. AKA. Two tRPC instances running.Therefore, I propose a new API for creating a client instance. This will allow for the following:
Alternative API 1:
In this API no
trpc.client
will be available. Instead, you can reference the client directly. This also allows for usage outside of React. Here we are using named properties.trpc
will always be thehooks
instance, andclient
will always be the standard tRPC proxyclient
instance.Alternative API 2:
Keep the raw client inside of a
trpc.useContext()
call as it is today. Allow passing in aclient
instance tocreateSWRProxyHooks
to use the same client instance. This would allow for usage outside of React.Better SSR support
trpc-swr
will take advantage of SWR'sfallbackData
&SWRConfig
to provide better SSR support. This requires a new API forcreateSWRProxyHooks
:.getKey
:The reason this is wildy important is for SSR to work from a global state standpoint. Providing the fallback data to a single
useSWR
hook will not populate the cache for other hooks (Wierdly enough since SWR uses a global cache anyway).The creation of this
.getKey
API also gives us a direct way to mutate SWR keys in the global cache. This is useful for optimistic updates, manual cache overwrites, etc.This is something thats been missing even from the current V2
SWR
core. Since we use proxies in tRPC this is a very easy API to implement and will provide tremendous DX for SWR users.Future & possible API's
Prefetch data before React mounts directly into SWR cache
This is a feature SWR is in the process of releasing. It will allow for prefetching data before React mounts. This is useful for SSR, and also for prefetching data before a user navigates to a page. A new API for
trpc-swr
might look like this:Potential API 1
Using the
.getKey
method from above.Potential API 2
Using a special
.preload()
on queries.SWR's
useSWRMutation
APISWR is also working on a brand new mutation API similar to
react-query
for triggerable mutations. This is a very exciting API and I'm looking forward to it. I'm not sure how this will work with tRPC yet, but I'm sure we can figure something out.Footnotes
This is a very rough draft of what I'm thinking. I'm sure there are many things I'm missing. I'm also not sure if this is the best way to do things. I'm open to suggestions and feedback. Additionally, some API's in the current release are not accounted for here. I'll be sure to update this RFC once I have some more time.
Once I have some feedback on the above suggestions I am happy to start a PR for this new API. And huge thanks to @sachinraja for making this wonderful library :)
The text was updated successfully, but these errors were encountered: