Qwik JS error - Only primitive and object literals can be serialized

252 Views Asked by At

I have a signin page which is built upon Qwik & appwrite. There is a piece of code where I'm trying to get the current user's session details & If I get the session details then I'm redirecting the user to the "dashboard" page.

Below is the code which I'm using for redirecting the user & the line nav("/dashboard"); is generating the error when it's got enabled. If I commented, the line page works fine but without any redirection. -

useTask$(({ track }) => {
  track(() => sid);

  if (sid != "") {
    nav("/dashboard");
  }
});

Screenshot of error -

enter image description here

Complete Signin page code -

import { $, component$, useTask$ } from "@builder.io/qwik";
import { routeLoader$, z, RequestEvent } from "@builder.io/qwik-city";
import type { DocumentHead } from "@builder.io/qwik-city";
import { Link, useNavigate, routeAction$ } from "@builder.io/qwik-city";
import type { InitialValues, SubmitHandler } from "@modular-forms/qwik";
import { formAction$, useForm, zodForm$ } from "@modular-forms/qwik";
import * as setCookie from "set-cookie-parser";
import {
  AppwriteEndpoint,
  AppwriteHostname,
  AppwriteProject,
  SsrHostname,
  AppwriteService,
} from "~/AppwriteService";

const loginSchema = z.object({
  email: z
    .string()
    .min(1, "Please enter your email.")
    .email("The email address is badly formatted."),
  password: z
    .string()
    .min(1, "Please enter your password.")
    .min(8, "You password must have 8 characters or more."),
});

type LoginForm = z.infer<typeof loginSchema>;

export const useFormLoader = routeLoader$<InitialValues<LoginForm>>(() => ({
  email: "",
  password: "",
}));

export const useFormAction = formAction$<LoginForm>(
  async (values, requestEvent) => {
    console.log("On Server...");

    try {
      const response = await fetch(
        `${AppwriteEndpoint}/account/sessions/email`,
        {
          method: "POST",
          headers: {
            "x-appwrite-project": AppwriteProject,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            email: values.email,
            password: values.password,
          }),
        }
      );

      const json = await response.json();
      console.log("email session json data: ", json);

      if (json.code >= 400) {
        console.log("Exception Caught1: ", json.message);
        requestEvent.json(400, { mesages: json.message });
        return;
      }

      const ssrHostname =
        SsrHostname === "localhost" ? SsrHostname : "." + SsrHostname;
      const appwriteHostname =
        AppwriteHostname === "localhost"
          ? AppwriteHostname
          : "." + AppwriteHostname;

      const cookiesStr = (response.headers.get("set-cookie") ?? "")
        .split(appwriteHostname)
        .join(ssrHostname);

      const cookiesArray = setCookie.splitCookiesString(cookiesStr);
      const cookiesParsed = cookiesArray.map((cookie: any) =>
        setCookie.parseString(cookie)
      );

      for (const cookie of cookiesParsed) {
        requestEvent.cookie.set(cookie.name, cookie.value, {
          domain: cookie.domain,
          secure: cookie.secure,
          sameSite: cookie.sameSite as any,
          path: cookie.path,
          maxAge: cookie.maxAge,
          httpOnly: cookie.httpOnly,
          expires: cookie.expires,
        });
      }

      console.log("email session created: ", requestEvent);
      requestEvent.json(200, json);

      // return requestEvent;
    } catch (err: any) {
      console.log("Exception Caught2: ", err.message);
      requestEvent.json(400, { mesages: err.message });

      // return requestEvent;
    }
  },
  zodForm$(loginSchema)
);

export const useAccountLoader = routeLoader$(async ({ cookie }) => {
  const sessionNames = [
    "a_session_" + AppwriteProject.toLowerCase(),
    "a_session_" + AppwriteProject.toLowerCase() + "_legacy",
  ];

  const hash =
    cookie.get(sessionNames[0])?.value ??
    cookie.get(sessionNames[1])?.value ??
    "";

  AppwriteService.setSession(hash);

  // let account;
  // try {
  //   account = await AppwriteService.getAccount();
  // } catch (err) {
  //   console.log(err);
  //   account = null;
  // }

  // Neccessary fix row now, until "XMLHttpRequest is not defined" is fixed
  const authCookies: any = {};
  authCookies["a_session_" + AppwriteProject] = hash;
  let account;
  try {
    const response = await fetch(`${AppwriteEndpoint}/account`, {
      method: "GET",
      headers: {
        "x-appwrite-project": AppwriteProject,
        "x-fallback-cookies": JSON.stringify(authCookies),
      },
    });

    if (response.status >= 400) {
      throw new Error(await response.text());
    }

    account = await response.json();
  } catch (err) {
    console.log(err);
    account = null;
  }

  return {
    account,
  };
});

export default component$(() => {
  const [loginForm, { Form, Field }] = useForm<LoginForm>({
    loader: useFormLoader(),
    action: useFormAction(),
    validate: zodForm$(loginSchema),
  });

  const nav = useNavigate();

  console.log("loginForm Result: ", loginForm);

  const account = useAccountLoader();
  console.log("user account info1: ", account);
  console.log("user account info2: ", account.value);
  console.log("user account info3: ", account.value?.account?.$id);

  let sid: string = "";
  if (account.value?.account?.$id) {
    sid = account.value.account.$id;
  }
  console.log("sid: ", sid);

  useTask$(({ track }) => {
    track(() => sid);

    if (sid != "") {
      nav("/dashboard");
    }
  });

  type signinFormData = {
    email: string;
    password: string;
  };

  const handleSubmit: SubmitHandler<LoginForm> = $(
    (values: signinFormData, event) => {
      // Runs on client
      console.log("Form data in client side: ", values);
    }
  );

  return (
    <>
      <section class="bg-gray-50 dark:bg-gray-900">
        <div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
          <a
            href="#"
            class="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white"
          >
            <img
              class="w-8 h-8 mr-2"
              src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/logo.svg"
              alt="logo"
            />
            Brandname
          </a>
          <div class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
            <div class="p-6 space-y-4 md:space-y-6 sm:p-8">
              <h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
                Sign in to your account
              </h1>
              <Form class="space-y-4 md:space-y-6" onSubmit$={handleSubmit}>
                <Field name="email">
                  {(field, props) => (
                    <div>
                      <label
                        for="email"
                        class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                      >
                        Your email
                      </label>
                      <input
                        {...props}
                        type="email"
                        value={field.value}
                        class={[
                          "bg-gray-50 border  text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
                          field.error ? "border-red-300" : "border-gray-300",
                        ]}
                        placeholder="[email protected]"
                      />

                      {field.error && (
                        <p class="mt-2 text-sm text-red-600 dark:text-red-500">
                          {field.error}
                        </p>
                      )}
                    </div>
                  )}
                </Field>

                <Field name="password">
                  {(field, props) => (
                    <div>
                      <label
                        for="password"
                        class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
                      >
                        Password
                      </label>
                      <input
                        {...props}
                        type="password"
                        value={field.value}
                        placeholder="••••••••"
                        class={[
                          "bg-gray-50 border text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
                          field.error ? "border-red-300" : "border-gray-300",
                        ]}
                      />

                      {field.error && (
                        <p class="mt-2 text-sm text-red-600 dark:text-red-500">
                          {field.error}
                        </p>
                      )}
                    </div>
                  )}
                </Field>

                <div class="flex items-center justify-between">
                  <div class="flex items-start">
                  </div>
                  <a
                    href="#"
                    class="text-sm font-medium text-primary-600 hover:underline dark:text-primary-500"
                  >
                    Forgot password?
                  </a>
                </div>

                <button
                  type="submit"
                  class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
                >
                  Sign in
                </button>
                <p class="text-sm font-light text-gray-500 dark:text-gray-400">
                  Don’t have an account yet?
                  <Link
                    href="/account/signup"
                    class="ml-3 font-medium text-primary-600 hover:underline dark:text-primary-500"
                  >
                    Sign up
                  </Link>
                </p>
              </Form>
              {JSON.stringify(loginForm.response)}

              {loginForm.response.status === false &&
                (loginForm.response.data.error.type ===
                  "general_rate_limit_exceeded" ||
                  loginForm.response.data.error.type ===
                    "user_invalid_credentials") && (
                  <>
                    <div
                      class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400"
                      role="alert"
                    >
                      {loginForm.response.data.error.message}
                    </div>
                  </>
                )}
            </div>
          </div>
        </div>
      </section>
    </>
  );
});

export const head: DocumentHead = {
  title: "Signin Page",
  meta: [
    {
      name: "description",
      content: "Qwik site description",
    },
  ],
};

UPDATE

I've created a GitHub repo with all the codes. Here is the link: https://github.com/mi6crazyheart/qwik-flowbite-starter-kit

App description:

This app uses appwrite for authentication & flowbite(https://flowbite.com/) as its css library.

Setup steps:

  1. I'm using Appwrite through a docker container. This is the setup documentation which can be followed for the setup: https://appwrite.io/docs/self-hosting

  2. After setting up the Appwrite docker instance, we need to add all project configuration details in the "AppwriteService.ts" of our Qwik app.

  3. We need to create a new user account using the below signup link & signup for a new user account: http://localhost:5173/account/signup/

  4. Then, we need to log in to our user account using the sign-in link below. In this step, I get the error when jumping to the below sign-in link. Sign in to the user account: http://localhost:5173/account/signin/

1

There are 1 best solutions below

0
On BEST ANSWER

If you replace useTask$ with useVisibleTask$ you will solve the issue.

Btw you can improve your code by using redirect

export const useAccountLoader = routeLoader$(async ({ cookie, redirect }) => {
  [...]
  if (account?.$id) {
    throw redirect(307, '/dashboard')
  }
});