Next.js Discord

Discord Forum

Server Actions in Client Components

Answered
Polar bear posted this in #help-forum
Open in Discord
Polar bearOP
I have a NextJS frontend and an Express backend set up. I am trying to do a quick "proof of concept" by implementing a button (on a client component) that fetches some data from my express API, and logs it to the console. I am trying to use server actions to do this, but the response returns undefined every time.

On the top level of my tree (top-level is server component), I also fetch the data to prove that it works: (refers to fetchRecords)

import { fetchMember, fetchRecords } from "@/app/api/read"

...

export default async function Board() {
    const response = await fetchMember()
    const response2 = await fetchRecords()

    return response && response2 ? ( // returns true with data )


And here is the fetchRecords function:

'use server'

...

export async function fetchRecords(): Promise<(APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true }) | null> {
    try {
        return await fetch<APIResponse<{ records: AttendanceRecord<number>[] }>>(
            "http://localhost:5000/api/attendance/records",
            {
                method: "GET",
                headers: headers()
            },
            FetchResultTypes.JSON
        ) as unknown as (APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true })
    } catch {
        return null
    }
}


When the fetchRecords is called from the top-level server component, it successfully returns a non-null response with the data. This, however, does not work:

"use client"

...

            <button
                onClick={async () => {
                    const response = await fetchRecords()
                    console.log(response?.success)
                }}>
                Fetch Records
            </button>


Instead, the response from the API is undefined. In fact, it doesn't even make a call to the server at all (no activity is logged on the backend). It's getting stuck on the front, and why is this?
Answered by Polar bear
well my friend wrote this fix:

export async function fetchRecords(): Promise<(APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true }) | any> {
    try {
        return await fetch<APIResponse<{ records: AttendanceRecord<number>[] }>>(
            "http://localhost:5000/api/attendance/records",
            {
                ...(await platformOptions())
            },
            FetchResultTypes.JSON
        ) as unknown as (APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true })
    } catch(err) {
        return console.log(`ERROR:\n\n${err}`)
    }
}


and

import { RequestOptions } from "@sapphire/fetch"

export async function platformOptions(): Promise<Partial<RequestOptions>> {
    if (typeof window === "undefined") {
        const { headers } = await import("next/headers")

        return {
            headers: headers()
        }
    } else {
        return {
            credentials: "include"
        }
    }
}
View full answer

25 Replies

@Polar bear I have a NextJS frontend and an Express backend set up. I am trying to do a quick "proof of concept" by implementing a button (on a client component) that fetches some data from my express API, and logs it to the console. I am trying to use server actions to do this, but the response returns undefined every time. On the top level of my tree (top-level is server component), I also fetch the data to prove that it works: (refers to ``fetchRecords``) ts import { fetchMember, fetchRecords } from "@/app/api/read" ... export default async function Board() { const response = await fetchMember() const response2 = await fetchRecords() return response && response2 ? ( // returns true with data ) And here is the ``fetchRecords`` function: ts 'use server' ... export async function fetchRecords(): Promise<(APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true }) | null> { try { return await fetch<APIResponse<{ records: AttendanceRecord<number>[] }>>( "http://localhost:5000/api/attendance/records", { method: "GET", headers: headers() }, FetchResultTypes.JSON ) as unknown as (APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true }) } catch { return null } } When the ``fetchRecords`` is called from the top-level server component, it successfully returns a non-null response with the data. This, however, does not work: ts "use client" ... <button onClick={async () => { const response = await fetchRecords() console.log(response?.success) }}> Fetch Records </button> Instead, the response from the API is undefined. In fact, it doesn't even make a call to the server at all (no activity is logged on the backend). It's getting stuck on the front, and why is this?
'use server'

...

export async function fetchRecords(): Promise<(APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true }) | null> {
    try {
        const res = await fetch<APIResponse<{ records: AttendanceRecord<number>[] }>>(
            "http://localhost:5000/api/attendance/records",
            {
                method: "GET",
                headers: headers()
            },
            FetchResultTypes.JSON
        );

        const data = await res.json() as unknown as (APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true })
        return data
    } catch {
        return null
    }
}
catch the error and log it
@Ray catch the error and log it
Polar bearOP
TypeError: fetch failed
on the server component, no error and it fetches successfully
just not on client
can you try with normal fetch instead of sapphire/fetch?
@Ray can you try with normal fetch instead of `sapphire/fetch`?
Polar bearOP
export async function fetchRecords2() {
    try {
        const response: any = await fetch(
            "http://localhost:5000/api/attendance/records",
            {
                method: "GET",
                headers: headers()           
            }
        )
        return await response.json()
    } catch(err) {
        return console.log(err)
    }
}
TypeError: fetch failed
same error
@Ray ts headers: Object.fromEntries(headers())
Polar bearOP
oop okay actually there's a new error
with this
TypeError: fetch failed
    at Object.fetch (node:internal/deps/undici/undici:11576:11) {
  cause: RequestContentLengthMismatchError: Request body length does not match content-length header
      at write (C:\Users\Parker\Dev\aedash\node_modules\.pnpm\next@13.5.4_@babel+core@7.23.6_react-dom@18.2.0_react@18.2.0\node_modules\next\dist\compiled\undici\index.js:1:77165)
      at _resume (C:\Users\Parker\Dev\aedash\node_modules\.pnpm\next@13.5.4_@babel+core@7.23.6_react-dom@18.2.0_react@18.2.0\node_modules\next\dist\compiled\undici\index.js:1:76786)
      at resume (C:\Users\Parker\Dev\aedash\node_modules\.pnpm\next@13.5.4_@babel+core@7.23.6_react-dom@18.2.0_react@18.2.0\node_modules\next\dist\compiled\undici\index.js:1:75473)
      at connect (C:\Users\Parker\Dev\aedash\node_modules\.pnpm\next@13.5.4_@babel+core@7.23.6_react-dom@18.2.0_react@18.2.0\node_modules\next\dist\compiled\undici\index.js:1:75361) {
    code: 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
  }
}
that's... odd
@Ray hmm, just pass the header you need?
Polar bearOP
it may have to do with my authentication cookie.. perhaps theres some difference between server / client components that causes it to be obtained differently
still i dont understand what that error means
@Ray the header of content-length doesn't match
Polar bearOP
well my friend wrote this fix:

export async function fetchRecords(): Promise<(APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true }) | any> {
    try {
        return await fetch<APIResponse<{ records: AttendanceRecord<number>[] }>>(
            "http://localhost:5000/api/attendance/records",
            {
                ...(await platformOptions())
            },
            FetchResultTypes.JSON
        ) as unknown as (APIResponse<{ records: AttendanceRecord<number>[] }> & { success: true })
    } catch(err) {
        return console.log(`ERROR:\n\n${err}`)
    }
}


and

import { RequestOptions } from "@sapphire/fetch"

export async function platformOptions(): Promise<Partial<RequestOptions>> {
    if (typeof window === "undefined") {
        const { headers } = await import("next/headers")

        return {
            headers: headers()
        }
    } else {
        return {
            credentials: "include"
        }
    }
}
Answer
Polar bearOP
kinda funny tbh. 😹
i suppose it's one way to solve the client / server discrepancy
and hey it works so
thanks for your help @Ray :) i am very grateful