React state inside React-context is not updating - javascript

Hello guys so I have the following problem:
I have a login form after user successfully provide the right info for signing in, I store the user object and access token in the AuthContext
I protect the home route using the context
Problem => the react state inside the context is not being updated
-[Edit] Solution => I found the solution and it was only changing the following code:
const {setAuth} = useAuth();
to the code:
const {setAuth} = useAuth({});
-[Edit 2] => Because I am a beginner I also discovered that navigation between components with anchor tag () or window location cause the lose of state data so I should use Link from react router dom to avoid re-rendering
AuthProvider.js
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
App.js
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
{/* public routes */}
<Route path="auth" element={<AuthLayout />}>
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
<Route path="unauthorized" element={<Unauthorized />} />
</Route>
{/* we want to protect the following routes */}
{/* RequireAuth having outlet => return child only if context auth has user object */}
<Route element={<RequireAuth />}>
<Route path="home" element={<Home />} />
</Route>
{/* catch all */}
</Route>
</Routes>
);
}
export default App;
RequireAuth.js [ Problem is here, the auth is always empty]
const RequireAuth = () => {
const { auth } = useAuth();
const location = useLocation();
return auth?.userObject ? (
// we used from location and replace so we want to save the previous location of the visited page by user so he is able to go back to it
<Outlet />
) : (
<Navigate to="/auth/login" state={{ from: location }} replace />
);
};
export default RequireAuth;
Login.js [Here I update the state]
const handleFormSubmission = async (data,e) => {
e.preventDefault();
try {
const response = await axios.post(
ApiConstants.LOGIN_ENDPOINT,
JSON.stringify({
Email: email,
Password: password,
}),
{
headers: ApiConstants.CONTENT_TYPE_POST_REQUEST,
}
);
//const roles = ;
const userObject = response?.data;
const accessToken = response?.data?.token;
setAuth({ userObject, password, accessToken });
console.log(userObject);
console.log(accessToken);
console.log(password);
message.success("You are successfully logged in");
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./context/AuthProvider";
import "./styles/ant-design/antd.css";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/*" element={<App />} />
</Routes>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
);

Did you import setAuth correctly?
Did you call setAuth({}) inside handleFormSubmission function?

I have gone through the code and there are few suggestions which will make code work.
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
function updateAuth(authVal) {
console.log("update auth called with value", authVal);
setAuth(authVal);
}
return (
<AuthContext.Provider value={{ auth, updateAuth }}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
Also we need few changes in the consumer code which looks like below
const { updateAuth } = useContext(AuthContext);
const handleFormSubmission = async (e) => {
e.preventDefault();
try {
const response = await axios.get("https://catfact.ninja/breeds?
limit=1");
//const roles = ;
const userObject = response?.data;
console.log("cats api data response is ", userObject);
updateAuth(userObject);
//setAuth({ userObject, password, accessToken });
} catch (err) {
console.error("some error");
}
};
I have made sample application on codesandbox for the same as well. Please find url as https://codesandbox.io/s/funny-scott-n7tmxs?file=/src/App.js:203-689
I hope this help and have a wonderful day :)

Related

Why does Typescript in React not recognize the currentUser value from an exported Context with built in provider?

I'm attempting to have the project I'm working on update the user according with the onAuthStateChanged function from Firebase which is quite handy. So by following a tutorial I created a context with a built in provider which I wrapped my index file in:
import { AuthContextProvider } from './context/AuthContext';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<AuthContextProvider> // The wrapper
<React.StrictMode>
<App />
</React.StrictMode>
</AuthContextProvider>
);
This is my created context for user authentication:
import { createContext, useEffect, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from '../firebase.config';
export const UserContext = createContext({});
export const AuthContextProvider = ({ children }: any) => {
const [currentUser, setCurrentUser] = useState<any | null>({});
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
console.log(user);
});
return () => {
unsub();
};
}, []);
return (
<UserContext.Provider value={{ currentUser }}>
{children}
</UserContext.Provider>
);
};
And finally my actual app file where I get the error:
TS2339: Property 'currentUser' does not exist on type '{}'.
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); //Error on this line
const saveElem = (
<div className={styles.wrapper}>
<LeftBar />
<ChatBody />
<RightBar />
<UserControlsContainer />
<ChatInput />
</div>
);
return (
<BrowserRouter>
<Routes>
<Route path='/'>
<Route index element={<ChatRooms />} />
<Route path='login' element={<LogInForm />} />
<Route path='signup' element={<SignUpForm />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;

React.createElement: type is invalid -- expected a string (for built-in components). The webpage is blank when ContextProvider is used in Index.js

I am working on a react website, where I am trying to use AuthTokens to create persistent login. The moment I wrap my AuthCotextProvider in Index.js I get the following error and the webpage is just blank.
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Here are my Code Snippets of App.js and Index.js
Index.js
import React from "react";
import ReactDOM from 'react-dom/client';
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
import { AuthContextProvider} from '../src/store/auth-context.js';
import { createRoot } from 'react-dom/client';
// const root = ReactDOM.createRoot(document.getElementById('root'));
// const container = document.getElementById('root');
// // Create a root.
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<AuthContextProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</AuthContextProvider>
);
App.js
import React,{useContext} from "react";
import { useSelector } from "react-redux";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import {Redirect} from "react-router-dom";
import Layout from "./components/Layout.js";
import Navigation from "./components/navigation.jsx";
import Reports from "./components/Reports";
import Login from "./components/Login";
import Signup from "./components/Signup.jsx";
import Home from "./components/Home";
import Dashboard from "./components/Dashboard";
import Forms from "./components/Forms";
import FormsProceed from "./components/FormsProceed";
import AuthContext from "./store/auth-context.js";
export default function App() {
const authCtx = useContext(AuthContext);
return (
<Layout>
<Switch>
<Route path='/' exact>
<Home />
</Route>
{!authCtx.isLoggedIn && (
<Route path='/login'>
<Login />
</Route>
)}
{!authCtx.isLoggedIn && (
<Route path='/signup'>
<Signup />
</Route>
)}
{!authCtx.isLoggedIn && (
<Route path='/signup'>
<Signup />
</Route>
)}
{!authCtx.isLoggedIn && (
<Route path='/dashboard'>
<Dashboard />
</Route>
)}
{!authCtx.isLoggedIn && (
<Route path='/forms'>
<Forms />
</Route>
)}
{!authCtx.isLoggedIn && (
<Route path='/formproceed'>
<FormsProceed />
</Route>
)}
{!authCtx.isLoggedIn && (
<Route path='/reports'>
<Reports />
</Route>
)}
<Route path='*'>
<Redirect to='/' />
</Route>
</Switch>
</Layout>
);
I am also attaching the inspect element error image for better understanding.
enter image description here
Added the code snipper for auth-context:
import React, { useState, useEffect, useCallback } from 'react';
let logoutTimer;
const AuthContext = React.createContext({
token: '',
isLoggedIn: false,
login: (token) => {},
logout: () => {},
});
// const calculateRemainingTime = (expirationTime) => {
// const currentTime = new Date().getTime();
// const adjExpirationTime = new Date(expirationTime).getTime();
// const remainingDuration = adjExpirationTime - currentTime;
// return remainingDuration;
// };
const retrieveStoredToken = () => {
const storedToken = localStorage.getItem('token');
// const storedExpirationDate = localStorage.getItem('expirationTime');
// const remainingTime = calculateRemainingTime(storedExpirationDate);
// if (remainingTime <= 3600) {
// localStorage.removeItem('token');
// localStorage.removeItem('expirationTime');
// return null;
// }
return {
token: storedToken,
// duration: remainingTime,
};
};
export const AuthContextProvider = (props) => {
const tokenData = retrieveStoredToken();
let initialToken;
if (tokenData) {
initialToken = tokenData.token;
}
const [token, setToken] = useState(initialToken);
const userIsLoggedIn = !!token;
const logoutHandler = useCallback(() => {
setToken(null);
localStorage.removeItem('token');
// localStorage.removeItem('expirationTime');
if (logoutTimer) {
clearTimeout(logoutTimer);
}
}, []);
const loginHandler = (token, expirationTime) => {
setToken(token);
localStorage.setItem('token', token);
// localStorage.setItem('expirationTime', expirationTime);
// const remainingTime = calculateRemainingTime(expirationTime);
// logoutTimer = setTimeout(logoutHandler, remainingTime);
};
useEffect(() => {
if (tokenData) {
console.log(tokenData.duration);
logoutTimer = setTimeout(logoutHandler, tokenData.duration);
}
}, [tokenData, logoutHandler]);
const contextValue = {
token: token,
isLoggedIn: userIsLoggedIn,
login: loginHandler,
logout: logoutHandler,
};
return (
<AuthContext.Provider value={contextValue}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
Can someone Suggest how to solve the error and get the website running?
Looking at this we can't figure out if there is something wrong in your provider component or not. But one thing I suspect is that you might be using new v6 of react-router-dom (https://reactrouter.com/docs/en/v6/getting-started/overview) package and if yes then your code needs to be re-structured as per new API of react-router-dom and that might solve the problem.

How can I redirect a user to login when a certain link is clicked using JWT

I am trying to make it so that when a user clicks on a certain Link (Upload Component), it will redirect them to to the login component and am not sure how to accomplish this task in React. I was directed to another answered question, but it hasn't helped me as I am still confused on what I need to do with my own set up. I understand I need to make my own protected route (maybe), but I saw others accessing useContext and I do not have any file with context. I am using Version 6 in react dom. I am also using React router and redux in my project so I know I need to access the state somehow, just not sure how to wrap my mind around it so if anyone could help, I would appreciate it. The user is being stored in local storage with JWT authentication.
App.js:
import React, {useState, useEffect, useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'; //Switch was replaced by Routes in react-router-dom v6
import './App.css';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Home from './components/pages/Home';
import Uploads from './components/pages/Uploads';
import Account from './components/pages/Account';
import Login from './components/pages/Login';
import LogOut from './components/Logout';
import Register from './components/pages/Register';
import Profile from './components/pages/Profile';
import EditProfile from './components/pages/EditProfile';
import BoardAdmin from './components/pages/BoardAdmin';
import BoardModerator from './components/pages/BoardModerator';
import BoardUser from './components/pages/BoardUser';
import {logout} from './slices/auth';
import EventBus from './common/EventBus';
const App = () => {
const [showModeratorBoard, setShowModeratorBoard] = useState(false);
const [showAdminBoard, setShowAdminBoard] = useState(false);
const {user: currentUser} = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
if (currentUser) {
setShowModeratorBoard(currentUser.roles.includes('ROLE_MODERATOR'));
setShowAdminBoard(currentUser.roles.includes('ROLE_ADMIN'));
} else {
setShowModeratorBoard(false);
setShowAdminBoard(false);
}
}, [currentUser]);
return (
<>
<Router>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/create/upload' element={<Uploads />} />
<Route path='/my-account' element={<Account />} />
<Route path='/login' element={<Login />} />
<Route path='/logout' element={<LogOut />} />
<Route path='/register' element={<Register />} />
<Route path='/profile' element={<Profile />} />
<Route path='/profile/edit' element={<EditProfile />} />
<Route path='/user' element={<BoardUser />} />
<Route path='/mod' element={<BoardModerator />} />
<Route path='/admin' element={<BoardAdmin />} />
</Routes>
<Footer />
</Router>
</>
);
};
export default App;
Auth.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {setMessage} from './messages';
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
export const register = createAsyncThunk(
'auth/register',
async ({username, email, password}, thunkAPI) => {
try {
const response = await AuthService.register(username, email, password);
thunkAPI.dispatch(setMessage(response.data.message));
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const login = createAsyncThunk(
'auth/login',
async ({username, password}, thunkAPI) => {
try {
const data = await AuthService.login(username, password);
return {user: data};
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await AuthService.logout();
});
const initialState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user: null};
const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[register.fulfilled]: (state, action) => {
state.isLoggedIn = false;
},
[register.rejected]: (state, action) => {
state.isLoggedIn = false;
},
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
},
});
const {reducer} = authSlice;
export default reducer;
It looks like adding the if statement in my upload file is making it so that a logged in user can access the h1 element that is in the file, but not an unauthenticated user. So its working to an extent, just not redirecting back to login.
Upload.jsx:
import React from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
function Uploads() {
const {user: currentUser} = useSelector((state) => state.auth);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Rendering the Link doesn't imperatively navigate, use the Navigation component.
Example:
import React, { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
function Uploads() {
const { user: currentUser } = useSelector((state) => state.auth);
if (!currentUser) {
return <Navigate to='/login' replace />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Typically you would protect the route instead. If you wanted to do that:
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoutes = () => {
const { user: currentUser } = useSelector((state) => state.auth);
return currentUser
? <Outlet />
: <Navigate to="/login" replace />;
}
...
<Routes>
<Route path='/' element={<Home />} />
<Route element={<PrivateRoutes />}>
<Route path='/create/upload' element={<Uploads />} />
... any other protected routes ...
</Route>
... other unprotected routes ...
</Routes>

TypeScript AuthContext cant take undefined when on Routes

I'm using TypeScript and I need to create a route with the user info and sign with google from firebase (I plan to switch to Redux as an exercise later).
Happens that when typing the user, if its logged in = User, if its not = undefined, but Router won't take an undefined as a child...
import { createContext, useState } from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { auth, firebase } from "./services/firebase";
import { Home } from "./pages/Home";
import { NewRoom } from "./pages/NewRoom";
type User = {
id: string;
name: string;
avatar: string;
};
type AuthContextType = {
user: User | undefined;
signInWithGoogle: () => Promise<void>;
};
export const AuthContext = createContext({} as AuthContextType);
function App() {
const [user, setUser] = useState<User>();
async function signInWithGoogle() {
// provider is whos giving the log in - google
const provider = new firebase.auth.GoogleAuthProvider();
const result = await auth.signInWithPopup(provider);
if (result.user) {
// username, photoLocation, uniqueID
const { displayName, photoURL, uid } = result.user;
// what if theres no data?
if (!displayName || !photoURL)
throw new Error("Missing information from Google Account");
// set user information
setUser({
id: uid,
name: displayName,
avatar: photoURL,
});
}
}
return (
<BrowserRouter>
<Routes>
<AuthContext.Provider value={{ user, signInWithGoogle }}>
<Route path="/" element={<Home />} />
<Route path="/room/new" element={<NewRoom />} />
</AuthContext.Provider>
</Routes>
</BrowserRouter>
);
}
export default App;
Whenever you want to render nothing, use null instead of undefined. If User is not not logged in, you can redirect to an unauthorized page using <Navigate to="/signin" /> which is a valid child for router (just a suggestion).
Sorry, it was late and past my bed time, it was a simple fix:
before:
`return (
<Routes>
<AuthContext.Provider value={{ user, signInWithGoogle }}>
<Route path="/" element={<Home />} />
<Route path="/room/new" element={<NewRoom />} />
</AuthContext.Provider>
</Routes>
</BrowserRouter>
after:
``
return (
<BrowserRouter>
<AuthContext.Provider value={{ user, signInWithGoogle }}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/room/new" element={<NewRoom />} />
</Routes>
</AuthContext.Provider>
</BrowserRouter>
);
``
Just move AuthContext out of Routes as it is not a Route, obviously

Issue with history.push

I'm working on react Disney+ clone etc and I was trying to do something like: if user isnt authorized then show login page but if authorized then show content. I used useHistory for this. And it works for a second, it just starts to download login page (background image is loading, but text of login page is visible) and then it disappears and content page is shown. Url changes for a second too.
App.js
function App() {
return (
<div className="App">
<Router>
<Header />
<Switch>
<Route path="/login">
<Login/>
</Route>
<Route path="/detail/:id">
<Detail/>
</Route>
<Route path="/">
<Home/>
</Route>
</Switch>
</Router>
</div>
);
}
Header.js
import React from 'react'
import {selectUserName, selectUserPhoto, setUserLogin, setUserSignOut} from '../../features/user/userSlice' ;
import { useDispatch, useSelector } from "react-redux" ;
import { auth, provider} from "../../firebase"
import { useHistory} from 'react-router-dom';
const Header = () => {
const dispatch = useDispatch()
const userName = useSelector(selectUserName);
const userPhoto = useSelector(selectUserPhoto);
const history = useHistory();
const signIn = () => {
auth.signInWithPopup(provider)
.then((result) => {
let user = result.user;
dispatch(setUserLogin({
name: user.displayName,
email: user.email,
photo: user.photoURL
}))
history.push('/');
})
}
const signOut = () => {
auth.signOut()
.then(() => {
dispatch(setUserSignOut());
history.push('/login');
})
}
}
Based on your issue you can handle Routes in a different way. So you have routes which can only shown during unauthorised situation and some routes only shown for authorised user. For that you can have following implementation.
First you can create ProtectedRoute function.
import React from "react";
import { Redirect, Route } from "react-router-dom";
function ProtectedRoute({ component: Component, ...restOfProps }) {
const isAuthenticated = localStorage.getItem("isAuthenticated");
console.log("this", isAuthenticated);
return (
<Route
{...restOfProps}
render={(props) =>
isAuthenticated ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
export default ProtectedRoute;
And then you can use this function in your main App where you will declare your routes with component.
import ProtectedRoute from "./component/ProtectedRoute";
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path="/login" component={Login} />
<ProtectedRoute path="/protected" component={ProtectedComponent} />
</BrowserRouter>
</div>
);
}
export default App;

Categories

Resources