I'm using React and Graphql and Apollo.
My index.js is as follows:
const client = new ApolloClient({
uri: GRAPHQL_URL,
fetchOptions: {
credentials: 'include'
},
request: (operation) => {
const token = localStorage.getItem(AUTH_TOKEN_KEY) || "";
operation.setContext({
headers: {
Authorization: `JWT ${token}`
}
})
},
clientState: {
defaults: {
isLoggedIn: !!localStorage.getItem(AUTH_TOKEN_KEY)
}
}
});
const IS_LOGGED_IN_QUERY = gql`
query {
isLoggedIn #client
}
`;
ReactDOM.render(
<ApolloProvider client={client}>
<Query query={IS_LOGGED_IN_QUERY}>
{({data}) => {
return data.isLoggedIn ? <App/> : <Login/>
}}
</Query>
</ApolloProvider>,
document.getElementById('root')
);
Thus if user is logged in I save token to localStorage and the <App /> is shown instead of <Login />
The <Login /> is as follows:
const Login = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [logIn, { loading: mutationLoading, error: mutationError }] = useMutation(LOG_IN_MUTATION);
const client = useApolloClient();
const handleSubmit = async (e) => {
e.preventDefault();
const res = await logIn({variables: {username, password}});
client.writeData({ data: { isLoggedIn: true } })
localStorage.setItem(AUTH_TOKEN_KEY, res.data.tokenAuth.token);
};
return ( ... )
In de <App /> I do a Graphql query to the backend to get de data of currently logged in user. That stuck code in the backend works as expected.
The <App /> is as follows:
function App() {
const {loading, error, data} = useQuery(GET_ME);
if (loading) return <div><LoadingIndicator1/></div>
if (!loading && error) return <div ><ErrorIndicator/></div>
return (
...
);
}
export default App;
And the GET_ME query is as follows:
export const GET_ME = gql`
{
me{
id
username
firstName
lastName
email
}
}
`;
The function for logging out is as follows:
const client = useApolloClient();
const signOut = (client) => {
localStorage.removeItem(AUTH_TOKEN_KEY);
client.writeData({
data: {
isLoggedIn: false
}
})
};
But the problem is when I log in with one user and the logout, then login with the other user, i still see the old one. If I then refresh I see the new user.
Any idea what I do wrong?
UPDATE
The <App /> component is as follows:
function App() {
const {loading, error, data} = useQuery(GET_ME);
if (loading) return <div><LoadingIndicator1/></div>
if (!loading && error) return <div ><ErrorIndicator/></div>
return (
<Router>
<UserContext.Provider value={data.me}>
<ToastProvider>
<Header/>
<Switch>
<Route path="/report/:id">
<NewReport/>
</Route>
<Route exact path="/report/">
<NewReport/>
</Route>
<Route path="/reports/">
<Reports/>
</Route>
<Route exact path="/">
<Home/>
</Route>
</Switch>
</ToastProvider>
</UserContext.Provider>
</Router>
);
}
And component where I use the user details is as follows:
render (
<div>
{localStorage.getItem(AUTH_TOKEN_KEY) !== null && <div>
<div className=''>
<span>{currentUser.username}</span>
<i className="fad fa-angle-down" />
</div>
</div>}
</div>
)
I do it in main component () and then pass it via React Context to all child components.
I want to do it on one place and pass it down as props.
Using context duplicates already existing apollo client context. You can simply useQuery to get the same (cache-only if you want).
Other hints:
'main' query (<Query query={IS_LOGGED_IN_QUERY}>) should be a FC (with useQuery hook) and combined into <App/> component;
if me is defined then user is logged in (unnecessary state duplication) - you can combine them in one query;
more common router auth/private/proteced routes?
outdated methods (apollo local state, router)
The main problem
... probably caused by cached results of GET_ME query. It's not network-only neither parametrized by some user id variable.
From Apollo docs (auth):
The easiest way to ensure that the UI and store state reflects the current user's permissions is to call client.resetStore() after your login or logout process has completed.
Related
I am having some issues with my routing currently when authenticated. Whenever I try to access my ViewPortfolio page at localhost:3000/portfolio/portfolioId it will redirect me back to my homepage. I am not sure what is going on. I have also tried manipulating the URL by modifying it to the correct URL link but it also redirects me back to /homepage when I am authenticated. The source codes can be found below. App.js is my router with PrivateRoute as the private route component and finally, CreateInterview.js where I redirect using js windows.location function to ViewPortfolio.js which will use useParams() react hook to get the param. But instead now after creating successfully and redirect to the correct URL with the portfolioId it will redirect back to homepage within less than a second.
PrivateRoute.js
import React from 'react'
import { Route, Redirect } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
const { currentUser } = useAuth()
return (
<Route
{...rest}
render={(props) => {
if (currentUser) {
return <Component {...props} />
} else {
return <Redirect to={{
pathname: "/",
state:{
from: props.location
}
}}/>
}
}
}>
</Route>
)
}
export default PrivateRoute
App.js
import React from "react"
.
.
.
import PublicRoute from "./PublicRoute";
function App() {
return (
<AuthProvider>
<Router>
<Switch>
{/* Auth Routes */}
<PublicRoute exact path='/' component={Login} />
.
.
.
<PrivateRoute exact path='/createInterview' component={CreateInterview} />
<PrivateRoute path='/manageInterview' component={ManageInterview} />
<PrivateRoute path='/portfolio/:portfolioId' component={ViewPortfolio} />
{/* Non-Existance Routes */}
<Route path="*" component={() => "404 NOT FOUND"} />
</Switch>
</Router>
</AuthProvider>
)
}
export default App
CreatInterview.js redirecting in js (onSubmit of the form)
async function handleSubmit(e) {
e.preventDefault();
setError('');
setLoading(true);
await database.portfolioRef.add({
intervieweeName: intervieweeNameRef.current.value,
intervieweeEmail: intervieweeEmailRef.current.value,
intervieweeMobileNumber: intervieweeMobileRef.current.value,
projectTitle: projectTitleRef.current.value,
portfolioTitle: portfolioNameRef.current.value,
dateCreated: new Date().toLocaleString('en-SG'),
createdBy: currentUser.displayName
}).then(function(docRef) {
console.log("This is the Document ID " + docRef.id.toString());
console.log(docRef.id);
window.location = '/portfolio/' + docRef.id;
})
setLoading(false)
}
Part of ViewPortfolio.js to receive the portfolioId from CreateInterview.js
const ViewPortfolio = () => {
let { portfolioId } = useParams();
AuthContext.js
import React, { useContext, useState, useEffect } from "react"
import { auth, database } from "../firebase";
import { getDocs, query, where } from "firebase/firestore";
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState(null)
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password).then(() => {
const Doc = query(database.usersRef, where("email", "==", email));
getDocs(Doc).then((querySnapshot) => {
let values = '';
querySnapshot.forEach((doc) => {
values = doc.id;
});
var userUpdate = database.usersRef.doc(values);
userUpdate.update({
lastActive: new Date().toLocaleString('en-SG'),
})
})
});
}
function logout() {
return auth.signOut();
}
function forgetPassword(email) {
return auth.sendPasswordResetEmail(email);
}
function updateEmail(email) {
return currentUser.updateEmail(email)
}
function updatePassword(password) {
return currentUser.updatePassword(password)
}
function updateDisplayName(name) {
return currentUser.updateDisplayName(name)
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged( user => {
setLoading(false)
setCurrentUser(user)
})
return unsubscribe
}, [])
const value = {
currentUser,
login,
forgetPassword,
logout,
updateEmail,
updatePassword,
updateDisplayName,
}
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
)
}
The initial currentUser state matches the unauthenticated state, so when the app initially renders, if you are accessing a protected route the redirection will occur because the currentUser state hasn't updated yet.
Since onAuthStateChanged returns null for unauthenticated users then I suggest using anything other than null for the initial currentUser state. undefined is a good indeterminant value. You can use this indeterminant value to conditionally render a loading indicator, or nothing at all, while the auth status is confirmed on the initial render.
AuthProvider
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState(); // <-- undefined
...
PrivateRoute
const PrivateRoute = (props) => {
const { currentUser } = useAuth();
if (currentUser === undefined) {
return null; // or loading spinner, etc...
}
return currentUser
? (
<Route {...props} />
)
: (
<Redirect
to={{
pathname: "/",
state: {
from: props.location
}
}}
/>
);
}
You should also really replace the window.location = '/portfolio/' + docRef.id; logic with a history.push('/portfolio/' + docRef.id); so you are not unnecessarily reloading the page.
const history = useHistory();
...
async function handleSubmit(e) {
e.preventDefault();
setError('');
setLoading(true);
try {
const docRef = await database.portfolioRef.add({
intervieweeName: intervieweeNameRef.current.value,
intervieweeEmail: intervieweeEmailRef.current.value,
intervieweeMobileNumber: intervieweeMobileRef.current.value,
projectTitle: projectTitleRef.current.value,
portfolioTitle: portfolioNameRef.current.value,
dateCreated: new Date().toLocaleString('en-SG'),
createdBy: currentUser.displayName
});
history.push('/portfolio/' + docRef.id);
} catch (error) {
// handle error, clear loading state
setLoading(false);
}
}
I am having some issues with my routing currently when authenticated. Whenever I try to access my ViewPortfolio page at localhost:3000/portfolio/portfolioId it will redirect me back to my homepage. I am not sure what is going on. I have also tried manipulating the URL by modifying it to the correct URL link but it also redirects me back to /homepage when I am authenticated. The source codes can be found below. App.js is my router with PrivateRoute as the private route component and finally, CreateInterview.js where I redirect using js windows.location function to ViewPortfolio.js which will use useParams() react hook to get the param. But instead now after creating successfully and redirect to the correct URL with the portfolioId it will redirect back to homepage within less than a second.
PrivateRoute.js
import React from 'react'
import { Route, Redirect } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
const PrivateRoute = ({ component: Component, ...rest }) => {
const { currentUser } = useAuth()
return (
<Route
{...rest}
render={(props) => {
if (currentUser) {
return <Component {...props} />
} else {
return <Redirect to={{
pathname: "/",
state:{
from: props.location
}
}}/>
}
}
}>
</Route>
)
}
export default PrivateRoute
App.js
import React from "react"
.
.
.
import PublicRoute from "./PublicRoute";
function App() {
return (
<AuthProvider>
<Router>
<Switch>
{/* Auth Routes */}
<PublicRoute exact path='/' component={Login} />
.
.
.
<PrivateRoute exact path='/createInterview' component={CreateInterview} />
<PrivateRoute path='/manageInterview' component={ManageInterview} />
<PrivateRoute path='/portfolio/:portfolioId' component={ViewPortfolio} />
{/* Non-Existance Routes */}
<Route path="*" component={() => "404 NOT FOUND"} />
</Switch>
</Router>
</AuthProvider>
)
}
export default App
CreatInterview.js redirecting in js (onSubmit of the form)
async function handleSubmit(e) {
e.preventDefault();
setError('');
setLoading(true);
await database.portfolioRef.add({
intervieweeName: intervieweeNameRef.current.value,
intervieweeEmail: intervieweeEmailRef.current.value,
intervieweeMobileNumber: intervieweeMobileRef.current.value,
projectTitle: projectTitleRef.current.value,
portfolioTitle: portfolioNameRef.current.value,
dateCreated: new Date().toLocaleString('en-SG'),
createdBy: currentUser.displayName
}).then(function(docRef) {
console.log("This is the Document ID " + docRef.id.toString());
console.log(docRef.id);
window.location = '/portfolio/' + docRef.id;
})
setLoading(false)
}
Part of ViewPortfolio.js to receive the portfolioId from CreateInterview.js
const ViewPortfolio = () => {
let { portfolioId } = useParams();
AuthContext.js
import React, { useContext, useState, useEffect } from "react"
import { auth, database } from "../firebase";
import { getDocs, query, where } from "firebase/firestore";
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState(null)
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password).then(() => {
const Doc = query(database.usersRef, where("email", "==", email));
getDocs(Doc).then((querySnapshot) => {
let values = '';
querySnapshot.forEach((doc) => {
values = doc.id;
});
var userUpdate = database.usersRef.doc(values);
userUpdate.update({
lastActive: new Date().toLocaleString('en-SG'),
})
})
});
}
function logout() {
return auth.signOut();
}
function forgetPassword(email) {
return auth.sendPasswordResetEmail(email);
}
function updateEmail(email) {
return currentUser.updateEmail(email)
}
function updatePassword(password) {
return currentUser.updatePassword(password)
}
function updateDisplayName(name) {
return currentUser.updateDisplayName(name)
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged( user => {
setLoading(false)
setCurrentUser(user)
})
return unsubscribe
}, [])
const value = {
currentUser,
login,
forgetPassword,
logout,
updateEmail,
updatePassword,
updateDisplayName,
}
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
)
}
The initial currentUser state matches the unauthenticated state, so when the app initially renders, if you are accessing a protected route the redirection will occur because the currentUser state hasn't updated yet.
Since onAuthStateChanged returns null for unauthenticated users then I suggest using anything other than null for the initial currentUser state. undefined is a good indeterminant value. You can use this indeterminant value to conditionally render a loading indicator, or nothing at all, while the auth status is confirmed on the initial render.
AuthProvider
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState(); // <-- undefined
...
PrivateRoute
const PrivateRoute = (props) => {
const { currentUser } = useAuth();
if (currentUser === undefined) {
return null; // or loading spinner, etc...
}
return currentUser
? (
<Route {...props} />
)
: (
<Redirect
to={{
pathname: "/",
state: {
from: props.location
}
}}
/>
);
}
You should also really replace the window.location = '/portfolio/' + docRef.id; logic with a history.push('/portfolio/' + docRef.id); so you are not unnecessarily reloading the page.
const history = useHistory();
...
async function handleSubmit(e) {
e.preventDefault();
setError('');
setLoading(true);
try {
const docRef = await database.portfolioRef.add({
intervieweeName: intervieweeNameRef.current.value,
intervieweeEmail: intervieweeEmailRef.current.value,
intervieweeMobileNumber: intervieweeMobileRef.current.value,
projectTitle: projectTitleRef.current.value,
portfolioTitle: portfolioNameRef.current.value,
dateCreated: new Date().toLocaleString('en-SG'),
createdBy: currentUser.displayName
});
history.push('/portfolio/' + docRef.id);
} catch (error) {
// handle error, clear loading state
setLoading(false);
}
}
I'm trying to get some auth experience and I've got React with React Router, I found a custom auth check for routes that I thought looked good, and tried to implement. Basically it would sign the user in, change the auth value to true and be able to call on that auth value from the hook to check.
Here's my codesandbox, my problem is I have to use AWS Cognito for this project so the sign in call has to be from an async function and not through a promise...
Clicking 'sign in check' calls handleLogin and starts the async function in the useAuthHook with signIn, which sets authed to true via an effect hook. That change is reflected by using the 'auth check' button, but when trying to navigate to a protected route the console logs the default values.
Here's the steps;
...
<Button variant="primary" onClick={handleLogin} type="button">
sign in check
</Button>
...
const handleLogin = () => {
signIn();
};
Now the signIn hook from useAuthHook;
async signIn() {
const user = "totally real"; //AWS await request
testValue = "a diff string";
if (user) {
setUser(user);
console.log(authed, user);
return "/storageSolution";
}
}
the effect hook that updates a ref hook and a useState hook(testing both cases);
React.useEffect(() => {
if (user) {
authed.current = true;
setStateAuth(true);
}
}, [user]);
both stateAuth and the authed ref are returned along with the earlier signIn, and used in RequireAuth before my routes;
export function RequireAuth({ children }) {
const location = useLocation();
const { authed, user, stateAuth } = useAuth();
console.log(authed, user, stateAuth);
return authed === true ? (
children
) : (
<Navigate to="/" replace state={{ path: location.pathname }} />
);
}
but clicking protected route check after signing in shows default values in the console, whereas clicking auth check shows the updates values.
I found https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function this on stale state, but neither of the reasons it gives seem to be the problem. I've got several different ways of updating and reading that value, but none work. What am I missing?
React hooks don't share state. Move all the state and logic from the useAuth hook into the AuthProvider component. After this is done the useAuth hook simply returns the current authContext value.
Example:
const authContext = React.createContext();
export function useAuth() {
return React.useContext(authContext);
}
export function AuthProvider({ children }) {
const authed = React.useRef(false);
const [user, setUser] = React.useState();
const [stateAuth, setStateAuth] = React.useState(false);
let testValue = "some string";
React.useEffect(() => {
if (user) {
authed.current = true;
setStateAuth(true);
}
}, [user]);
return (
<authContext.Provider
value={{
authed,
user,
testValue,
stateAuth,
async signIn() {
const user = "totally real"; // where the await auth req would be
testValue = "a diff string";
if (user) {
setUser(user);
console.log(authed, user);
return "/storageSolution";
}
}
}}
>
{children}
</authContext.Provider>
);
}
index.js
Import and wrap the app with the AuthProvider component.
...
import { RequireAuth, AuthProvider } from "./useAuthHook";
...
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={<LoginSignup />} />
<Route
path="/storageSolution"
element={
<RequireAuth>
<StorageSolution />
</RequireAuth>
}
/>
<Route path="/secret" element={<SecretRoute />} />
</Routes>
</BrowserRouter>
</AuthProvider>
</React.StrictMode>,
rootElement
);
I am using Protected Route for my dashboard and I only want people who signed in would be able to see the dashboard. So, in my Login component, I am fetching the data and this is the place where I check if the email and password of the user is right. So what I want to do is send a boolean variable to parent element which is index.js and according to the value I want to show the dashboard to the user.
So here is my index.js:
const hist = createBrowserHistory();
console.log(hist)
ReactDOM.render(
<Router history={hist}>
<Switch>
<Route path="/" component={Login} exact />
<ProtectedRoute path="/admin" component={(props) => <AdminLayout {...props} />} isAuth={true}/>
<Route path="" component={() => "404 NOT FOUND"} />
</Switch>
</Router>,
document.getElementById("root")
);
So instead of the true inside, isAuth={true}, I want to send a variable to check.
Also my Login component:
const Login = ({ submitForm }) => {
const [isSubmitted, setIsSubmitted] = useState(false);
function submitForm() {
setIsSubmitted(true);
}
const { handleChange, values, handleSubmit, errors } = useForm(
submitForm,
validate
);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [login, setLogin] = useState(false);
const fetchLogin = async (email, password) => {
try {
setLoading(true);
const res = await Axios({
data: {
user_email: email,
user_password: password,
},
});
if (res.status === 200) {
setLogin(true);
localStorage.setItem("user-info", JSON.stringify(res));
}
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
};
function loginButton() {
fetchLogin(values.email, values.password);
}
return (
<form>
</form>
);
};
export default Login;
So thanks for your helps...
There are different ways.
You can read retrieve user-info from localStroage in your ProtectedRoute. In this case Login would not need to do anything more than localStorage.setItem("user-info", JSON.stringify(res))
You can propagate the login-state up to the parent component using a callback function. See this question for an example.
If you want to access the login state also from other components you could consider to use a React Context. Here is an example.
I'm using code from a tutorial, which uses createContext and I'm kind of confused on what exactly it's doing, and I believe that it's causing errors where I wouldn't necessarily expect. I have two components, Dashboard and Login which are different pages of my web app. It generates the error: Unhandled Rejection (TypeError): Cannot read property 'data' of undefined For some reason, the following line in Dashboard.js:
function Dashboard() {
const [favPokemons, setFavPokemons] = useState([]);
const { userData, setUserData } = useContext(UserContext);
setFavPokemons(userData.user.favPokemon); // This line is the problematic line
}
causes an error in Login.js in its try catch clause:
import UserContext from "../../context/userContext";
import ErrorNotice from "../misc/ErrorNotice";
function Login () {
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [error, setError] = useState();
const { setUserData } = useContext(UserContext);
const history = useHistory();
const submit = async (e) => {
e.preventDefault();
try{
const loginUser = {email, password};
const loginResponse = await axios.post("https://minipokedexbackend.herokuapp.com/users/login", loginUser);
console.log(userData); // line Login.js:21 is in image below line 22
console.log(loginResponse) // line Login.js:22, log is in image below
setUserData({
token: loginResponse.data.token,
user: loginResponse.data.user
});
localStorage.setItem("auth-token", loginResponse.data.token);
history.push("/dashboard");
} catch(err) {
err.response.data.msg && setError(err.response.data.msg)
}
};
Could someone explain what createContext and why it would be causing an error in two seemingly unrelated components? I have a feeling that it has to do with userData not quite being generated when Dashboard is rendered?
EDIT:
Sorry for the lack of information, data referenced in the Login.js file is data from my server accessing mongoDB. Its response contains token and user info, which includes their id, displayname and an array of favpokemon
Here's userContext.js:
import { createContext } from 'react';
export default createContext(null);
Here's App.js:
function App() {
const [ userData, setUserData] = useState({
token: undefined,
user: undefined
});
return (
<BrowserRouter>
<UserContext.Provider value={{ userData, setUserData }}>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/register" component={Register} />
<Route path="/login" component={Login} />
<Route path='/dashboard' component={Dashboard}/>
</Switch>
</UserContext.Provider>
</BrowserRouter>
);
}
App.js also contains some functions to check if the user is logged in.
With the info you provided in the question, I managed to create a structure of your code. There might be logic or syntax errors because of the lack of information, but I want you to have a general idea how to use Context Hook during login.
UserContext.tsx
import React, { createContext, useState } from "react";
//create the context
export const UserContext = createContext<any>(undefined);
//create the context provider, we are using useState to ensure that we get reactive values from the context
export const UserProvider: React.FC = ({ children }) => {
//the reactive values
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [userData, setUserData] =setUserData({
token: '',
user: ''
});
//the store object
let state = {
email,
setEmail,
password,
setPassword,
userData.
setUserData
};
//wrap the application in the provider with the initialized context
return <UserContext.Provider value={state}>{children}</UserContext.Provider>;
};
export default UserContext;
Login.tsx
import UserContext from "../../context/userContext";
import ErrorNotice from "../misc/ErrorNotice";
function Login () {
const [error, setError] = useState();
const { email, setEmail, password, setPassword, userData, setUserData } = useContext(UserContext);
const history = useHistory();
const submit = async (e) => {
e.preventDefault();
try{
const loginUser = {email, password};
const loginResponse = await axios.post("https://minipokedexbackend.herokuapp.com/users/login", loginUser);
console.log(userData); // line Login.js:21 is in image below line 22
console.log(loginResponse) // line Login.js:22, log is in image below
setUserData({
token: loginResponse.data.token,
user: loginResponse.data.user
});
localStorage.setItem("auth-token", loginResponse.data.token);
history.push("/dashboard");
} catch(err) {
err.response.data.msg && setError(err.response.data.msg)
}
};
export default Login;
Dashboard.tsx
import UserContext from "../../context/userContext";
import ErrorNotice from "../misc/ErrorNotice";
import React, { useState, useEffect } from "react";
const Dashboard: React.FC = () => {
const [favPokemons, setFavPokemons] = useState([]);
const { userData, setUserData } = useContext(UserContext);
useEffect(() => {
setFavPokemons(userData?.user?.favPokemon);
}, []);
}
}
export default Dashboard;
App.tsx
Here I'm using a ternary expression. If (userData.token), got to dashboard otherwise go to Login Page.
function App() {
const { userData} = useContext(UserContext);
return (
<UserProvider>
<BrowserRouter>
<Header />
<Switch>
{!userData?.token ? (
<>
<Route path="/register" component={Register} />
<Route path="/login" component={Login} />
<Redirect exact from="/" to="/login" />
</>
) : (
<>
<Route exact path="/" component={Home} />
<Route path='/dashboard' component={Dashboard}/>
<Redirect exact from="/" to="/dashboard" />
</>
)
</Switch>
</BrowserRouter>
</UserProvider>
);
}
Try to make a habit of using null checks when you’re accessing nested values of a response.
setFavPokemons(userData.user.favPokemon);
Here, modify this line to:
setFavPokemons(userData?.user?.favPokemon);
Also, do you mind doing a console log on userData object or share the corresponding reducer to check whether the shape of the data is same or not?