Anuj
Anuj

Reputation: 398

Using server action to call api is not working

I am very new to Next.js. I have created login page and I want to call API on submit button click. There are few issues which i am getting

I am already adding "use server" in the action.ts still getting first error

Login Page:

import Image from "next/image";
import React from "react";
import lady from "../../../public/landing-girl.svg";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { signInUser } from "../actions/action";

export default function Login() {
  const callAPI = async (params: FormData) => {
    console.log(params);
    await signInUser("[email protected]", "Qwerty1@");
  };

  return (
    <div className="flex flex-1 flex-col justify-center items-center">
      <Image
        src={lady}
        alt="lady"
        fill={true}
        placeholder="empty"
        priority={false}
        style={{ objectFit: "cover" }}
      ></Image>
      <div className="w-screen h-screen bg-black/20 z-10" />
      <div className="w-fit min-w-[500px] h-auto bg-[#F2F2F2]/95 z-20 absolute rounded-lg items-center flex flex-1 px-10 py-5 flex-col">
        <span className="text-primary text-3xl font-semibold">Login</span>
        <span className="text-text-primary mt-3">
          Welcome back! Please enter your details.
        </span>
        <form action={callAPI} className="mt-5 w-full">
          <Input label="Email Id" placeholder="Enter your email"></Input>
          <Input label="Password" placeholder="Enter your passoword"></Input>
          <Button className="w-full" type="submit">
            Sign In
          </Button>
        </form>

        <span className="mt-5 text-text-primary text-sm">
          {"Don't have account? "}
          <Link href={"/signup"}>
            <span className="text-primary font-semibold">Sign Up</span>
          </Link>
        </span>
      </div>
    </div>
  );
}

action.ts

"use server";
import { callSignInUser } from "@/lib/server";

export async function signInUser(email: string, password: string) {
  const res = await callSignInUser(email, password);
  return res;
}

index.ts

export const API_BASE_URL = "http://xxxxxx/api/";

export const createAPIEndpoint = (path: string) => {
  return `${API_BASE_URL}${path}`;
};

server.ts

import { isOKResponse, returnError, returnSuccess } from "@/types";
import { createAPIEndpoint } from ".";

export const callSignInUser = async (email: string, password: string) => {
  const json = {
    email: email,
    password: password,
  };
  const response = await fetch(createAPIEndpoint(`/v1/auth/jwt/create/`), {
    method: "POST",
    body: JSON.stringify(json),
  });
  try {
    const data = await response.json();
    if (isOKResponse(response)) {
      return returnSuccess<{}>(data);
    }
  } catch {
    console.log("error");
  }

  return returnError(new Error("Error fetching newest news"));
};

Network Tab: enter image description here

Please can someone explain me what is wrong here? Please let me know what shall I change to make it work.

Upvotes: 0

Views: 1279

Answers (1)

Matthias
Matthias

Reputation: 81

Actual issue and why

On re-reading through I think the issue is probably something happening in your fetch call that you're not handling the error. The error will be logged out in the terminal you're running next from, not your browser console, because it's happening server side. Your browser console and network call (the screenshot you have) are only going to show you client side events.

That POST to /login you're seeing is not the fetch call. That is the signInUser server action being invoked from the login page. All server actions are POST calls under the hood, and always return 200 regardless of what actually happens in the server side function (see discussion here).

Yes, you've got a try catch block in the code below, but it won't actually catch the first function calls:

  const response = await fetch(createAPIEndpoint(`/v1/auth/jwt/create/`), {
    method: "POST",
    body: JSON.stringify(json),
  });
  try {
    const data = await response.json();
    if (isOKResponse(response)) {
      return returnSuccess<{}>(data);
    }
  } catch {
    console.log("error");
  }

If you notice your fetch call is outside of the try catch block, and so if either there's an error in your fetch call or your createAPIEndpoint it will just throw an error then and there and not be caught by the catch. You'd want to move that fetch call inside the try catch block.

I've created a stripped down version here to show you simplified flow.

In short, it just removes the extra server actions calls. I don't have your auth API, so I've just substituted a test one, but that call to an external API is there.


Extra

When going through your code I noticed a couple of other things:

  1. In React any component is self closing, so for Image you can just have:
      <Image
        src={lady}
        alt="lady"
        fill={true}
        placeholder="empty"
        priority={false}
        style={{ objectFit: "cover" }}/>

rather than having to have the additional </Image> after it.

  1. I can't see what is in your type file, but this returnSuccess<{}>(data); seems a little odd. You've got returnSuccess as a generic with a type of an empty object.

  2. isOKResponse and returnError are being imported from your types file too, but you're invoking them as functions?

  3. You're defining a function inside and then exporting from index.ts, this is fine but it's an odd use of an index file. Typically, these are used in a directory as central export point for other files in that directory. Eg, you'd have a directory of auth.ts, utils.ts and others. And then in the index.ts file you'd just have export {authFunction1...} from './auth.ts'. As I say, it's just a file really you can do what you want with it but just thought I'd mention it.

  4. return returnError(new Error("Error fetching newest news")); I don't know what that returnError function is doing. But as this is an async call you probably don't want to be returning an error; instead just throw it. Because it's going to resolve as a promise, it would require you to check the value of the resolved promise directly.

It could be fine here, as I don't know what that returnError function is doing. But it's an odd pattern you will want to avoid if you are actually returning an error - it will get even worse if you ever have to chain any promises. Typically you only want to use return a value where it's a sync function because you'll be using the value anyway.

  1. You've probably heard this, and if you're just playing around with auth feel free to ignore it, but if you are trying to build something give to actual uses I'd recommend just using something like nextauth or an auth library.

Upvotes: 1

Related Questions