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? : {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
When i run the logInAsGuest
function, I got 2 different result about the state of authUser.
Here is the result
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
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.
Very unconventional way of doing things here.
React is cyclic. Actions lead to updates. Update leads to rerender.
First thing:
Your
isLogin
is completely detached from yourauthUser
state. You initialize it withfalse
, but never update it. So you have twoisLogin
s, one fromauthUser
and another state variable. Firstly have one single source of truth.Read isLogin from authUser, by simply doing
const isLogIn = authUser.isLogin
. No need forsetIsLogin
as now, authService will tell whetherisLogin
istrue
orfalse
.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:
I have updated the new value in React context, and now the cycle is complete.