I am building an app using next13 (to make use of server side components), however, for some reason my existing AuthContext is not working. I am getting the following error:
TypeError: React.createContext is not a function
From what I can see, the AuthContext needs to be set to 'use client', as there is use of useState and useEffect within it, but for some reason the application no longer recognises that createContext is actually a function.
This is my AuthContext:
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { onAuthStateChanged, signOut, signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'firebase/auth';
import { auth } from '../config';
const AuthContext = createContext({});
export const useAuth = () => useContext(AuthContext);
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setLoading(true);
setUser(user ?? null);
setLoading(false);
});
return () => unsubscribe();
}, []);
const login = async (email, password) => {
await signInWithEmailAndPassword(auth, email, password);
};
const logout = async () => {
setUser(null);
await signOut(auth)
};
const register = async (email, password) => {
try {
const userCred = await createUserWithEmailAndPassword(auth, email, password);
await userCred.user.sendEmailVerification({
url: process.env.NEXT_PUBLIC_HOST
});
} catch (err) {
return {
errorCode,
errorMessage
}
}
};
return (
<AuthContext.Provider value={{ user, loading, login, logout, register }}>
{children}
</AuthContext.Provider>
);
};
The AuthContext is then used in my main layout page within the app directory:
'use client';
import { CssBaseline, Container } from '#mui/material';
import { NavBar, Footer } from '../components';
import { AuthContextProvider } from '../context';
import '#fontsource/roboto/300.css';
import '#fontsource/roboto/400.css';
import '#fontsource/roboto/500.css';
import '#fontsource/roboto/700.css';
const RootLayout = ({ children }) => {
return (
<html lang='en'>
<head>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<AuthContextProvider>
<CssBaseline />
<NavBar />
<Container component='main' sx={{ padding: 3 }}>
{children}
</Container>
<Footer />
</AuthContextProvider>
</body>
</html>
);
}
export default RootLayout;
I am unsure if I need to take a different approach to authentication, perhaps using the next-auth package, but I am not sure what the best way would be.
Cheers for any help!
Here's an example of useContext I am using on my application.
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
import { getAuth, User } from 'firebase/auth'
import { initializeApp, getApps, getApp } from 'firebase/app'
import nookies from 'nookies'
const firebaseConfig = {
...
}
getApps().length ? getApp() : initializeApp(firebaseConfig)
const auth = getAuth()
const AuthContext = createContext<User | null>(null)
export function AuthProvider({ children }: any) {
//
const [user, setUser] = useState<User | null>(null)
useEffect(() => {
return auth.onIdTokenChanged(async (user) => {
if (!user) {
setUser(null)
nookies.set(undefined, 'token', '', { path: '/' })
} else {
const token = await user.getIdToken()
setUser(user)
nookies.set(undefined, 'token', token, { path: '/' })
}
})
}, [])
useEffect(() => {
const handle = setInterval(async () => {
const user = auth.currentUser
if (user) await user.getIdToken(true)
}, 15 * 60 * 1000)
return () => clearInterval(handle)
}, [])
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>
}
export const useAuth = () => {
return useContext(AuthContext)
}
Note that we're also forcing token refresh every 15 minutes, and saving that to cookies. You can access cookies in server pages using the new next13 cookies package.
You can also get the user by importing the useAuth hook we just created.
For example
'use client'
import useAuth from '../context/AuthProvider'
const Page = () => {
const {user} = useAuth()
// Rest of your application
}
Hope it helps
Related
I am trying to make a connect button when the user clicks on it and connect he store the value inside a global state that i can use inside the whole application but this code doesn't work at all , why is it wrong ?
import React, { useState, useEffect } from 'react';
export const UserContexts = React.createContext();
const UserContext = ({children}) => {
const [getUser, setGetUser] = useState(null);
function connect() {
ethereum.request({ method : 'eth_requestAccounts'}).then(accounts => {
const account = accounts[0];
setGetUser(account)
})
}
useEffect(() => {
getUser ? null : connect();
},[])
const { Provider } = UserContexts;
return (
getUser ? <Provider value={getUser} >
{children}
</Provider>: null
)
}
export default UserContext
// navbar
import UserContext from './userContext'
<button onClick={UserContext.connect()} > connect </button>
when a user clicks on navbar connect button he login then when he login the _app saves the state globally so I can use it everywhere inside the app , I know this is a wrong syntax but how can I make it work ?
I solved this problem combined useContext and useReducer.
import React, {createContext,useContext,useEffect,useReducer} from "react";
const UserContext = createContext();
export function useBlockchainContext() {
return useContext(BlockchainContext);
}
function reducer(state, { type, payload }) {
return {
...state,
[type]: payload,
};
};
const init_state = {
user: ""
}
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, init_state);
return (
<BlockchainContext.Provider value={[state, dispatch]}>
{children}
</BlockchainContext.Provider>
)
}
// navbar
import { useBlockchainContext } from "../userContext";
export default function NavBar() {
const [state,dispatch] = useBlockchainContext();
const connect = ()=> {
ethereum.request({ method : 'eth_requestAccounts'}).then(accounts => {
const account = accounts[0];
dispatch({
type: "user",
payload: {
account
}
});
})
};
return(
<button onClick={()=>connect()} >{state.user !==""?state.user.slice(0, 4) + "..." + state.user.slice(-4):connect}</button>
)
}
I should make user auth with React JS and Firebase. I have written the codes bellow and getting
POST https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=XXXXXXX%3B 400 I have done everything following the instruction but don't know what the problem is. When the user signs up, it is not appearing in firebase identifier.
base.js
import * as firebase from "firebase/app";
import {getAuth} from "firebase/auth";
const app = firebase.initializeApp({
...
});
export const auth = getAuth(app);
export default app;
AuthContext.js
import React, { useState, useContext, useEffect } from "react";
import { auth } from "../base";
import {
createUserWithEmailAndPassword,
onAuthStateChanged,
} from "#firebase/auth";
export const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
function signup(email, password) {
return createUserWithEmailAndPassword(auth, email, password);
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
signup,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
Code in signUp page
const { signup, currentUser } = useAuth();
const handleSubmit = async (e) => {
e.preventDefault();
const v1 = USER_REGEX.test(user);
const v2 = PWD_REGEX.test(pwd);
if (!v1 || !v2) {
setErrMsg("Invalid Entry");
return;
}
try {
setErrMsg('')
setLoading(true)
await signup(emailRef.current.value, pwdRef.current.value);
} catch {
setErrMsg("Failed to create an account");
}
setLoading(false)
console.log(user, emailRef, pwdRef);
setSuccess(true);
};
I have a AuthProvider and useAuth method that returns useContext(AuthContext) inside my App.js root file, however, if I console.log the return value of useAuth() in App.js, it is undefined, why??
In any other child component like Loginpage, there is an authContext displayed..
my App.js:
import { AuthProvider, useAuth } from './providers/AuthProvider'
function App() {
const auth = useAuth()
console.log({ auth }) //undefined??
AuthProvider.js:
import React, { useContext, useState, useEffect } from 'react'
import { app, auth, firestore, storage } from '../firebase'
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState(undefined)
const [loginError, setLoginError] = useState(null)
const login = async (email, password) => {
try {
return auth.signInWithEmailAndPassword(email, password)
} catch (error) {
setLoginError(error)
}
}
const logout = () => auth.signOut()
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
})
return unsubscribe
}, [])
const value = {
loginError,
login,
signup,
logout,
currentUser,
app,
firestore,
storage,
auth,
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
Since you import AuthProvider in same file of App it gives a clue that you didn't use the Context API correctly.
Context values are available for Context-Consumers.
Context-Consumers are children of Context-Provider, therefore your App component have to be a child of <AuthContext.Provider>:
<AuthProvider>
<App />
</AuthProvider>
In React app, I was going to implement Auth0 login like the following.
When the user accesses the /login, it will check if the user is authenticated.
Here is the code.
import React from 'react';
import { useHistory } from "react-router-dom";
import { useAuth0 } from "#auth0/auth0-react";
import SplashScreen from "src/components/SplashScreen";
const LoginView = () => {
const { loginWithRedirect, isAuthenticated } = useAuth0();
const history = useHistory();
React.useEffect(() => {
if (isAuthenticated) {
history.push("/explore");
} else {
loginWithRedirect();
}
}, [isAuthenticated])
return <SplashScreen />;
};
export default LoginView;
But when I log in from the Auth0 login page, I am redirected to the /login page of the React app, and it falls in looping indefinitely.
I tried to log out the isAuthenticated value on the console like in the above code, but it is false even though I logged in on the Auth0 authentication page correctly.
Please let me know how I can solve this issue.
Thanks.
This code is working.
import React from 'react';
import { useHistory } from "react-router-dom";
import { useAuth0 } from "#auth0/auth0-react";
import SplashScreen from "src/components/SplashScreen";
const LoginView = () => {
const { isAuthenticated, loginWithRedirect } = useAuth0();
const history = useHistory();
React.useEffect(() => {
async function checkUser() {
if (isAuthenticated) {
await history.push("/explore");
} else {
loginWithRedirect();
}
}
checkUser(); // called async checkUser()
}, [isAuthenticated, loginWithRedirect]); // added loginWithRedirect
return <SplashScreen />;
}
export default LoginView;
I used the below pattern source: https://thinkster.io/tutorials/auth0-react-login-and-user-profile/protect-the-profile-route
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
useEffect(() => {
if (loading || isAuthenticated) {
return;
}
const fn = async () => {
await loginWithRedirect({
appState: { targetUrl: path }
});
};
fn();
}, [loading, isAuthenticated, loginWithRedirect, path]);
I found different already answered questions to my question, but the don't help.
I use a custom context to call the firebase.auth().onAuthStateChanged() and set the currentUser.
import React, { useState, useEffect } from "react";
import app from "../firebase";
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
app.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider value={{ currentUser }}>
{children}
</AuthContext.Provider>
);
};
In my component I call the AuthContext and the currentUser:
import React, { useContext, useEffect, useState } from "react";
import app from "./firebase";
import { AuthContext } from "./Auth/Auth";
function MyComponent() {
const [invoices, setInvoices] = useState([]);
const { currentUser } = useContext(AuthContext);
const getInvoices = () => {
const database = app.firestore();
const unsubscribe = database
.collection("invoices")
.where("uid", "==", currentUser.uid) // HERE currentUser IS NULL
.orderBy("date", "desc")
.onSnapshot((snapshot) => {
setInvoices(
snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }))
);
});
return () => {
unsubscribe();
};
};
useEffect(() => {
getInvoices();
}, []);
return (<> ... </>);
}
export default MyComponent;
I believe my issue has something to do with promises and the user is not yet loaded. But still I don't know what to do here.
The potential issue could be the value of currentUser returns a bit later so you need to add an extra check in your MyComponent component.
I would add null check for currentUser and extend the dependency array as:
useEffect(() => {
if (currentUser) {
getInvoices();
}
}, [currentUser]);
Probably in the first round the useEffect callback was running once currentUser was still null.