Skip to content
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

NextJS compatibility #413

Closed
samvaughton opened this issue Apr 17, 2024 · 17 comments
Closed

NextJS compatibility #413

samvaughton opened this issue Apr 17, 2024 · 17 comments
Labels
client Client package related feature 🚀 New feature or request RSVP 👍👎 Explicit request for reactions to gauge interest in resolving this issue

Comments

@samvaughton
Copy link

Description

The library looks great - personally I am finding it difficult to get it working cleanly within a nextJS app that wants to use the generated API on the server side of NextJS (ie "use server";). If I am passing functions around into client components to be called, NextJS mandates that they be async and also have "use server"; either in the function itself or in the file etc...

Is there any consideration for NextJS compatibility for the services.gen.ts file or an option? Right now I will probably have to write a post-script that removes all export class (since NextJS does not allow this either) and transforms them into a list of exported functions e.g async ${serviceName}${operationName}(..... for example. And also put a "use server"; at the top of the file.

If this sounds reasonable, I could try and get around to a PR?

@samvaughton samvaughton added the feature 🚀 New feature or request label Apr 17, 2024
@mrlubos
Copy link
Member

mrlubos commented Apr 17, 2024

Yes there is, do you have Discord? Would be easier to discuss how this could work

@samvaughton
Copy link
Author

Too late for me now unfortunately - we can hash it out on Discord perhaps later this week or the weekend.

Did you have a general plan in mind for it?

@mrlubos
Copy link
Member

mrlubos commented Apr 17, 2024

I didn't mean now! The only pointer I have for you now is #308

@mrlubos
Copy link
Member

mrlubos commented Apr 17, 2024

Related thread is #342, both features must be satisfied to consider this a good solution

@jordanshatford
Copy link
Collaborator

@samvaughton could you not pass "SomeService.someFunc" as that would be an asynchronous function. They are static on the class. If that would work, then the solution may be to simply add "use server" to the top of the file for nextjs

@samvaughton
Copy link
Author

samvaughton commented Apr 18, 2024

I have tried a fair few configurations and NextJS is very picky about this...

When doing something like this enquiryAction={EnquiriesService.createEnquiry}

Normal services.gen.ts without modification results in:

 Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
  <... formMode="CREATE" clientData=... enquiryAction={function}>
                                                      ^^^^^^^^^^

So I add "use server"; to the file and it points to the export class EnquiriesService as the culprit:

× Only async functions are allowed to be exported in a "use server" file.

 │ ╭─▶ export class EnquiriesService {
 12 │ │     /**
 13 │ │      * Adds a new enquiry
 14 │ │      * Adds a new enquiry
 15 │ │      * @returns EnquiryResponse Successfully added a new enquiry.
 16 │ │      * @throws ApiError
 17 │ │      */
 18 │ │     public static createEnquiry(
 19 │ │       data: $OpenApiTs["/v1/enquiries"]["post"]["req"],

The only way to fix it is to remove classes and export async function .... for all of them 🤷

I didn't mean now!

@mrlubos Woops, my bad :-)

@jordanshatford
Copy link
Collaborator

@samvaughton what if you put "use server" at the top of the static function?

@mrlubos
Copy link
Member

mrlubos commented Apr 18, 2024

For what it's worth, I don't think it's reasonable to require "use server" declaration, even if it worked. @samvaughton I'm considering exporting all operations as standalone functions anyway so I'd be in favour of that + export services as objects instead of classes

@samvaughton
Copy link
Author

samvaughton commented Apr 18, 2024

@jordanshatford tried it and unfortunately it does not work. Since passing a server function to a client component is a very specific thing, NextJS requires it to be async and have "use server"; in that file. And the moment you use "use server"; it requires every exported function in that file to be async too :( . It doesn't play nicely with static methods inside a class.

Extracting a single function out, a working file looks something like this:

"use server";

// This file is auto-generated by @hey-api/openapi-tsimport type { CancelablePromise } from "./core/CancelablePromise";
// removed imports for brevity

export async function createEnquiry(
  data: $OpenApiTs["/v1/enquiries"]["post"]["req"],
): CancelablePromise<
  ApiResult<$OpenApiTs["/v1/enquiries"]["post"]["res"][201]>
> {
  // "use server"; does not work here....

  const { requestBody } = data;
  return __request(OpenAPI, {
    // redacted for brevity
  });
}

@mrlubos
Copy link
Member

mrlubos commented Apr 18, 2024

I think we should switch to async either way, then unlocking this functionality would be trivial

@abramchikd
Copy link

Hi! Is there any progress on NextJS support?

@mrlubos
Copy link
Member

mrlubos commented Jun 21, 2024

@abramchikd Not yet, can you list out what's required with the latest version using the new Fetch API client?

@abramchikd
Copy link

Oh, actually I tried it with the new Fetch API client. And it seems to work great. Basically I just needed to add 'use server' in the beginning of services.gen.ts.

Thank you and sorry for disturbing.

I will post an update if I find that something is missing

@mrlubos
Copy link
Member

mrlubos commented Jun 21, 2024

@abramchikd No worries. How do you add the declaration? Also, the services are not using async keyword and that was previously mentioned as a requirement – what changed?

@abramchikd
Copy link

abramchikd commented Jun 27, 2024

@mrlubos Almost everything works fine with server actions.

I only had to make two changed:

  1. Add 'use server' in services.gen.ts so that the file looks like this:
'use server'
// This file is auto-generated by @hey-api/openapi-ts

import {...}
import {...}

export const getSomeRequest = (options?: Options) => { return (options?.client ?? client).get<GetSomeRequestResponse, GetSomeRequestError>({
    ...options,
    url: '/some/request'
}); };
...

It can be automated by running sed -i "1s/^/'use server'\n/" src/apiClient/services.gen.ts after openapi-ts.

  1. Write a wrapper for request functions, which removes request and response fields from the RequestResult, because class objects cannot be passed from Server components to Client components
'use server';

import { Options, RequestResult } from '@hey-api/client-fetch';
import { apiClient } from '@/apiClient/apiClient';

export async function apiRequest<Payload = unknown, Response, Error>(
    request: (options: Options<Payload>) => RequestResult<Response, Error>,
    options?: Options<Payload>,
): Promise<Response> {
    const {data, error} = await request({...options, client: apiClient});

    if (error) {
        throw new Error(JSON.stringify(error));
    }

    return data!;
}

So my request invocation looks like this:

const header = await apiRequest(getHeader, { path: { headerId } })

@mrlubos
Copy link
Member

mrlubos commented Jun 27, 2024

Interesting, thank you! If you'd be able to spin up a StackBlitz example at some point, that would be very appreciated

@mrlubos mrlubos added the RSVP 👍👎 Explicit request for reactions to gauge interest in resolving this issue label Aug 14, 2024
@mrlubos
Copy link
Member

mrlubos commented Jan 6, 2025

Moving this issue to #1515!

@mrlubos mrlubos closed this as completed Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client Client package related feature 🚀 New feature or request RSVP 👍👎 Explicit request for reactions to gauge interest in resolving this issue
Projects
None yet
Development

No branches or pull requests

4 participants