While using Multiple context in react hooks getting blank - reactjs

I am new to context api. I am trying to use multiple context.
I have created a UserContext and GlobalContext which I'm trying to use in the app.js but as soon as I put UserContext I get the blank page.
here is my app.js
import { useEffect } from 'react';
import { GlobalProvider } from './context/GlobalState'
import './App.css'
import { Home } from './pages/Home';
import { Login } from './pages/Login';
import { Register } from './pages/Register';
import axios from 'axios';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { UserProvider } from './context/UserContext';
const App = () => {
return (
<UserProvider>
<GlobalProvider>
<BrowserRouter>
<Routes>
<Route path='/login' element={<Login />} />
<Route path='/register' element={<Register />} />
<Route path='/home' element={<Home />} />
</Routes>
</BrowserRouter>
</GlobalProvider>
</UserProvider>
)
}
export default App;
Here is my UserContext.js
import axios from "axios";
import { createContext, useReducer } from "react";
import { userReducer } from "./AppReducer";
// Initial State
const initialState = {
isAuthenticated: false
}
// Creating Context
export const UserContext = createContext(initialState)
UserContext.displayName = "UserContext"
// Creating Provider
export const UserProvider = ({ childern }) => {
const [state, dispatch] = useReducer(userReducer, initialState);
// Actions
async function authenticate(user) {
try {
const config = {
Headers: {
'Content-Type': 'application/json'
}
}
const hasToken = res.data.token === undefined ? false : res.data.token;
dispatch({
type: 'AUTHENTICATE_USER',
payload: hasToken
})
const res = await axios.post('https://localhost:7188/api/user/authenticate', user, config);
if (res.data.token !== undefined) {
console.log("No token")
}
else {
console.log(res.data.token);
}
localStorage.setItem('jwt', res.data.token);
console.log(localStorage.getItem('jwt'));
} catch (error) {
console.log(error.response);
}
}
return (
<UserContext.Provider value={
{
isAuthenticated: state.isAuthenticated,
authenticate
}
}>
{childern}
</UserContext.Provider>
)
}
Here is AppReducer.js
export const userReducer = (state, action) => {
switch (action.type) {
case 'AUTHENTICATE_USER':
return {
...state,
isAuthenticated : action.payload
}
default:
return state;
}
}
When I start the server I'm getting blank page

Related

Why does React throws an error if my context is used in my custom hook for getting data from Firebase?

I'm attempting to move my data fetching to custom hooks off of the component file for better code organization and I'm having issues with the hook not working when used in conjunction with my context.
This is my AuthContext.tsx file
import React, { createContext, useEffect, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from '../firebase.config';
export const UserContext = createContext<any>({});
export const AuthContextProvider = ({ children }: any) => {
const [currentUser, setCurrentUser] = useState<unknown | null>({});
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
});
return () => {
unsub();
};
}, []);
return <UserContext.Provider value={{ currentUser }}>{children}</UserContext.Provider>;
};
This is my useRooms.ts file (my hook)
import * as React from 'react';
import { collection, query, where, getDocs, QueryDocumentSnapshot, DocumentData } from 'firebase/firestore';
import { db } from '../firebase.config';
import { UserContext } from '../context/AuthContext';
import { RoomsResult } from './types';
export default function useRooms(): RoomsResult {
const [userRooms, setUserRooms] = React.useState<string[] | null>(null);
const [isLoading, setIsLoading] = React.useState(true);
const { currentUser } = React.useContext(UserContext);
const userRoomsQuery = query(collection(db, 'rooms'), where('user', 'array-contains', currentUser.uid));
const fetchUserRoomsData = async () => {
setIsLoading(true);
const userRoomsDocs = await getDocs(userRoomsQuery);
setUserRooms(userRoomsDocs.docs.map((doc: QueryDocumentSnapshot<DocumentData>) => doc.id));
setIsLoading(false);
};
console.log(userRooms);
return { rooms: userRooms, loading: isLoading, fetchUserRoomsData };
}
The error I recieve is:
As for those file names seen in the error they are all from my App.tsx file here
import React, { useContext } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { UserContext } from './context/AuthContext';
import { SignUpForm, LogInForm, ChatRooms } from './components/exporter';
type Styles = {
wrapper: string;
};
function App() {
const styles: Styles = {
wrapper:
'bg-purple-200 h-[100vh] w-[100vw] grid grid-cols [minmax(100px,_250px)_1fr_minmax(150px,_250px)] grid-rows-[85%_minmax(50px,_350px)] absolute',
};
const { currentUser } = useContext(UserContext);
const ProtectedRoute = ({ children }: any) => {
if (!currentUser) {
return <Navigate to="/login" />;
} else return children;
};
return (
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<ProtectedRoute>
<div id="portal-container">
<ChatRooms />
</div>
</ProtectedRoute>
}
></Route>
<Route path="login" element={<LogInForm />} />
<Route path="signup" element={<SignUpForm />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
);
}
export default App;
The hook itself works as long as I do not use the line const { currentUser } = React.useContext(UserContext); inside the useRooms.ts file, but soon as I do I receive the error in picutre. I place the use of the custom Hook useRooms inside of my <ChatRooms/> component which can be seen is rendered as the most nested element in App.tsx. Any idea as to what is causing this and why ?
I managed to fix the issue by setting the default state of currentUser in AuthContextProvider to null instead of {}
Unfortunately this makes it so that when I refresh my web page it now forgets the logged in user. But this may be intended behavior until I check the Firebase Auth docs.

React useContext is returning undefined

I am trying to use context with my Gatsby project. I have successfully implemented this in my previous project and I have copied the code over to my new project and it's not working as intended.
This is my context.js file:
import React, { useContext, useState } from "react";
const defaultState = {
isLoggedIn: false,
};
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [isLoggedIn, setIsLoggedIn] = useState(false);
function toggle() {
console.log("BOO!");
}
const value = {
isLoggedIn,
setIsLoggedIn,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
This is my app.js file:
import React from "react";
import { Router } from "#reach/router";
import IndexPage from "./index";
import ProjectPage from "./project";
import { AuthProvider } from "../contexts/context";
const App = () => (
<AuthProvider>
<Router basepath="/app">
<IndexPage path="/" component={IndexPage} />
<ProjectPage path="/project" component={ProjectPage} />
</Router>
</AuthProvider>
);
export default App;
This is my index.js file:
import React, { useContext } from "react";
import { Link } from "gatsby";
import { useAuth } from "../contexts/context";
import { AuthContext } from "../contexts/context";
const IndexPage = () => {
console.log(useAuth())
return (
<div className="w-40 h-40 bg-red-400">
{/*<Link to="/project">to projects</Link>*/}
<div>Click me to toggle: uh</div>
</div>
);
};
export default IndexPage;
useAuth() should return the desired components and functions but instead is always returning undefined. I have looked over my previous code as well as snippets on stack overflow and I can't seem to find the correct fix.
The following includes code that successfully built and executed:
Original context.js
import '#stripe/stripe-js'
/* Functionality */
import React, { useContext, useEffect, useState } from "react";
import { navigate } from "#reach/router";
import firebase from 'gatsby-plugin-firebase';
import { useLocalStorage } from 'react-use';
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [isLoggedIn, setIsLoggedIn] = useState(false)
const [isLoading, setIsLoading] = useLocalStorage("loading", false);
// Sign In
const signInWithRedirect = (source) => {
let provider;
switch(source) {
case 'Google':
provider = new firebase.auth.GoogleAuthProvider()
break;
case 'Github':
provider = new firebase.auth.GithubAuthProvider()
break;
default:
break;
}
setIsLoading(true)
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
.then(() => {
// Existing and future Auth states are now persisted in the current
// session only. Closing the window would clear any existing state even
// If a user forgets to sign out.
// ...
// New sign-in will be persisted with session persistence.
return firebase.auth().signInWithRedirect(provider)
})
.catch((error) => {
// Handle Errors here.
let errorCode = error.code;
let errorMessage = error.message;
});
}
// Sign Out
const signOut = () => {
firebase.auth().signOut().then(() => {
// Sign-out successful.
setIsLoggedIn(false)
navigate('/app/login')
}).catch((error) => {
// An error happened.
});
}
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
try {
// If user is authenticated
if (!!user) {
// Fetch firestore document reference
var docRef = firebase.firestore().collection("study_guide_customers").doc(user.uid)
docRef.get().then((doc) => {
console.log('checking doc')
// If the document doesn't exist, create it and add to the firestore database
if (!doc.exists) {
console.log('inside customer')
const customer = {
customerCreationTimestamp: firebase.firestore.Timestamp.now(),
username: user.displayName,
email: user.email
}
firebase.firestore().collection("study_guide_customers").doc(user.uid).set(customer)
.then(() => {
// After docuement for user is created, set login status
setIsLoggedIn(!!user)
setIsLoading(false)
})
.catch((error) => {
console.error("Error writing document: ", error);
});
// If document for user exists, set login status
} else {
setIsLoggedIn(!!user)
setIsLoading(false)
}
})
}
} catch {
console.log('Error checking firestore existence and logging in...')
}
})
}, [isLoggedIn, isLoading, setIsLoading, setIsLoggedIn])
const value = {
signOut,
isLoggedIn,
isLoading,
setIsLoading,
setIsLoggedIn,
signInWithRedirect,
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
Original app.js
/* Stripe Security */
import '#stripe/stripe-js'
/* Functionality */
import React from "react"
import { Router } from "#reach/router"
import PrivateRoute from "../components/PrivateRoute"
import Profile from "../components/Profile"
import Login from "../components/Login"
import Projects from "../components/Projects"
import IndexPage from "./index"
import NotFoundPage from './404'
import { AuthProvider } from "../contexts/context"
const App = () => (
<AuthProvider>
<Router basepath="/app">
<PrivateRoute path="/profile" component={Profile} />
<Login path="/login" component={Login}/>
<IndexPage path="/" component={IndexPage}/>
<Projects path="/projects" component={Projects} />
</Router>
</AuthProvider>
)
export default App
Original index.js
/* Stripe Security */
import '#stripe/stripe-js'
/* Functionality */
import * as React from "react"
import IndexContact from "../components/Index/Contact"
import IndexSelectedProjects from "../components/Index/SelectedProjects"
import IndexFeaturedProjects from "../components/Index/FeaturedProjects"
import IndexFooter from "../components/Index/Footer"
import IndexStudyGuide from "../components/Index/StudyGuide"
import IndexNavbar from "../components/Index/Navbar"
import IndexHeader from "../components/Index/Header"
import IndexAbout from '../components/Index/About'
import IndexExperience from '../components/Index/Experience'
import { useMount } from 'react-use';
const IndexPage = () => {
useMount(() => localStorage.setItem('loading', false));
return (
<>
<IndexNavbar />
<IndexHeader />
<IndexAbout />
<IndexExperience />
<IndexFeaturedProjects />
<IndexSelectedProjects />
<IndexStudyGuide />
<IndexContact />
<IndexFooter />
</>
)
}
export default IndexPage
Then in any component I could simply use the following code to access the context
import { useAuth } from "../contexts/context"
const { isLoggedIn, signInWithRedirect, isLoading } = useAuth()
Child components are mounted before parent. Fix your context.js file to add a default value for isLoggedIn state:
const defaultState = {
isLoggedIn: false,
setIsLoggedIn: () => {}
};
const AuthContext = React.createContext(defaultState);
Your defaultState should also include default methods for any parts of the context you wish to work with.

React UseContext latest value

I have a react firebase Application for authentication. I am using Usecontext along with the setState. You can see the code below. This scenario works fine when user logs in for the first time or signs up. But as soon as I reload, the context value is null for a split second and then it gets the latest value from the auth of firebase. This is causing a problem for me to access the private route even though the user is logged in.
AuthContext.js File
import React, { useContext, useEffect, useState } from "react";
import { firebaseAuth } from "../firebase";
import {
createUserWithEmailAndPassword,
signOut,
signInWithEmailAndPassword,
onAuthStateChanged,
} from "firebase/auth";
const AuthContext = React.createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState(null);
console.log("Auth Context User:", user);
useEffect(() => {
const unsub = onAuthStateChanged(firebaseAuth, (user) => {
setUser(user);
});
return unsub();
}, [user]);
function SignUp(email, password) {
return createUserWithEmailAndPassword(firebaseAuth, email, password);
}
function Logout() {
return signOut(firebaseAuth);
}
function Login(email, password) {
return signInWithEmailAndPassword(firebaseAuth, email, password);
}
const value = {
logout: Logout,
signup: SignUp,
login: Login,
user,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
App.js File
import Header from "./components/Header";
import Login from "./components/Login";
import NotFound from "./components/NotFound";
import Signup from "./components/Signup";
import Home from "./components/Home";
import AuthGuard from "./guard/AuthGuard";
import ErrorComp from "./components/ErrorComp";
import { AuthContextProvider } from "./context/AuthContext";
import { GlobalStyles } from "./styledComps/Global";
import { Route, Routes } from "react-router-dom";
function App() {
return (
<>
<GlobalStyles />
<AuthContextProvider>
<Header />
<ErrorComp>
<Routes>
<Route path="" element={<Login />} exact />
<Route path="/signup" element={<Signup />} />
<Route
path="/home"
element={
<AuthGuard>
<Home />
</AuthGuard>
}
/>
<Route path="*" element={<NotFound />} />
</Routes>
</ErrorComp>
</AuthContextProvider>
</>
);
}
export default App;
AuthGuard.js File
import React from "react";
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "../context/AuthContext";
const AuthGuard = ({ children }) => {
let { user } = useAuth();
let location = useLocation();
console.log("user", user);
if (!user) {
return <Navigate to="/" state={{ from: location }} />;
}
return children;
};
export default AuthGuard;
Since the user has to get refetched when you reload (which takes some time), the user object will be null until the process has finished. You could simply try to just guard your rendering by
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState(null);
console.log("Auth Context User:", user);
useEffect(() => {
const unsub = onAuthStateChanged(firebaseAuth, (user) => {
setUser(user);
});
return unsub();
}, [user]);
function SignUp(email, password) {
return createUserWithEmailAndPassword(firebaseAuth, email, password);
}
function Logout() {
return signOut(firebaseAuth);
}
function Login(email, password) {
return signInWithEmailAndPassword(firebaseAuth, email, password);
}
const value = {
logout: Logout,
signup: SignUp,
login: Login,
user,
};
if ( user !== null ) {
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
return <>Loading ... or render eg. a spinner</>
};

lost auth state when page refresh in reactjs hooks

When i login with valid credentials server send JWT to browser i store this JWT in localstore page redirect to home page everything works fine and in home page i have loadUser function which send request to server to get the user details of valid user but when i refresh the page it home page never execute because auth state returns to false and page redirect from home to login page
This is App.js
import React, { Fragment } from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Navbar from "./components/layout/Navbar";
import Home from "./components/pages/Home";
import Login from "./components/auth/Login";
import PrivateRoute from "./components/routing/PrivateRoute";
import "./App.css";
const App = () => {
return (
<AuthState>
<Router>
<Fragment>
<Navbar />
<div className="container">
<Alerts />
<Switch>
<PrivateRoute exact path="/" component={Home} />
<Route exact path="/login" component={Login} />
</Switch>
</div>
</Fragment>
</Router>
</AuthState>
);
};
export default App;
This is my authsate code from where my state changes after successful login
import React, { useReducer } from "react";
import axios from "axios";
import AuthContext from "./authContext";
import authReducer from "./authReducer";
import {
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
} from "../types";
const AuthState = props => {
const initialState = {
isAuthenticated: null,
user: null,
};
const [state, dispatch] = useReducer(authReducer, initialState);
// Load User
const loadUser = async () => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
dispatch({ type: USER_LOADED, payload: res.data });
} catch (err) {
dispatch({ type: AUTH_ERROR });
}
};
// Login User
const login = async formData => {
const config = {
headers: {
"Content-Type": "application/json"
}
};
try {
const res = await axios.post("api/auth", formData, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
});
loadUser();
} catch (err) {
dispatch({
type: LOGIN_FAIL,
payload: err.response.data.msg
});
}
};
return (
<AuthContext.Provider
value={{
isAuthenticated: state.isAuthenticated,
user: state.user,
loadUser,
login,
}}
>
{props.children}
</AuthContext.Provider>
);
};
export default AuthState;
This is reducer code authReducer.js
import {
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
} from "../types";
export default (state, action) => {
switch (action.type) {
case USER_LOADED:
return {
...state,
isAuthenticated: true,
user: action.payload
};
case LOGIN_SUCCESS:
localStorage.setItem("token", action.payload.token);
return {
...state,
isAuthenticated: true
};
case AUTH_ERROR:
case LOGIN_FAIL:
localStorage.removeItem("token");
return {
...state,
isAuthenticated: false,
user: null,
};
default:
return state;
}
};
This is private route
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import AuthContext from "../../context/auth/authContext";
const PrivateRoute = ({ component: Component, ...rest }) => {
const authContext = useContext(AuthContext);
const { isAuthenticated, loading } = authContext;
return (
<Route
{...rest}
render={props =>
!isAuthenticated && !loading ? (
<Redirect to="/login" />
) : (
<Component {...props} />
)
}
/>
);
};
export default PrivateRoute;
That's to be expected.
If you want to persist the token you should save it in localStorage or a cookie.
Then, you should check for the token existence and validity in componentDidMount or in an useEffect (with a void dependency array) if you are using hooks.
It'd be nice if you could show us where you are making AJAX requests because if, for example, you are using Axios you can encapsulate this logic in a request interceptor.
Probably the JSON web token which is used for user authentication/authorization. Can you give us more information about this?

React TypeScript: How to userReducer with two different contexts?

On the Home page, I want to be able to change the language, to 'kr' from the default 'en'. I've made the button click call a function and I then change the context which in turn calls the reducer, is this the right way?
Also when I use the reducer am I mutating the state or not?
How do I apply types to the action in the reducer and the default state?
import React, { useReducer } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { setGlobalContext, globalContext } from "./components/context";
import { Layout } from "./components/layout";
import { Home } from './routes/home';
import { NotFound } from './routes/not-found';
import { globalReducer } from './components/reducer'
const Router: React.FC = () => {
const [global, setGlobal] = useReducer(globalReducer, {
language: 'en',
})
return (
<setGlobalContext.Provider value={{ setGlobal }}>
<globalContext.Provider value={{ global }}>
<BrowserRouter>
<Route render={({ location }) => (
<Layout location={ location }>
<Switch location={ location }>
<Route exact path = '/' component = { Home } />
<Route component = { NotFound }/>
</Switch>
</Layout>
)} />
</BrowserRouter>
</globalContext.Provider>
</setGlobalContext.Provider>
);
}
export { Router };
./components/reducer looks like
const CHANGE_LANGUAGE = 'CHANGE_LANGUAGE';
const globalReducer = (state: any, action: any) => {
switch (action.type) {
case CHANGE_LANGUAGE:
return {...state,
language: action.value
}
default:
return state
}
}
export { globalReducer, CHANGE_LANGUAGE }
and ./components/context
import React from "react";
const setGlobalContext = React.createContext({});
const globalContext = React.createContext({});
export { setGlobalContext, globalContext };
and ./routes/home
import React, { useContext, useEffect, useRef, useState} from 'react';
import { setGlobalContext, globalContext } from "./context";
import { CHANGE_LANGUAGE } from './components/reducer'
const Home: React.FC<propsInterface> = (props) => {
const { global } = useContext(globalContext) as {global: any};
const { setGlobal } = useContext(setGlobalContext) as {setGlobal: React.Dispatch<React.SetStateAction<any>>};
const changeLanguage = () => {
setGlobal({type: CHANGE_LANGUAGE, value: 'kr'})
}
return (
<button onClick={changeLanguage}>Change language to Korean<button>
)
}
export { Home };

Resources