Iam trying to get the string from localstorage and check that with some condition and redirect the page based on the value from localstorage
but the problem is, i could see the page for couple of seconds even before it redirects to origin page
basically its authentication kind of thing
here is the code
import React from "react";
import AdminDashboard from "../components/AdminDashboard";
import Router from "next/router";
import SignIn from "../pages/index";
import useRouter from "next/router";
import fetch from "isomorphic-unfetch";
var userType;
export default function Test({ token }) {
const [userType, setuserType] = React.useState(false);
React.useLayoutEffect(() => {
if (userType !== true) {
const lt = localStorage.getItem("userType");
if (lt !== true) Router.push("/");
}
return () => {};
}, []);
{
console.log(token);
}
return (
<div>
<AdminDashboard>Admin Page </AdminDashboard>
</div>
);
}
You can render the page in the client only to avoid page flashing.
On the other hands, You can just using useEffect.
const Page = null;
React.useEffect(() => {
if (userType !== true) {
const lt = localStorage.getItem("userType");
if (lt !== true) Router.push("/");
} else {
Page = (
<div>
<AdminDashboard>Admin Page </AdminDashboard>
</div>
)
}
}, [])
return Page
Related
I have a problem regarding my withAuth HOC in my Next JS project. This should let users access some routes only if they are logged in.
The problem is that it needs like 1 or 2 seconds to process the request and in this time the private route is rendered, then it is replaced with the desired one (the one for sign in).
import { useRouter } from "next/router";
import { useEffect } from "react";
import { LaLoader } from "../components/LaLoader";
import { useMeQuery } from "../generated/graphql";
export const withAuth = (Component) => {
const AuthenticatedComponent = () => {
const router = useRouter();
const { data, loading } = useMeQuery()
useEffect(() => {
const getUser = async () => {
if (!data?.me) {
router.push('/signin');
}
if (!data?.me.isCompleted) {
router.push('/complete-profile')
}
};
getUser();
});
if (loading) {
return <LaLoader />
}
return data?.me ? <Component data={data} /> : null;
};
return AuthenticatedComponent;
};
The behaviour I want is: if the request is still processing (loading), on the page will be rendered a loader; if the user isn't logged in he will be redirected to the sign in page and if he's signed in the private component will be displayed.
Thank you in advance for your help!
I don't think the getUser function needs to be declared async since it doesn't appear to call any asynchronous code nor await anything. With the synchronous code I think you just need to wait for the loading state to clear and do the same check to redirect the user.
Example:
export const withAuth = (Component) => (props) => {
const router = useRouter();
const { data, loading } = useMeQuery();
useEffect(() => {
if (!loading) {
if (!data?.me) {
router.push("/signin");
} else if (!data?.me?.isCompleted) {
router.push("/complete-profile");
}
}
});
if (loading) {
return <LaLoader />;
}
return data?.me ? <Component {...props} data={data} /> : null;
};
I'm trying to produce a minimal example of routing to login if no session is found. Here is my code from _app.js inside pages folder :
function MyApp({ Component, pageProps }) {
const [user, setUser] = useState(null)
const router = useRouter()
useEffect(() => {
const session = document.cookie.includes("session_active=true")
if (session) {
fetch("/api/user")
.then(u => u.json().then(setUser))
} else {
const redirectURI = router.pathname
const url = {pathname: "/login", query: {"redirect_uri": redirectURI}}
router.push(url)
}
}, [])
if (!user) return Loading()
return (<div>User {user.name} {user.surname}</div>)
}
My login is inside pages/login.js with this content :
const Login = () => (<div>Login page</div>)
export default Login
However it's stuck on the loading page even though I don't have the session. Am I misusing the router ?
The URL is changed properly to /login?redirect_uri=%2Ffoo but the content is not the one from my Login
Below is a stackblitz reproduction: https://stackblitz.com/edit/github-supacx-rpl5rm
I see the problem, You are preventing the app to load.
You are not changing user's state in case there is no session_active cookie.
You are trying to render the only loading component instead of the next App.
if (!user) return Loading()
Solution:
Let the app render
render the loading component inside the return statement of the app component
import React, { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
export default function App({ Component, pageProps }) {
const [user, setUser] = useState(null)
const router = useRouter()
useEffect(() => {
const session = document.cookie.includes('session_active=true')
if (session) {
fetch('/api/user').then((u) => u.json().then(setUser))
} else {
setUser(true) // set to true.
const redirectURI = router.pathname
const url = { pathname: '/login', query: { redirect_uri: redirectURI } }
router.push(url)
}
}, [])
return (
<>
{!user && <div>loading</div>}
<Component {...pageProps} />
</>
)
}
I am not sure which approach you will use to pass user info to all components. My suggestion would be to create a context for authentication and wrap the app with it. Then handle the user session and redirection in the context.
I'm trying to make a page to show the details of each video.
I fetched multiple video data from the back-end and stored them as global state.
This code works if I go to the page through the link inside the app. But If I reload or open the URL directory from the browser, It can not load the single video data.
How should I do to make this work?
Thanx
Single Video Page
import { useState, useEffect, useContext } from "react";
import { useParams } from "react-router-dom";
import { VideoContext } from "../context/videoContext";
const SingleVideo = () => {
let { slug } = useParams();
const [videos, setVideos] = useContext(VideoContext);
const [video, setVideo] = useState([]);
useEffect(() => {
const result = videos.find((videos) => {
return videos.uuid === slug;
});
setVideo((video) => result);
}, []);
return (
<>
<div>
<h1>{video.title}</h1>
<p>{video.content}</p>
<img src={video.thumbnail} alt="" />
</div>
</>
);
};
export default SingleVideo;
Context
import React, { useState, createContext, useEffect } from "react";
import Axios from "axios";
import { AxiosResponse } from "axios";
export const VideoContext = createContext();
export const VideoProvider = (props) => {
const [videos, setVideos] = useState([]);
const config = {
headers: { "Access-Control-Allow-Origin": "*" },
};
useEffect(() => {
//Fetch Vidoes
Axios.get(`http://localhost:5000/videos`, config)
.then((res: AxiosResponse) => {
setVideos(res.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<VideoContext.Provider value={[videos, setVideos]}>
{props.children}
</VideoContext.Provider>
);
};
I think the reason is because when you refresh the app, you fetch the video data on context and the useEffect on your single video page component runs before you receive those data.
To fix you can simply modify slightly your useEffect in your single video component to update whenever you receive those data:
useEffect(() => {
if (videos.length) {
const result = videos.find((videos) => {
return videos.uuid === slug;
});
setVideo((video) => result);
}
}, [videos]);
I am practicing AWS' Cognito. For front-end I am using React and for routing I am using React-router-dom. For Cognito validation I am using amazon-cognito-identity-js package. My Congito signin, signup and confirmation logic works fine. I made one helper function where I validate the Congnito. and reuse it in different component. I split my Nav bar into two components. From Congnito current user I made one callback function and use it in useEffect, and dependencies put the callback function, by default getAuthenticatedUser is null. I add condition where it fetch the data, if getAuthenticatedUser then redirect to signin and signup page. Because of this condition I am getting the error: Can't perform a React state update on an unmounted component...... Also when I signed in it does not change the nav bar name, I have to refresh the browser then I can see the change. I share my code in codesandbox.
This is my helper function
import React, { useState, useCallback, useEffect, useMemo } from 'react';
import { CognitoUserPool } from 'amazon-cognito-identity-js';
const Pool_Data = {
UserPoolId: 'us-east-1_IEyFfUupx',
ClientId: '63fc9g5c3g9vhqdalrv9eqhoa2',
};
export default function useHandler() {
const [state, setstate] = useState({
loading: false,
isAuthenticated: false
})
const { loading, isAuthenticated } = state;
const userPool = new CognitoUserPool(Pool_Data)
const getAuthenticatedUser = useCallback(() => {
return userPool.getCurrentUser();
},
[],
);
console.log(getAuthenticatedUser());
useEffect(() => {
getAuthenticatedUser()
}, [getAuthenticatedUser])
const signOut = () => {
return userPool.getCurrentUser()?.signOut()
}
console.log(getAuthenticatedUser());
return {
loading,
isAuthenticated,
userPool,
getAuthenticatedUser,
signOut
}
};
This is my navigation
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import SigninLinks from './SigninLinks';
import SignoutLinks from './SignoutLinks';
import useHandlder from '../configHandler/useHandler';
const Nav = () => {
const { getAuthenticatedUser } = useHandlder();
const Links = getAuthenticatedUser() ? <SigninLinks /> : <SignoutLinks />
return (
<nav className="nav-wrapper grey darken-3">
<div className="container">
<h2 className="brand-logo">Logo</h2>
{
Links
}
</div>
</nav>
);
};
export default Nav;
This is Home screen where it display the data and getting error
import React, { useState, useEffect } from "react";
import { api } from './api';
import useHandlder from './configHandler/useHandler'
import { Redirect } from 'react-router-dom';
const Home = () => {
const [state, setstate] = useState([]);
const { getAuthenticatedUser } = useHandlder();
useEffect(() => {
fetchData()
}, [])
const fetchData = async () => {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts`);
const data = await response.json();
setstate(data)
}
return getAuthenticatedUser() === null ? <Redirect to="/signin" /> : //In here is the //error happening.
<div className="row">
<h1>hello welcome to home</h1>
{
state?.map((i: string, id: number) => <h1 key={id}>{i.title}</h1>)
}
</div>
};
export default Home;
Issue
The issue is your app starts on the home ("/") path and renders the Home component. Home initiates a GET request upon mounting and checks for an authenticated user, and if there is none, renders a redirect to your "/signin" route.
The fetch is asynchronous so when the redirect occurs the GET request is resolving after Home has been unmounted and it tries to update the local state with the response data, but can't.
Solution
You need to use an Abort Controller to cancel in-flight requests. If the component unmounts, an effect cleanup function cancels the fetch request. In Home update the useEffect hook to create an AbortController and signal to be used in a cleanup function.
useEffect(() => {
const controller = new AbortController(); // <-- create controller
const { signal } = controller; // <-- get signal for request
const fetchData = async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts`,
{ signal } // <-- pass signal with options
);
const data = await response.json();
setstate(data);
};
fetchData();
return () => controller.abort(); // <-- return cleanup function to abort
}, []);
Demo
Hey guys I'm trying to show the data I get from firestore.
When I'm saving the code in the IDE and I'm on the current page, it is working.
But if then I go to another page/refresh the browser - it doesn't render/render in time and render the "hold" I set him to return
the code:
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import { useAuth } from '../contexts/AuthContext';
export default function Cart() {
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const user = useAuth()
const userDoc = firebase.firestore().collection("cart").doc(userMail)
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email, console.log(userMail))
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
}, [])
if (userCart === undefined) return <h1>hold</h1>
const { item } = userCart
console.log(item);
return (
<main className="main-cart">
//here im try to make sure it got the data befor render//
{item && item.map(item => {
return (
<div key={item.itemId}>
<h3>{item.name}</h3>
</div>
)
})}
</main>
)
}
i just had to replace the 2nd parameter from the useEffect to userCart