skip to Main Content

i’m trying to build an AuthContext. But i’m facing the issue in changing the state of the variable. So here is my code

index.js

import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useEffect, useState, createContext, useContext } from "react";
import Login from "./login";
import ClientPage from "./client";
import Dashboard from "./admin";
import fetchUrl from "./fetch";
import { useAuth } from "./Auth/AuthProvider";

export default function Home() {
  const [title, setTitle] = useState("Login duls Kang");
  const [version, setVersion] = useState("1.0.0");
  const { authUser, setAuthUser, isLogin, setIsLogIn } = useAuth();

  console.log(authUser);

  useEffect(() => {
    // the problem occur here
    console.log("There is a change");
    console.log("CUR", authUser.state);
    console.log("CUR STATE", authUser);
    // setIsLogIn(authUser.state.isLogin);
    console.log("CHANGE", authUser, isLogin);
  }, [authUser]);

  return (
    <div>
      <Head>
        <title>{title}</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="relative w-full pt-[5%] pb-[2%] h-screen min-h-fit flex flex-col items-center justify-start">
        <div className="absolute w-[30%] md:w-[10%] min-h-fit text-[3.7vw] md:text-[1.4vw] left-[2%] top-[2%] flex flex-col justify-start items-start">
          <div className="aspect-[20/5] w-[100%] left-[2%] top-[2%]">
            Testing: {version}
          </div>
          <div className="aspect-[20/5] w-[100%] left-[2%] top-[6%]">
            Auth? &nbsp;&nbsp;: {authUser.state.admin ? "Admin" : "Client"}
          </div>
          <div className="aspect-[20/5] rounded-[5%/15%] flex justify-center items-center w-[100%] left-[2%] top-[10%] bg-[purple] text-[white]">
            <button
              onClick={async () => await authUser.logOut()}
              className="w-full h-full"
            >
              LogOut
            </button>
          </div>
        </div>

        <div className="w-[85%] md:w-[40%] mt-[25%] md:mt-[0%] min-h-fit h-screen bg-[grey] rounded-[2%/2%] flex flex-col items-center justify-start overflow-y-scroll">
          {!isLogin && <Login></Login>}
          {isLogin && authUser.state.admin && <Dashboard auth={auth} />}
          {isLogin && !authUser.state.admin && <ClientPage />}
        </div>
        <div className="aspect-[200/50] w-[40%] bg-[white]"></div>
      </div>
    </div>
  );
}

AuthProvider.js

import React, {
  Component,
  createContext,
  useState,
  useContext,
  useEffect,
} from "react";
import AuthService from "./AuthService";

const AuthContext = React.createContext();

export function useAuth() {
  return useContext(AuthContext);
}

export function AuthProvider(props) {
  const [authUser, setAuthUser] = useState(new AuthService());
  const [isLogin, setIsLogIn] = useState(false);

  const value = {
    authUser,
    setAuthUser,
    isLogin,
    setIsLogIn,
  };

  return (
    <AuthContext.Provider value={value}>{props.children}</AuthContext.Provider>
  );
}

AuthService.js

const axios = require("axios").default;
axios.defaults.baseURL = "http://localhost:1337/api";

class AuthService {
  constructor() {
    this.state = {
      admin: false,
      jwt: "",
      email: "",
      username: "",
      isLogin: false,
    };
  }

  currentUser() {
    return this.state;
  }

  LogInAsGuest() {
    console.log("Logging in as guest... Auth context");
    this.state = {
      admin: false,
      jwt: "",
      email: "",
      username: "",
      isLogin: true,
    };
    console.log(this.currentUser());
  }

  logOut() {
    this.state = {
      admin: false,
      jwt: "",
      email: "",
      username: "",
      isLogin: false,
    };
    // }
    console.log(this.currentUser());
  }
}

export default AuthService;

login.js

import { useContext, useState } from "react";
import { useAuth } from "./Auth/AuthProvider";

const Login = ({}) => {
  const [credential, setCredential] = useState({ username: "", password: "" });
  const { authUser, setAuthUser, isLogin, setIsLogIn } = useAuth();
  return (
    <>
      <div className="flex flex-col text-[3.75vw] md:text-[1.25vw] justify-start gap-[6%] items-center w-[100%] h-[70%]">
        <div className="aspect-[200/35] w-[100%] bg-[lightgrey] text-center text-[6vw] md:text-[2vw] flex justify-center items-center rounded-[2%/10%]">
          Login duls Bray
        </div>
        <div className="relative aspect-[200/30] md:aspect-[200/20] w-[70%] bg-[green] rounded-[2%/12%]">
          <input
            className="absolute w-full h-full p-[5%] rounded-[2%/12%]"
            placeholder="username"
            onChange={(e) => {
              setCredential({
                ...credential,
                username: e.target.value,
              });
            }}
          />
        </div>
        <div className="relative aspect-[200/30] md:aspect-[200/20] w-[70%] bg-[green] rounded-[2%/12%]">
          <input
            className="absolute w-full h-full p-[5%] rounded-[2%/12%]"
            type="password"
            placeholder="password"
            onChange={(e) => {
              setCredential({
                ...credential,
                password: e.target.value,
              });
            }}
          />
        </div>
        <div className="relative aspect-[200/30] md:aspect-[200/20] w-[70%] bg-[green] rounded-[2%/12%]">
          <button
            onClick={async () => {
              const { username, password } = credential;
              await setCredential({
                username: "",
                password: "",
              });
              await authUser.LogIn({
                identifier: username,
                password: password,
              });
            }}
            className="absolute w-full h-full rounded-[2%/12%] hover:bg-[lightgreen] active:bg-[green]"
          >
            Login
          </button>
        </div>
        <div className="relative aspect-[200/30] md:aspect-[200/20] w-[70%] bg-[green] rounded-[2%/12%]">
          <button
            onClick={async () => {
              console.log("Logging in as guest...");
              await authUser.LogInAsGuest();
            }}
            className="absolute w-full h-full rounded-[2%/12%] hover:bg-[lightgreen] active:bg-[green]"
          >
            Log in as client
          </button>
        </div>
      </div>
    </>
  );
};

export default Login;

_app.js

import "../styles/globals.css";
import "tailwindcss/tailwind.css";
import { AuthProvider } from "./Auth/AuthProvider";

function MyApp({ Component, pageProps }) {
  return (
    <AuthProvider>
      <Component {...pageProps} />
    </AuthProvider>
  );
}

export default MyApp;

And here is the initial state of authUser

enter image description here

When i run the logInAsGuest function, I got 2 different result about the state of authUser.
Here is the result
enter image description here

So, when I print the authUser object directly, it shows that the isLogin state is already changed into true. But, when i print the the isLogin state via authUser.currentUser().isLogin, it shows that it is still false.

The question is, is there any way to update the isLogin state by using function inside AuthService class? Thank you for the help

2

Answers


  1. You can use custom events to notify subscribers of a change to your AuthService state. I’ve done this myself by taking advantage of the standard EventTarget and CustomEvent classes.

    Add a private EventTarget instance to your AuthService and expose an addEventListener and removeEventListener methods that serve as wrappers around the same methods in your EventTarget. Now, whenever the authentication state changes you can use your EventTarget to dispatchEvent and notify any subscribers.

    To get this working with React Hooks, use the useEffect hook to subscribe to the AuthService event(s). Return a function from useEffect that handles removing the event listener as well.

    To synchronize state across tabs, use LocalStorage to track authentication state, and subscribe to Storage Events to listen for when another tab updates the Auth state.

    Finally, make sure your AuthService is a singleton. Currently you are instantiating the service multiple times, each with its own state. This means they will get out of sync. Rather than export the AuthServuce class itself, export an instance thereof that everyone uses. Alternatively, you can use createContext to instantiate a single instance of the AuthService and useContext to retrieve it. Either way, don’t be instantiating multiple instances of the service.

    Login or Signup to reply.
  2. Very unconventional way of doing things here.
    React is cyclic. Actions lead to updates. Update leads to rerender.

    First thing:

     const [isLogin, setIsLogIn] = useState(false);
    
      const value = {
        authUser,
        setAuthUser,
        isLogin,
        setIsLogIn,
      };
    

    Your isLogin is completely detached from your authUser state. You initialize it with false, but never update it. So you have two isLogins, one from authUser and another state variable. Firstly have one single source of truth.

    Read isLogin from authUser, by simply doing const isLogIn = authUser.isLogin. No need for setIsLogin as now, authService will tell whether isLogin is true or false.

    Second, you are updating mutating state in non React way and that update will not be propagated to React. I would suggest you to run your rendering updates using React only. For that you can do something like this:

              <button
                onClick={async () => {
                  console.log("Logging in as guest...");
                  await authUser.LogInAsGuest();
                  const newVal = authUser.currentUser();
                  setAuthUser(newVal);
                }}
                className="absolute w-full h-full rounded-[2%/12%] hover:bg-[lightgreen] active:bg-[green]"
              >
                Log in as client
              </button>
    

    I have updated the new value in React context, and now the cycle is complete.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search