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.
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 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 can't render props from useContext because it returns undefined before desired object. I can't seem to find any solution to my problem.
This is the child element I'm trying to render:
const Reviews = ({reviews}) => {
return (
<div className={styles['review__card']}>
{reviews.map((review) => {
return(
<div className={styles['review__card__item']}>
<div className={styles['card__item__meta']}>
<span>{review.name}</span>
<span><StarRating rating={review.rating}/></span>
</div>
<div className={styles['card__item__p']}>
<p>{review.revew}</p>
</div>
</div>
)})}
</div>
)
}
export default Reviews
This is Parent Element:
import React, { useContext, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { RestaurantsContext } from '../context/RestaurantsContext';
import Wrapper from '../components/Wrapper'
import Header from '../components/Header'
import Reviews from '../components/Reviews'
import AddReview from '../components/AddReview'
import RestaurantFinder from '../apis/RestaurantFinder';
const RestaurantDetailPage = () => {
const {id} = useParams()
const {selectedRestaurant, setSelectedRestaurant} = useContext(RestaurantsContext)
useEffect(()=> {
const fetchDate = async () => {
try {
const response = await RestaurantFinder.get(`/${id}`)
setSelectedRestaurant(response.data.data)
}
catch (err) {console.log(err)
}
}
fetchDate()
}, [])
console.log(selectedRestaurant.reviews)
return (
<Wrapper>
<Header title={ 'RestaurantDetailPage' }/>
<div>{selectedRestaurant && (
<>
<Reviews reviews={selectedRestaurant.reviews}/>
<AddReview/>
</>
)}</div>
</Wrapper>
)
}
export default RestaurantDetailPage
Whenever I console.log(selectedRestaurant.reviews) it gives me undefined and then it gives me object query. I assume that .map() is getting an error because it is trying to render that first argument which is undefined
Here is Context js
import React, {useState, createContext} from 'react';
export const RestaurantsContext = createContext();
export const RestaurantsContextProvider = props => {
const [restaurants, setRestaurants] = useState([])
const [selectedRestaurant, setSelectedRestaurant] = useState([])
const addRestaurants = (restaurant) => {
setRestaurants([...restaurants, restaurant]);
}
return (
<RestaurantsContext.Provider value={{restaurants, setRestaurants, addRestaurants, selectedRestaurant, setSelectedRestaurant }}>
{props.children}
</RestaurantsContext.Provider>
)
}
I have found the issue and solved it though I'm not entirely sure how it works.
const [selectedRestaurant, setSelectedRestaurant] = useState([])
default useState value should be null using useState(null)
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..
...
I have tried some solutions that came by, on this link particularily...
I tried changing value inside my TodosContext.js file.. which didn't work, too..
One more thing that I have tried is to call useContext() function from another component, that didn't work neither..
Here's my code.
App.js:
import React, { useState, useContext } from 'react';
import TodoList from './components/TodoList';
import NewTodo from './components/NewTodo';
import { TodosProvider, TodosContext } from './components/contextapi/TodosContext';
function App() {
const [input, setInput] = useState('');
const [todos, setTodos] = useContext(TodosContext);
const _handleInput = (e) => {
setInput(e.target.value)
}
const _todoAdd = (e) => {
if (e.key === 'Enter') {
setTodos(
[...todos, { content: input, id: Date.now(), completed: false }]
)
setInput('')
}
}
const _todoRemove = (id) => {
const newTodos = todos.filter(todo => todo.id !== id)
setTodos(newTodos)
}
return (
<div>
<header>
<h3>To-Do Manager | Context API</h3>
</header>
<TodosProvider>
<NewTodo newTodo={_todoAdd} handleInput={_handleInput} newVal={input} />
<TodoList todos={todos} />
</TodosProvider>
</div>
);
}
export default App;
TodosContext.js:
import React, { useState, createContext } from 'react';
export const TodosContext = createContext()
export const TodosProvider = ({ children }) => {
const [todos, setTodos] = useState([]);
return (
<TodosContext.Provider value={[todos, setTodos]}>{children}</TodosContext.Provider>
)
}
TodoList.js:
import React, { useContext } from 'react';
import Todo from './Todo';
import RemoveTodoFromList from './RemoveTodoFromList';
import { TodosContext } from './contextapi/TodosContext'
function TodoList() {
const [todos, setTodos] = useContext(TodosContext);
return (
<div>
{todos.map(todo => (
<div>
<Todo key={todo.id} todo={todo} />
</div>
))}
</div>
)
}
export default TodoList
I'm really struggling with this one, I spent whole day figuring out what went wrong.. Thanks!
We fixed it inside the comments.
createContext needs an object as parameter that defines your context.
In your case it should be export const TodosContext = createContext([[],() => {}]).
So the application knows the first element of the tuple is an array and so iterable.