Tech Stack
react - 17.0.2
react-dom - 17.0.2
next - 11.1.1
Contexts
userContext.js
import React from "react";
const UserContext = React.createContext(null);
export default UserContext;
utilContext.js
import React from "react";
const UtilContext = React.createContext(null);
export default UtilContext;
_app.js - docs
import { useState, useEffect } from "react";
import { ChakraProvider } from "#chakra-ui/react";
import { Provider as ReduxProvider } from "react-redux";
import { useStore } from "../store";
import UserContext from "#src/context/userContext";
import UtilContext from "#src/context/utilContext";
function MyApp({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState);
const [user, setUser] = useState(null);
const [showNav, setShowNav] = useState(true);
const [showToTop, setShowToTop] = useState(false);
useEffect(() => {
const user = getLocalStorage("user");
if (user) {
setUser(JSON.parse(user));
}
// show and hide navbar
let prevScrollpos = window.pageYOffset;
window.onscroll = function () {
let currentScrollPos = window.pageYOffset;
if (prevScrollpos > currentScrollPos) {
setShowNav(true);
} else {
setShowNav(false);
}
prevScrollpos = currentScrollPos;
// scroll to top button
if (
document.body.scrollTop > 20 ||
document.documentElement.scrollTop > 20
) {
setShowToTop(true);
} else {
setShowNav(true);
setShowToTop(false);
}
};
}, []);
const updateUser = (data) => {
let localUser = getLocalStorage("user");
if (localUser) {
localUser = JSON.parse(localUser);
} else {
localUser = {};
}
const mergeUser = { ...localUser, ...data };
setUser(mergeUser);
setLocalStorage("user", JSON.stringify(mergeUser));
};
return (
<>
<ChakraProvider>
<ReduxProvider store={store}>
<UserContext.Provider value={{ data: user, setUser: updateUser }}>
<UtilContext.Provider value={{ showNav, showToTop }}>
<Component {...pageProps} />
</UtilContext.Provider>
</UserContext.Provider>
</ReduxProvider>
</ChakraProvider>
</>
);
}
export default MyApp;
component.js
import UserContext from "#src/context/userContext";
const Component=()=>{
const user = useContext(UserContext);
const [posts, setPosts] = useState({});
useEffect(() => {
console.log("User changed..")
if (!user.data?._id) return;
setPosts({ loading: true });
GET(`/api/post/fetch/all/by-author/${user.data._id}?private=true`)
.then((res) => setPosts({ data: res.data, loading: false }))
.catch((err) => setPosts({ err: true, loading: false }));
}, [user]);
// render posts which is a long list
}
The problem
You can see in the _app.js file, I am updating the utilContext on the window.scroll event.
But updating utilContext is also triggering the hook of Component.
And, whenever I scroll the page, then I got this message logged on the console.
I don't see this anywhere that a context update will update the rest of the contexts in the application, please let me know if I am doing something wrong.
User changed..
User changed..
User changed..
...
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 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
I have a problem to set the user in my web page. I used AppContext where all the components to update the states are. When I run my code, it says that setUser is undefined, but I defined it in AppContext and imported it in App.jsx. Anyone can help? I saw others solution and it seems that
const { setUser } = useContext(AppContext);
have to be out of the component, but shows this error:
TypeError: Cannot destructure property 'setUser' of 'Object(...)(...)' as it is undefined.
App
D:/ELEARNING/PROYECTS/PROYECT-ANDREA/store-lopez-andrea/src/App.js:11
8 | import { Router } from "./routers/Router";
9 |
10 | function App() {
> 11 | const { setUser } = useContext(AppContext);
12 | useEffect(() => {
13 | getUser().then((user) => {
14 | setUser(user);
This is App.jsx:
import "./App.css";
import { Header } from "./components/header/Header";
import { Nav } from "./components/nav/Nav.jsx";
import { getUser } from "./services/users";
import { AppContext } from "./context/AppContext";
import { Notification } from "./components/notification/Notification";
import { Router } from "./routers/Router";
function App() {
const { setUser } = useContext(AppContext);
useEffect(() => {
getUser().then((user) => {
setUser(user);
});
}, [setUser]);
return (
<div>
<Notification />
<Nav />
<Header />
<Router />
<AppContext />
</div>
);
}
export default App;
This is AppContext.jsx:
import React,{ useState } from "react";
import { usePagination } from "../components/utils/pagination.jsx";
export const AppContext = React.createContext();
export default function AppProvider({ children }) {
const [user,setUser] = useState({})
const [points, setPoints] = useState(0)
const [products, setProducts] = useState([])
const [reedemStatus, setReedemStatus] = useState({})
const [history, setHistory] = useState([])
const paginationList = usePagination(products, 16)
const paginationHistoryList = usePagination(history, 16)
const totalProducts = products.length
const totalHistory = history.length
const handlerAddPoint =(value)=>{
const newUser = {...user}
newUser.points = user.points + value
setUser(newUser)
}
const handlerSubtractPoint =(points)=>{
const newUser = {...user}
newUser.points = user.points - points
setUser(newUser)
}
return(
<AppContext.Provider value={{user,
setUser,
handlerAddPoint,
handlerSubtractPoint,
points,
setPoints,
products,
setProducts,
totalProducts,
paginationList,
reedemStatus,
setReedemStatus,
history,
setHistory,
paginationHistoryList,
totalHistory}}>
{children}
</AppContext.Provider>
);
}
In Users.jsx I have the "getUser" component
import {BASE_URL, headers} from "./constant"
export const getUser = async()=>{
try{
const response= await fetch(BASE_URL+"user/me",{headers})
const data = await response.json()
return data
} catch (error){
console.log(error)
}
}
You can access the variables and functions in your context only within components embedded in your provider. In order to achieve what you want, menage to put your App component in AppProvider. You can for example do it like so in your index file:
ReactDOM.render(
<React.StrictMode>
<AppProvider>
<App />
</AppProvider>
</React.StrictMode>,
document.getElementById("root")
);
And you don't need to have <AppContext /> in your return.
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.