orval
orval copied to clipboard
SWR: Attempted import error: 'swr' does not contain a default export (imported as 'useSwr').
What are the steps to reproduce this issue?
- Run codegen with swr generator
- Import one of the helper functions into an RSC component
- Terminal shows this error
Nothing breaks, it's just a warning, but annoying
More info: https://github.com/vercel/swr/issues/2694
What happens?
What were you expecting to happen?
The codegen could add "use client" to parts that actually call useSWR
and separate keys from this code as the keys are used in server components.
Any other comments?
Obviously we don't want to call useSWR
in server components, however there are parts of the generated code that are useful in server components. One of these, which I make use of in Storyden's codebase is the generated keys. This means I can use fetch
on the server but still use the generated types and paths.
export const getThreadListKey = (params?: ThreadListParams) =>
[`/v1/threads`, ...(params ? [params] : [])] as const;
These generated key functions are useful to keep everything type safe - even while Orval doesn't generate server code, I can use these to ensure my code is at least somewhat type safe for server components:
import { server } from "src/api/client";
import { ThreadListOKResponse } from "src/api/openapi/schemas"; // schema imports are fine - no useEffect dependents here
import { getThreadListKey } from "src/api/openapi/threads"; // this causes the error, even though I'm not actually using useEffect dependents (useSWR) - the runtime still warns me because getThreadListKey is in the same file as a useSWR import+call
import { Client } from "./Client";
type Props = {
category: string;
};
export async function FeedScreen(props: Props) {
const key = getThreadListKey({ categories: [props.category] })[0]; // imported from Orval
const data = await server<ThreadListOKResponse>(key);
return <Client category={props.category} threads={data.threads} />;
}
What versions are you using?
Package Version: "orval": "^6.17.0",
PR is welcome. SSR has been a nightmare for library implementors!
I'd be up for doing a PR to split these files, where's a good place to start?
I think we just need to put the SWR keys in a separate file to the actual fetch calls, should be quite simple!
Hmm this was handled differently for React Query and the files need not be split for SSR: https://github.com/anymaniax/orval/issues/985
@Southclaws
Hi, this similar issue seems to have been resolved in v14
of Next.js
. This may also have been resolved, so could you please check again with v14
?
@melloware
This problem is probably a problem with Next.js
, not orval
. The "bug" label attached to issue may not be necessary.
I removed bug and will close for now unless the OP can confirm.
Why was this issue closed? I just ran into the same problem with SWR. I imported the function that does Axios request into a server component and wanted to do a request on the server side but it failed with an error that matches the name of this issue. This makes the SWR generator kind of useless for those who want to do both server-side and client-side fetching unless I generate completely separate API files for use in server components but it feels very wrong considering that the files generated for the SWR client already have everything I need to use on server as well. I think splitting the files is the way to go. I don't know how come that this works in ReactQuery, but even there the hooks could be just tree-shaken away from the server bundle.
I got some extra information: To be more correct this error doesn't crash the build and everything seems to be working on the server, including loading data. Nonetheless, it doesn't seem to be safe to use in production like this.
I tried to split it manually into separate files, but as long as they are re-exported by the same index.ts
file, the error will persist. I imagine creating an entirely different barrel file would be quite a big change for orval, but thankfully the file with hooks can be marked with 'use client'
directive and the server will ignore it, I can confirm that the error is then gone. So I'd propose to do something like this (in case of split-tags):
openapi/
├─ api/
│ ├─ tag-1/
│ │ ├─ tag-1.ts
│ │ ├─ tag-1-hooks.ts
│ ├─ tag-2/
│ │ ├─ tag-2.ts
│ │ ├─ tag-2-hooks.ts
│ ├─ index.ts
├─ models/
│ ├─ index.ts
With hooks files having 'use client'
on top. Something similar can be probably done about split
and tags
mode, but as for single
I am afraid nothing can be done.
Nice debugging. Still a big change but at least you know what works now.
@isoroka-plana
Hi, I would like to ask you a few questions to help me understand better.
- Is it correct that what you are looking for is to use only the following two functions that do not depend on
swr
?
- key generation function
- Axios client
- Also, is it correct to understand that it is used in the server-side component of
Next.js
?
- Yes, I am looking to use axios client, and the function that does the axios request (if that's what you mean by key generation function, there's one that generates swr key, but it's only relevant for SWR itself). Here's an example that I am looking to import:
export const booksGet = (options?: SecondParameter<typeof customInstance>) => {
return customInstance<Book[]>({url: `api/v1/books`, method: 'GET'}, options);
}
- Yes, that's exactly right. Example component:
export const BooksPage = async () => {
const books = await booksGet();
return (
<div>
{books .map(book => <Book key={book.id} book={book} />)}
</div>
)
}
So the axios stuff is client/server agnostic while swr stuff is client only, that's why in order to make convenient to use it's best to split them into separate files. Cause I can create the above function myself using the same axios client and models used by Orval but still that means I have to hardcode the API path.
@isoroka-plana
Thank you for letting me know.
I thought this could be achieved by specifying the axios
client, what do you think?
orval.config
:
module.exports = {
'petstore-file': {
input: {
target: './petstore.yaml',
},
output: {
client: 'axios',
target: 'src/gen/endpoints',
},
},
};
generated
const listPets = (
params: ListPetsParams,
version: number = 1,
) => {
return listPetsMutator<Pets>(
{url: `/v${version}/pets`, method: 'GET',
params
},
);
}
usage:
const pets = await listPets(params);
return (
<div>
{pets .map(pet => <Pet key={pet.id} pet={pet} />)}
</div>
)
}
@soartec-lab thank you for suggesting this idea. I considered this option but I still need swr for requests from the client side. This would require me to generate 2 different outputs 1 for plain axios and 1 for swr. Each of these will create their own models if I get everything correctly. It's a messier approach than just creating copies of the functions that I need by myself. But from the perspective of Nextjs + orval user there should be a way to use 1 generator and use its output on both sides.
@isoroka-plana
Hmmm. This can be achieved by creating automatic generation of axios
for the server component and automatic generation of swr
for the client component. For example, the directory structure is as follows.
# generated by `axios`
gen/server-endponin
# generated by `swr`
gen/client-endponin
But you say you don't want that. In other words, rather than managing two generators, you want to satisfy both with one generator.
Could you please explain in more detail why the above method is insufficient?
@soartec-lab Because the models will be generated by each of the generators, it will create a mess with imports since 1 and the same API. And swr client already uses axios and exports the necessary functions.
I understand. It's true that if you're using both, it's confusing and difficult to use. I change the label to enhancement
.
I figured out an undocumented feature, that the models can be generated without generating endpoints and the endpoint generators can be pointed to models generated by the model generator 🤷 I found it surprising but it actually works, although there was a bit of a hiccup that the API exports also reexported the models, so I had to disable index files there. I managed to achieve the desired result by using multiple entries in the config file like so:
export default defineConfig({
myapi: {
input,
output: {
mode: 'tags-split',
workspace: './build/openapi/myapi',
schemas: './models',
},
},
myapiClient: {
input,
output: {
mode: 'tags',
workspace: './build/openapi/myapi',
target: './api/client',
schemas: './models',
indexFiles: false,
client: 'swr',
override: {
mutator: {
path: '../../../src/orval-mutator/custom-client-instance.ts',
name: 'customInstance',
},
},
},
},
myapiServer: {
input,
output: {
mode: 'tags',
workspace: './build/openapi/myapi',
target: './api/server',
schemas: './models',
indexFiles: false,
override: {
mutator: {
path: '../../../src/orval-mutator/custom-server-instance.ts',
name: 'customInstance',
},
},
},
},
});
and then in tsconfig.json
:
"@myapi/api": ["./build/openapi/myapi/index.ts"],
"@myapi/api/client/*": ["./build/openapi/myapi/api/client/*"],
"@myapi/api/server/*": ["./build/openapi/myapi/api/server/*"],
Maybe simply the documentation can be improved to have some sort of article with advanced stuff and maybe no need to complicate the implementation.
I've found a workaround for this edge case so closing