Next.js Discord

Discord Forum

Error: SyntaxError: Unexpected token < in JSON at position 0

Unanswered
D34dlyK1ss posted this in #help-forum
Open in Discord
I am trying to send a NextResponse from my API

77 Replies

but when I want to read the JSON I get the error
anyone who can help?
been whole day with 0 fixes
Black-throated Blue Warbler
you are missing a { in your return NextResponse.json
@Black-throated Blue Warbler you are missing a { in your return NextResponse.json
I have the code like this rn
still getting the same error
Black-throated Blue Warbler
hmm one sec
I also thought of sending the response with headers: {"Content-Type": "application/json"} but doesn't work
I thought "application/x-www-form-urlencoded" was being forced on the response
but I'm not sure if that's the case
Black-throated Blue Warbler
the docs are showing it like this:

export async function POST(request: Request) {
const formData = await request.formData()
const name = formData.get('name')
const email = formData.get('email')
return Response.json({ name, email })
}


not using NextResponse
I tried that too
doesn't work
I know the login() function is working fine. the databse query is working as expected
I just wanted a way to customise errors on the client side
but I can't even get a working case... working πŸ˜‚
Black-throated Blue Warbler
what happens when you return an empty object?

return NextResponse.json({})
should also test returning the empty json above your login function just to rule out the login function
same thing
this is so weird 😭
Black-throated Blue Warbler
I would need to see some more code to help debug i think.

Have you considered a server action for this task?
oh wait a minute, you are using a server action to call an next api route?

Thats probably your issue
you don't need to fetch an api route in your server action, you can call your login function directly in the server action.
I think I did that 2 days ago
and then I took a 360ΒΊ on the matter of sending errors
Black-throated Blue Warbler
here i'll grab you an example from my project
cause I wanted to handle server action errors with a custom modal
Sun bear
I would console.log(response)

Maybe the 500 error returns not what you expected. Maybe better return 200 and use a structure like:

{
success: false,
msg: "your error"
}
been there done that
Response {
  [Symbol(realm)]: null,
  [Symbol(state)]: {
    aborted: false,
    rangeRequested: false,
    timingAllowPassed: true,
    requestIncludesCredentials: true,
    type: 'default',
    status: 404,
    timingInfo: {
      startTime: 930979.1861000061,
      redirectStartTime: 0,
      redirectEndTime: 0,
      postRedirectStartTime: 930979.1861000061,
      finalServiceWorkerStartTime: 0,
      finalNetworkResponseStartTime: 0,
      finalNetworkRequestStartTime: 0,
      endTime: 0,
      encodedBodySize: 0,
      decodedBodySize: 0,
      finalConnectionTimingInfo: null
    },
    cacheState: '',
    statusText: 'Not Found',
    headersList: HeadersList {
      cookies: null,
      [Symbol(headers map)]: [Map],
      [Symbol(headers map sorted)]: null
    },
    urlList: [ [URL] ],
    body: { stream: undefined }
  },
  [Symbol(headers)]: HeadersList {
    cookies: null,
    [Symbol(headers map)]: Map(9) {
      'vary' => [Object],
      'cache-control' => [Object],
      'x-powered-by' => [Object],
      'content-type' => [Object],
      'content-encoding' => [Object],
      'date' => [Object],
      'connection' => [Object],
      'keep-alive' => [Object],
      'transfer-encoding' => [Object]
    },
    [Symbol(headers map sorted)]: null
  }
}
this is the actual log
Black-throated Blue Warbler
Here's what i'm using for my server action, its performing a fetch but thats not a local api route, thats my my externabl backend.

/**
 * Signs in the user with the provided credentials.
 * Sets the JWT cookie and redirects to the dashboard on success.
 * @param _prevState - Previous state, unused.
 * @param formData - Form data containing 'identifier' and 'password'.
 * @returns A message indicating the failure of the operation, or redirects to the dashboard on success.
 */
export async function sign_in(
  _prevState: unknown,
  formData: FormData
): Promise<FormMessage> {
  try {
    const identifier = formData.get("identifier") as string;
    const password = formData.get("password") as string;
    const response = await fetch(AUTH_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ identifier, password }),
    });
    const { jwt, error } = await response.json();
    await new Promise((resolve) => setTimeout(resolve, 1500));

    if (error) {
      if (error.status === 400) {
        return { message: "Invalid username or password" };
      }
      return { message: error.message || "Server Error", type: "error" };
    }

    if (jwt) {
      setJwtCookie(jwt);
    }
  } catch (error) {
    return { message: "Server Error" };
  }
  redirect("/dashboard");
}
but in the end, I may just execute login directly
Black-throated Blue Warbler
Then on my form i use useFormState to display the message if there is one.

"use client";

import styles from "../form.module.css";
import Link from "next/link";
import { useFormState } from "react-dom";
import { sign_in } from "@/utils/authActions";
import { Input, Submit, Message } from "@/components/form-inputs";

/**
 * SignInForm Component
 *
 * This component renders a sign-in form with fields for username/email and password.
 * It uses the `useFormState` hook to manage form state and handle submission actions.
 * The form includes links for password recovery and account sign-up.
 *
 * @returns {JSX.Element} The rendered sign-in form component.
 */
const SignInForm: React.FC<FormProps> = () => {
  const [state, action] = useFormState(sign_in, {
    message: null,
  });

  return (
    <form action={action} className={styles.form} aria-labelledby="legend">
      <fieldset>
        <legend id="legend" className="srOnly">
          Sign In Form
        </legend>
        <div className={styles.field}>
          <Input
            id="identifier"
            label="Username / Email"
            required
            aria-required="true"
            aria-label="Username or Email"
          />
        </div>
        <div className={styles.field}>
          <Input
            type="password"
            id="password"
            label="Password"
            required
            aria-required="true"
            aria-label="Password"
          />
          <Link href="/forgot-password">{`Forget your password?`}</Link>
        </div>
        <div className={styles.actions}>
          <Submit title="Submit" />
          <Link href="/sign-up">{`Don't have an account? Sign up!`}</Link>
        </div>
        <Message state={state} />
      </fieldset>
    </form>
  );
};

export default SignInForm;
you're converting formData to JSON first
I'd think that's my problem not doing the same
Black-throated Blue Warbler
i'm just saying on your form you can use useFormState which will allow you to display your error message how you want it.

  
  const [state, action] = useFormState(sign_in, {
    message: null,
  });


<form action={action}></form>
yeah I guess I'm just dumb
I never understood the States so I have been avoiding πŸ˜‚
my bad then
Black-throated Blue Warbler
this is all new for a lot of us πŸ˜„

Can i see what your login function looks like?
const db = connectToDatabase();

interface UserRow extends RowDataPacket {
    id: string;
    username: string;
    email?: string;
}

export async function login(username: string, password: string) {
    try {
        const [rows] = await db.execute<UserRow[]>(
            `SELECT id, username, email 
             FROM user 
             WHERE (username = ? OR email = SHA2(?, 256)) 
             AND password = SHA2(?, 256)`,
            [username, username, password]
        );

        if (!rows.length) throw "Username/Email and Password combination does not match an existing user";

        const user = { id: rows[0].id, username: rows[0].username, email: rows[0].email };
        const expires = new Date(Date.now() + 30 * 60 * 1000);
        const session = await encrypt({ user, expires });

        cookies().set("session", session, {
            expires: expires,
            httpOnly: true,
            secure: true
        });
    } catch (err) {
        console.error("Error executing queries:", err);
    }
}
forget about it
I'll use states
I know I can do the login directly
Black-throated Blue Warbler
your sign in function could look something like this, rather than throwing errors return it as a message you can use to display in your popup whatever with useFormState

"use server";

async function sign_in(
  _prevState: unknown,
  formData: FormData
): Promise<any> {
  try {
    const username = formData.get("username") as string;
    const password = formData.get("password") as string;

    const [rows] = await db.execute<UserRow[]>(
      `SELECT id, username, email 
       FROM user 
       WHERE (username = ? OR email = SHA2(?, 256)) 
       AND password = SHA2(?, 256)`,
      [username, username, password]
  );

  if (!rows.length) {
    return { message: "Invalid username or password" };
  }

  const user = { id: rows[0].id, username: rows[0].username, email: rows[0].email };
  const expires = new Date(Date.now() + 30 * 60 * 1000);
  const session = await encrypt({ user, expires });

  cookies().set("session", session, {
      expires: expires,
      httpOnly: true,
      secure: true
  });

  } catch (error) {
    return { message: "Server Error" };
  }
  redirect("/dashboard");
}
  const [state, action] = useFormState(sign_in, {
    message: null,
  });


you would access your message with state.message
exactly
I'll do that
Black-throated Blue Warbler
i'm pretty much using server actions for all post requests now, don't have any api routes anymore.
alright
Black-throated Blue Warbler
your state doesn't have to look like that either, can do whatever you want with it.
  const [state, action] = useFormState(sign_in, {
    success: false,
    status: 401,
    message: 'Not Authorized',
    sandwich: "bologna"
  });
other option if you'd rather throw errors is just create an error.tsx to display the error in a modal or wahtever
yeah, let me just find my way through this and see if everything goes nicely
then I can finally tag this as answered πŸ˜„
Black-throated Blue Warbler
This does a better job explaining than i did if you get confused.

https://www.pronextjs.dev/form-actions-with-the-useformstate-hook
@Black-throated Blue Warbler isn't useFormState deprecated?
import "@/app/globals.css";
import { login } from "@/utils/userController";
import NotificationModal from '@/app/components/notificationModal';
import { useFormState } from "react-dom";

export default async function LoginPage() {
    const [state, action] = useFormState(login, {
        message: null
    });

    return (
        <div>
            <div id="divLogin" className="row justify-content-md">
                <h1>Login</h1>
                <form action={action}>
                    <div id="loginUser" className="col-12">
                        <label htmlFor="inputUsername">Username / Email</label>
                        <input id="inputUsername" name="username" type="text" placeholder="Username / Email" autoComplete="on" required />
                        <span id="spanErrorUsername" className="spanError"></span>
                    </div>
                    <div id="loginPass" className="col-12">
                        <label htmlFor="inputPassword">Password</label>
                        <input id="inputPassword" name="password" type="password" placeholder="Password" autoComplete="on" required />
                        <span id="spanErrorPassword" className="spanError"></span>
                    </div>
                    <div id="loginButton" className="col-12">
                        <input className="btn btn-primary" type="submit" value="Login" />
                    </div>
                </form>
                <div className="col-12">
                    <a href="/recover">Forgot password?</a>
                    <br />
                    <a href="/register">I want to register</a>
                </div>
            </div>
            {state && <NotificationModal state={state} />}
        </div>
    );
}
Error:
  Γ— You're importing a component that needs useFormState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
  β”‚ Learn more: https://nextjs.org/docs/getting-started/react-essentials        
  β”‚
  β”‚
   ╭─[C:\Users\D34dlyK1ss\Documents\Code\who-is-it\app\login\page.tsx:1:1]      
 1 β”‚ import "@/app/globals.css";
 2 β”‚ import { login } from "@/utils/userController";
 3 β”‚ import NotificationModal from '@/app/components/notificationModal';
 4 β”‚ import { useFormState } from "react-dom";
   Β·          ────────────
 5 β”‚
 6 β”‚ export default async function LoginPage() {
 6 β”‚    const [state, action] = useFormState(login, {
idk what to do here
+ I'd like my modal to be on root layout
Great golden digger wasp
instead of using NextResponse you can use simply Response. Try it and then tell that it is working or not.
but you can forget about that
I'm on a diferent path for the same end
@D34dlyK1ss I'm on a diferent path for the same end
Great golden digger wasp
why?
simplicity
@D34dlyK1ss simplicity
Great golden digger wasp
?
I'm not using API routes anymore
that's about it
I'm executing the code right on form action
worked perfectly like that before, I just need to find a way to make the error show up
Black-throated Blue Warbler
It's not deprecated yet, but i belive it is getting replaced with https://react.dev/reference/react/useActionState soon
basically just renamed it