Next js getting double result - reactjs

I am trying to build a web application using next js. Router Push is working fine, it's redirect to login page, but after redirect in login page i am getting double notification. where the problem? i want to show only one notification. please help me.
(1) dashboard Page
import { useRouter } from "next/router";
import { useContext } from "react";
import { toast } from "react-toastify";
import AppContext from "../../components/AppContext";
import DashLayout from "../../layouts/DashLayout";
function DashMain() {
const router = useRouter();
const server = useContext(AppContext);
if (!server.auth) {
router.push("/auth/login");
toast.success("Please login to access dashboard");
}
return (
<>
{server.auth ?
<h2>Welcome to Dashboard</h2>
:
<h1>Please login to access dashboard</h1>
}
</>
);
}
export default DashMain;
(2) Login Page
import { useContext, useState } from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import Link from "next/link";
import Image from "next/image";
import AppContext from "../../components/AppContext";
import { toast } from "react-toastify";
import MainLayout from "../../layouts/MainLayout";
function Login() {
const server = useContext(AppContext);
const router = useRouter();
if (server.auth) {
router.push("/");
toast.success("You are already logged In");
}
//states
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState("");
//Login Submit
const submitForm = (e) => {
e.preventDefault();
server.login({ email, password, setErrors });
}
if (server.userLoding) {
return (
<div id="mainmodal">
<div className="modalconent">
<span className='loader'></span>
</div>
</div>
);
}
return (
<>
.......
</>
);
}
export default Login;
Login.getLayout = function getLayout(page) {
return <MainLayout>{page}</MainLayout>;
};
Same way, when router redirect login page to home page i am getting double notification. please give me proper solution.

If you are using React 18 with StrictMode, then in development only a double-render is purposely occurring. This section of the documentation highlights this feature:
https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors
Essentially, it's helping you deal with a bad side effect:
if (server.auth) {
router.push("/");
toast.success("You are already logged In");
}
Since server is a context and auth is set, when React 18 performs its intentional double-render, your component triggers this twice. Because when your login page unmounts and remounts, context.auth is preserved.
A better way to handle to handle this situation would be to use a single hook the redirect logic where you detect location.pathname. For example:
useRedirects.ts
import { useEffect } from 'react';
import { useLocation, useRouter } from 'react-router-dom';
import toast from 'toast';
const useRedirects = () => {
const location = useLocation();
const { pathname } = location;
const router = useRouter();
// when pathname changes, do stuff
useEffect(() => {
// if on dashboard and not authed, force login page
if (pathname === '/dashboard' && !server.auth) {
router.push("/auth/login");
toast.success("Please login to access dashboard");
}
// if on login page and authed, go to dashboard
if (pathname === '/login' && server.auth) {
router.push("/");
toast.success("You are already logged In");
}
}, [pathname]);
return null;
};
Include this hook in your app.js file component:
import useRedirects from './useRedirects';
const App = () => {
useRedirect();
// ...
// return ...
};
export default App;
Now you can remove this from your dashboard page:
if (!server.auth) {
router.push("/auth/login");
toast.success("Please login to access dashboard");
}
And remove this from your login page:
if (server.auth) {
router.push("/");
toast.success("You are already logged In");
}
Essentially, the double render happens on purpose in React 18 to help you identify bad code (improperly created side effects). It doesn't happen in production and should help identify issues.

Related

what is difference between useNavigate and redirect in react-route v6?

I was trying to build my authentication logic with react-router v6 in react
and I found some odd thing when I ran my code below is my code
import React from 'react';
import useStore from '../../../store/store';
import { useEffect } from 'react';
import {useNavigate,redirect } from 'react-router-dom';
export default function (SpecificComponent, option, adminRoute = null) {
const navigate = useNavigate();
const {auth,accessToken}= useStore();
// option = null // pages taht anyone can access
// option = true // pages that only loginuser can access
// option = false // pages that loginuser can not access
// option = true ,adiminRoute = true // pages that only admin can access
function AuthenticationCheck(props){
useEffect(()=>{
auth(accessToken).then(res => {
if (res.accessToken) {
setAccessToken(res.accessToken)
}
//when authentication failed
if (res.isAuth===false) {
if (option) {
//to sign in page
navigate('/signin')
}
//when authentication successed
} else {
//trying to get in admin page but user is not admin
if (adminRoute && res.role !== "Admin") {
navigate('/landingpage')
//trying to get in admin page and user is admin
} else if (adminRoute && res.role === "Admin") {
navigate('/admin')
} else {
//trying to get in login page but user is signed in already
if (option === false) {navigate('/landing')}
}
}
//passed above, go to SpecificComponent
});
},[])
return(
<SpecificComponent {...props} />
)
}
return AuthenticationCheck
}
when I ran this code and trying to get in admin page as admin user, I could get in admin page but It trapped in loop and rerender admin page over and over.
else if (adminRoute && res.role === "Admin") {
navigate('/admin')
}
so I changed navigate with another react-router method redirect and It worked totally fine.
However,I still couldn't get that why navigate dind't work but redirect did?
this is redirect
export declare const redirect: RedirectFunction;
/**
* #private
* Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
*/
and this is useNavigate
export declare function useNavigate(): NavigateFunction;
/**
* Returns the context (if provided) for the child route at this level of the route
* hierarchy.
* #see https://reactrouter.com/docs/en/v6/hooks/use-outlet-context
*/
P.S Now, I get It It would've gone to admin page even I did my code like below but still not sure why useNavigate fall in infinity loop and that's what I want to know
else if (adminRoute && res.role === "Admin") {
}
Thx for reading, your help will be appreciated
From my understanding and reading from the docs, redirect is used in actions and loaders.
And useNavigate is a hook and it can only be used in React Hooks and React Components.
So for example you can use useNavigate to redirect the user in your Homepage Component, for example in some state change.
import { useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
export const Homepage = () => {
const [shouldRedirect, setShouldRedirect] = useState(false);
const navigate = useNavigate();
useEffect(() => {
if (shouldRedirect) navigate("/profile");
}, [shouldRedirect]);
return (
<div>
<button onClick={() => setShouldRedirect(true)}>Redirect</button>
</div>
);
};
And redirect should be used in normal functions, like loaders and actions, citing an example from the docs:
import { redirect } from "react-router-dom";
const loader = async () => {
const user = await getUser();
if (!user) {
return redirect("/login");
}
};
Source:
useNavigate
redirect

React Navigate to page based on useEffect dependency

I'm using GoTrue-JS to authenticate users on a Gatsby site I'm working on and I want the homepage to route users to either their user homepage or back to the login page.
I check the existence of a logged-in user in a Context layer then define a state (user) that is evaluated on the homepage with a useEffect hook with the state as the dependency.
The expected behavior is that the useEffect hook will trigger the check for a user once the function is completed and route the user. But what happens is that the hook seems to check without the user state getting changed which routes the user to the login page.
Here's an abridged version of the code:
context.js
import React, {
useEffect,
createContext,
useState,
useCallback,
useMemo,
} from "react";
import GoTrue from 'gotrue-js';
export const IdentityContext = createContext();
const IdentityContextProvider = (props) => {
//create the user state
const [user, setUser] = useState(null);
//init GoTrue-JS
const auth = useMemo(() => {
return new GoTrue({
APIUrl: "https://XXXXXX.netlify.app/.netlify/identity",
audience: "",
setCookie: true,
});
},[]);
//get the user if they are signed in
useEffect(() => {
setUser(auth.currentUser());
},[auth]);
return (
<IdentityContext.Provider value={{ auth,user }}>
{props.children}
</IdentityContext.Provider>
)
}
export default IdentityContextProvider;
index.js
import { navigate } from 'gatsby-link';
import { useContext, useEffect } from 'react'
import { IdentityContext } from '../contexts/IdentityContext';
export default function HomePage() {
const { user } = useContext(IdentityContext);
useEffect(() => {
if (user) {
navigate("/user/home");
console.log("there's a user");
} else {
navigate("/login");
console.log("no user");
}
}, [user]);
return null
}
When I remove the navigate functions I see no user, then there's a user in the log when I load the homepage. I thought the useEffect hook would only fire if the state I listed in the dependency array (user) was changed. If there's no user then auth.currentUser() will return null and if there is one, then I will get all the user data.
Why are you using the user as a dependency? Just use the useEffect with an empty dependency. Also if you want to block the view while the processing, make a state as isLoading ( bool) and conditional render with it
!isLoading ?
<></>
:
<h1>Loading..</h1>
Here's the solution: Netlify's gotrue-js will return null for currentUser() if there is no user signed in so I need to first declare my user state as something other than null then set my conditional to detect null specifically so my app knows the check for a signed in user occurred.
context.js
import React, {
useEffect,
createContext,
useState,
useCallback,
useMemo,
} from "react";
import GoTrue from 'gotrue-js';
export const IdentityContext = createContext();
const IdentityContextProvider = (props) => {
//create the user state
//set either to empty string or undefined
const [user, setUser] = useState("");
//init GoTrue-JS
const auth = useMemo(() => {
return new GoTrue({
APIUrl: "https://XXXXXX.netlify.app/.netlify/identity",
audience: "",
setCookie: true,
});
},[]);
//get the user if they are signed in
useEffect(() => {
setUser(auth.currentUser());
},[auth]);
return (
<IdentityContext.Provider value={{ auth,user }}>
{props.children}
</IdentityContext.Provider>
)
}
export default IdentityContextProvider;
index.js
import { navigate } from 'gatsby-link';
import { useContext, useEffect } from 'react'
import { IdentityContext } from '../contexts/IdentityContext';
export default function HomePage() {
const { user } = useContext(IdentityContext);
useEffect(() => {
if (user) {
navigate("/user/home");
console.log("there's a user");
} else if (user == null) {
navigate("/login");
console.log("no user");
}
}, [user]);
return null
}
There was a similar question regarding Firebase where they were also getting no user on load even when one was signed in because of the state. The accepted answer is doesn't provide a snippet so it's gone to the wisdom of the ancients, but I was able to work with another engineer to get this solution.

React - Warning: Cannot update a component (`PrivateRoute`) while rendering a different component (`Modules`)

Getting the following error on all child components.
react-dom.development.js:86 Warning: Cannot update a component
(PrivateRoute) while rendering a different component (Example). To
locate the bad setState() call inside Examples,
I've found lots of examples of the same error but thus far no solutions
React Route Warning: Cannot update a component (`App`) while rendering a different component (`Context.Consumer`)
Can Redux cause the React warning "Cannot update a component while rendering a different component"
The PrivateRoute wraps the component to redirect if not logged in.
export default function PrivateRoute() {
const session: ISessionReducer = useSelector((state: RootState) => state.core.session);
useEffect(() => {
if (!session.jwt) <Navigate to="/login" />;
}, [session]);
return <Outlet />;
};
It is happening because useEffect runs after the component is rendered. So what's happening in this case is that your Outlet component is getting rendered first before your code in useEffect runs. So if the jwt token doesn't exist then it will try to redirect but it won't be able to because your Outlet will already be rendered by then.
So I can give you the solution of what I use to check if the jwt token exist.
1.) I create a custom hook for checking if the token exists.
2.) And then I use that custom hook in my privateRoute component to check if the user is loggedIn.
useAuthStatus.js
import { useState, useEffect } from 'react'
import { useSelector } from 'react-redux'
export const useAuthStatus = () => {
const [loggedIn, setLoggedIn] = useState(false)
const [checkingStatus, setCheckingStatus] = useState(true)
const { user } = useSelector((state) => state.auth)
useEffect(() => {
if (user?.token) {
setLoggedIn(true)
} else {
setLoggedIn(false)
}
setCheckingStatus(false)
}, [user?.token])
return { loggedIn, checkingStatus }
}
PrivateRoute component
import { Navigate, Outlet } from 'react-router-dom'
import { useAuthStatus } from '../../hooks/useAuthStatus'
import CircularProgress from '#mui/material/CircularProgress'
const PrivateRoute = () => {
const { loggedIn, checkingStatus } = useAuthStatus()
if (checkingStatus) {
return <CircularProgress className='app__modal-loader' />
}
return loggedIn ? <Outlet /> : <Navigate to='/login' />
}
export default PrivateRoute

firebase onAuthStateChanged executes while sign-in state doesn't change, it also returns null before returning user

I'm learning firebase authentication in react. I believed onAuthStateChanged only triggers when the user sign-in state changes. But even when I go to a different route or refresh the page, it would still execute.
Here is my AuthContext.js
import React, {useContext,useEffect,useState} from 'react';
import {auth} from './firebase';
import { createUserWithEmailAndPassword, onAuthStateChanged, signInWithEmailAndPassword,
signOut } from "firebase/auth";
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({children}) {
const [currentUser,setCurrentUser] = useState();
const [loading,setLoading] = useState(true);
useEffect(()=>{
const unsub = onAuthStateChanged(auth,user=>{
setLoading(false);
setCurrentUser(user);
console.log("Auth state changed");
})
return unsub;
},[])
function signUp(email,password){
return createUserWithEmailAndPassword(auth,email,password)
}
function login(email,password){
return signInWithEmailAndPassword(auth,email,password);
}
function logout(){
return signOut(auth);
}
const values = {
currentUser,
signUp,
login,
logout
}
return <AuthContext.Provider value={values}>
{!loading && children}
</AuthContext.Provider>;
}
I put onAuthStateChanged in useEffect(), so every time the component renders the code inside will run. But why would onAuthStateChanged() still run when user sign-in state does not change? I'm asking this question because it created problems.
onAuthStateChanged would first return a user of null. If user is already authenticated, it would return the user a second time. Because of the first "null" user, my other page would not work properly. For example, I have this private router that would always redirect to the login page even when the user is authenticated.
My Private Route
import React from 'react';
import {Route, Navigate} from "react-router-dom";
import { useAuth } from './AuthContext';
export default function Private() {
const {currentUser} = useAuth();
return <div>
{currentUser ? <>something</>:<Navigate to="/login"/>}
</div>;
}
if onAuthStateChanged doesn't trigger when I don't sign in or log out, I wouldn't have the problems mentioned above
I don't think the issue is with onAuthStateChange per se, but rather the fact that you're setting loading to false first, and only setting the current user afterwards. While react attempts to batch multiple set states together, asyncronous callbacks which react is unaware of won't be automatically batched (not until react 18 anyway).
So you set loading to false, and the component rerenders with loading === false, and currentUser is still on its initial value of undefined. This then renders a <Navigate>, redirecting you. A moment later, you set the currentUser, but the redirect has already happened.
Try using unstable_batchedUpdates to tell react to combine the state changes into one update:
import React, { useContext, useEffect, useState } from "react";
import { unstable_batchedUpdates } from 'react-dom';
// ...
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => {
unstable_batchedUpdates(() => {
setLoading(false);
setCurrentUser(user);
});
console.log("Auth state changed");
});
return unsub;
}, []);

React-router : conditionally redirect at render time

So I have this basic component <Redirectable />:
import React from 'react';
import {
useParams,
useHistory,
Redirect,
} from 'react-router-dom';
export default () => {
const history = useHistory();
const {id} = useParams();
if (!checkMyId(id) {
// invalid ID, go back home
history.push('/');
}
return <p>Hey {id}</p>
}
But I get the following error:
Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
I also tried: <Redirect push to="/" />, but same error.
What's the correct way to handle this? I read about onEnter callback at <Router /> level, but as far as I'm concerned, the check should happen at <Redirectable /> level.
There should be a solution, shouldn't it? I don't feel like I'm doing something completely anti-react-pattern, am I?
This seems to do the trick. I was not able to find any documentation as to why this occures. All I was able to find was different examples with callbacks but this solved it for me.
import React from 'react';
import {
useParams,
useHistory,
Redirect,
} from 'react-router-dom';
const MyComponent = () => {
const history = useHistory();
const {id} = useParams();
if (!checkMyId(id) {
// invalid ID, go back home
history.push('/');
}
return <p>Hey {id}</p>
}
export default MyComponent;
It seems that react may recognize export default () => { as a pure component and so side effects are prohibited.
Yes, it seems to me you are written the component in a anti pattern way. Can you please update like below:
const Rediractabke = () => {
const history = useHistory();
const {id} = useParams();
if (!checkMyId(id) {
// invalid ID, go back home
history.push('/');
}
return <p>Hey {id}</p>
}
export default as Redirectable;
#c0m1t was right, the solution was to use useEffect:
import React, {useEffect} from 'react';
import {
useParams,
useHistory,
Redirect,
} from 'react-router-dom';
export default () => {
const history = useHistory();
const {id} = useParams();
useEffect(() => {
if (!checkMyId(id) {
// invalid ID, go back home
history.push('/');
}
})
return <p>Hey {id}</p>
}

Resources