This is a useEffect gotcha and I run into it at least once a month. :(
Anyway,
I have a component that is rendering one of two components based on a state condition.
This works fine except for one problem. I get the infamous "flicker" render of the previous component. I am trying to mask this with a third component - dumb loader spinner. This is where the problem occurs. I can't get the dumb thing to work.
My working code is the following. The only relevant parts are those with comments.
Scroll further down for my non-working pseudo code solution.
Thank you.
import React, {useState} from 'react';
import { BrowserRouter, Route, Redirect } from "react-router-dom";
import { withRouter } from "react-router";
import {Switch} from 'react-router';
import LandingWithoutClients from './PageComponents/Landing';
import LandingWithClients from './PageComponents/Landing/LandingWithClients';
import Workflows from "./PageComponents/Workflows";
import SaveAndLoad from "./PageComponents/SaveAndLoad";
import Calendar from "./PageComponents/Calendar";
import Navbar from "./PageComponents/Navigation/Navbar";
import Authentication from './PageComponents/Authentication'
import Navigation from "./PageComponents/Navigation";
import { MuiPickersUtilsProvider } from 'material-ui-pickers';
import MomentUtils from '#date-io/moment';
import db from "./services/indexDB";
import SaveIcon from "#material-ui/icons/Save";
function App(props){
const [clientExistsState, updateClientsExistsState] = useState(false);
db.clients.toArray(function(data){
if(data[0]){
updateClientsExistsState(true)
}else{
updateClientsExistsState(false)
}
})
let Nav = clientExistsState ? Navbar : Navigation
//_____________________________________________________If clientsExists assign Landing with LandingWithClients otherwise assign Landing with LandingWithoutClients
let Landing = clientExistsState ? LandingWithClients : LandingWithoutClients
//___________________________________________________________________________________
function redirectToClientsList(){
window.location.href = "/";
}
function redirectToCalendar(){
window.location.href = "/calendar";
}
function redirectToAuthentication(){
window.location.href = "/authentication";
}
function redirectToSaveAndLoad(){
window.location.href = "/save-and-load";
}
return (
<div className="App">
<Provider>
<MuiPickersUtilsProvider utils={MomentUtils}>
<BrowserRouter>
<div>
<Nav
endpointProps = {props}
redirectToClientsList = {redirectToClientsList}
redirectToCalendar={redirectToCalendar}
redirectToAuthentication={redirectToAuthentication}
redirectToSaveAndLoad={redirectToSaveAndLoad}
/>
<Switch>
<Route exact path="/" component={Landing} /> {/* Assign Landing Component*/}
<Route exact path="/client/:id/client-name/:client/workflows" component={Workflows} />
<Route exact path="/calendar" component={Calendar} />
<Route exact path="/authentication" component={Authentication} />
<Route exact path="/save-and-load" component={SaveAndLoad} />
<Redirect from="/*" to="/" />
</Switch>
</div>
</BrowserRouter>
</MuiPickersUtilsProvider>
</Provider>
</div>
);
}
export default withRouter(App);
here is a pseudo code fix with two useEffect instances
function App(props){
// code ...
cons [ loaderBool, setLoaderBool] = useState(true);
let Landing = Loader;
useEffect(() => {
if (loaderBool) {
setTimeout(() => {
setLoaderBool(false)
},500)
}
}, [])
useEffect(() => {
if (loaderBool) {
Landing = Loader
} else {
Landing = clientExistsState ? LandingWithClients : LandingWithoutClients
}
}, [loaderBool])
return(
<div>
<Route exact path="/" component={Landing} />
</div>
)
}
I fixed it like this:
import React, {useState, useEffect} from 'react';
import { BrowserRouter, Route, Redirect } from "react-router-dom";
import { withRouter } from "react-router";
import {Switch} from 'react-router';
import LandingWithoutClients from './PageComponents/Landing';
import LandingWithClients from './PageComponents/Landing/LandingWithClients';
import Workflows from "./PageComponents/Workflows";
import SaveAndLoad from "./PageComponents/SaveAndLoad";
import Calendar from "./PageComponents/Calendar";
import Navbar from "./PageComponents/Navigation/Navbar";
import Loader from './PageComponents/Loader';
import Authentication from './PageComponents/Authentication'
import Navigation from "./PageComponents/Navigation";
import { MuiPickersUtilsProvider } from 'material-ui-pickers';
import MomentUtils from '#date-io/moment';
import db from "./services/indexDB";
import SaveIcon from "#material-ui/icons/Save";
import Context,{Provider} from "./services/context";
// if client is active display Navigation.
// if client is not active then display NavigationWitSlide
// create new landing page
function App(props){
const [loaderBool, setLoaderBool] = useState(true)
const [clientExistsState, updateClientsExistsState] = useState(false);
db.clients.toArray(function(data){
if(data[0]){
updateClientsExistsState(true)
}else{
updateClientsExistsState(false)
}
})
let Nav = clientExistsState ? Navbar : Navigation
let Landing = clientExistsState ? LandingWithClients : LandingWithoutClients
function redirectToClientsList(){
window.location.href = "/";
}
function redirectToCalendar(){
window.location.href = "/calendar";
}
function redirectToAuthentication(){
window.location.href = "/authentication";
}
function redirectToSaveAndLoad(){
window.location.href = "/save-and-load";
}
// check if clients exists
useEffect(()=>{
setTimeout(()=>{
setLoaderBool(false)
},500)
},[])
return (
<div className="App">
<Provider>
<MuiPickersUtilsProvider utils={MomentUtils}>
<BrowserRouter>
<div>
<Nav
endpointProps = {props}
redirectToClientsList = {redirectToClientsList}
redirectToCalendar={redirectToCalendar}
redirectToAuthentication={redirectToAuthentication}
redirectToSaveAndLoad={redirectToSaveAndLoad}
/>
<Switch>
<Route exact path="/" component={(function(){
if(loaderBool){
return Loader
}else{
return Landing
}
}())} />
<Route exact path="/client/:id/client-name/:client/workflows" component={Workflows} />
<Route exact path="/calendar" component={Calendar} />
<Route exact path="/authentication" component={Authentication} />
<Route exact path="/save-and-load" component={SaveAndLoad} />
<Redirect from="/*" to="/" />
</Switch>
</div>
</BrowserRouter>
</MuiPickersUtilsProvider>
</Provider>
</div>
);
}
export default withRouter(App);
Try useMemo.
const Landing = useMemo(() => {
if (!loaderBool) {
if (clientExistsState) {
return LandingWithClients;
}
return LandingWithoutClients;
}
return Loader;
}, [clientExistsState, loaderBool]);
Related
This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 9 months ago.
Unable to route to admin page even after successful login, displays a blank screen. On successful login system should navigate and display the admin screen. Could someone please advise why the routing is not happening ?
//login.js
import React, { useEffect, useState } from 'react';
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
const Login = () =>{
const { register, errors, handleSubmit } = useForm();
const [loginData, setLoginData] = useState("");
const [helperText, setHelperText] = useState('');
const navigate = useNavigate();
const onSubmit = (data) => {
try {
const userEmail = "dev#test.com"; // for time being just hard code data
const userPassword = "somePass123"; // for time being just hard code data
if(data.email === userEmail && data.password === userPassword ){
localStorage.setItem('loginEmail', userEmail);
setLoginData(userEmail);
navigate('/admin');
window.location.reload(true)
} else {
setHelperText("Invalid login details");
}
} catch (e){
console.log(e);
}
};
console.log(errors);
return (
<div className="wrapper">
<h3>Login</h3>
<section className="col2">
<div className='loginSection'>
<form onSubmit={handleSubmit(onSubmit)}>
<label>Email</label>
<input
type="text"
{...register("email", { required: true})}
/>
<label>Password</label>
<input
type="text"
{...register("password", { required: true})}
/>
<label>
<span className="loginValidationText">{helperText}</span>
</label>
<section className="col4">
<input type="submit" />
</section>
</form>
</div>
</section>
</div>
)
}
export default Login
//protectedRoute.js
import React from "react";
import { Route, BrowserRouter } from "react-router-dom";
export const ProtectedRoute = ({ component: Component, ...rest }) => {
return (
<Route
{...rest}
render={(props) => {
if (localStorage.getItem("loginEmail")) {
return <Component {...props} />;
} else {
return (
<>
<BrowserRouter
to={{
pathname: "/login",
state: {
from: props.location,
},
}}
/>
</>
);
}
}}
/>
);
};
//navigation.js
import React from 'react';
import { NavLink} from 'react-router-dom';
const Navigation = () => {
return (
<div className="App">
<div className="wrapper">
<div id="wrap">
<nav className="siteNavigation_nav_links">
<div className="main_links_nav">
<img className='logoimage' alt="SSS Logo" src="/images/super.png"></img>
<div className="navigationpanel">
<NavLink className="mob_link" to="/">Home</NavLink>
<NavLink className="mob_link" to="/team">Team</NavLink>
<NavLink className="mob_link" to="/login">Login</NavLink>
</div>
</div>
</nav>
</div>
</div>
</div>
)
}
export default Navigation;
//App.js
import React, { useEffect, useState } from 'react';
import { BrowserRouter, Route, Routes, Switch} from "react-router-dom";
import Navigation from './components/navigation';
import Home from "./components/home";
import Team from "./components/team";
import Admin from "./components/admin";
import Login from "./components/login";
import { ProtectedRoute } from "./components/protectedRoute";
function App() {
return (
<BrowserRouter>
<Navigation />
<Routes>
<Route path="/" element={<Home />}>
</Route>
<Route path="/team" element={<Team />}>
</Route>
<Route path="/login" element={<Login />}>
</Route>
<Route path="/admin" element={
<ProtectedRoute >
<Admin />
</ProtectedRoute>
}>
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
Change PotectedRoute.js to the code below, as what you are doing is more like what we used to do for React Router Dom v5 while you are using v6.
import { Navigate, useLocation} from "react-router-dom";
export const ProtectedRoute = ({children}) => {
let location = useLocation();
if(!localStorage.getItem("loginEmail")){
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
};
The information from useLocation passed as prop to Navigate can be used in Login so you send the user to that specific url where they were going to instead of a hard coded one (admin in your case), useful if you had multiple protected routes. Though it's not a requirement, you can remove it.
To know more about authentication in React Router Dom v6, visit this example on StackBlitz from their documentation. Don't look at the editor lint errors, it's the most complete and straightforward authentication example.
I have two(USER and ADMIN) users in my react project and I want to redirect them to their respected page after a successful login. To achieve this I created a standalone component called authenticatedRoute.js and wrap those components by AuthenticatedRoute component. Here are the codes
AuthenticatedRoute.js
import { useHistory } from "react-router-dom";
import { Route } from "react-router-dom";
const AuthenticatedRoute = ({ children, ...rest }) => {
const history = useHistory();
var tokenn = JSON.parse(localStorage.getItem("desta"));
var user_type = tokenn?.user.roles[0]; //USER or ADMIN
var loggedIn = tokenn?.user_status; //true
const redirect = () => {
try {
switch (user_type) {
case "USER":
return [history.push("/user"), children];
case "ADMIN":
return [history.push("/admin"), children];
default:
console.log("redirect");
}
} catch (err) {
return [history.push("/signin"), children];
}
};
return (
<Route
render={() => {
return loggedIn ? redirect() : history.push("/signin");
}}
/>
);
};
export default AuthenticatedRoute;
App.js (minimal code)
import "./App.css";
import Header from "./components/Header/Header";
import HeaderLinks from "./components/Header/HeaderLinks";
import Hero from "./components/Header/Hero/Hero";
import Login from "./components/auth/Login";
import {
BrowserRouter as Router,
Route,
Switch,
useHistory,
} from "react-router-dom";
import SignUp from "./components/auth/SignUp";
import ViewUserProfile from "./components/Admin/ViewUserProfile";
import AdminHeader from "./components/Admin/AdminHeader";
import ManageBusinessType from "./components/Admin/ManageBusinessType";
import ManageCompany from "./components/Admin/ManageCompany";
import UserDashboard from "./components/Users/UserDashboard";
import ViewProfile from "./components/Users/ViewProfile";
import { ResetPassword } from "./components/Users/ResetPassword";
import ChangePassword from "./components/Users/ChangePassword";
import { useEffect } from "react";
import AuthenticatedRoute from "./components/auth/authRoute/AuthenticatedRoute";
function App() {
return (
<Router>
<Switch>
<AuthenticatedRoute path="/" exact>
<Hero />
</AuthenticatedRoute>
<AuthenticatedRoute path="/admin" exact>
<AdminHeader />
</AuthenticatedRoute>
<AuthenticatedRoute path="/user" exact>
<UserDashboard />
</AuthenticatedRoute>
</Switch>
</Router>
);
}
export default App;
After a successful login it redirects me to the right user role page but it's empty page as the screenshot below.
How can I fix the Issue? I used react-router-dom#5.3.0
Thanks
You are rendering the result of a navigation action instead of JSX. AuthenticatedRoute should render either a Route rendering the routed content or a Redirect to the appropriate path.
Example:
If loggedIn is falsey, then redirect to "/signin", otherwise check the role the route should have access to, and if the role matches render a Route with the props passed through, otherwise redirect to the appropriate user/admin path.
import { useHistory } from "react-router-dom";
import { Route } from "react-router-dom";
const AuthenticatedRoute = ({ roles, ...props }) => {
const tokenn = JSON.parse(localStorage.getItem("desta"));
const user_type = tokenn?.user.roles[0]; //USER or ADMIN
const loggedIn = tokenn?.user_status; //true
return loggedIn
? roles.includes(user_type)
? <Route {...props} />
: <Redirect to={user_type === "USER" ? "/user" : "/admin"} />
: <Redirect to="/signin" />;
};
export default AuthenticatedRoute;
Specify the roles a route should accessible by.
function App() {
return (
<Router>
<Switch>
<AuthenticatedRoute path="/admin" roles={["ADMIN"]}>
<AdminHeader />
</AuthenticatedRoute>
<AuthenticatedRoute path="/user" roles={["USER"]}>
<UserDashboard />
</AuthenticatedRoute>
<Route path="/">
<Hero />
</Route>
</Switch>
</Router>
);
}
I am working on a project where I am using the strikingDash template. Here I face some issues of routing while changing the routes from URL.
auth.js
import React, { lazy, Suspense } from "react"
import { Spin } from "antd"
import { Switch, Route, Redirect } from "react-router-dom"
import AuthLayout from "../container/profile/authentication/Index"
const Login = lazy(() =>
import("../container/profile/authentication/overview/SignIn")
)
const SignUp = lazy(() =>
import("../container/profile/authentication/overview/SignUp")
)
const ForgetPassword = lazy(() =>
import("../container/profile/authentication/overview/ForgetPassword")
)
const EmailConfirmation = lazy(() =>
import("../container/profile/authentication/overview/EmailConfirmation")
)
const VerificationPage = lazy(() =>
import("../container/profile/authentication/overview/VerificationPage")
)
const NotFound = () => {
console.log("NOT FOUND")
return <Redirect to="/" />
}
const FrontendRoutes = () => {
return (
<Switch>
<Suspense
fallback={
<div className="spin">
<Spin />
</div>
}
>
<Route exact path="/verification" component={VerificationPage} />
<Route exact path="/email-confirmation" component={EmailConfirmation} />
<Route exact path="/forgetPassword" component={ForgetPassword} />
<Route exact path="/signup" component={SignUp} />
<Route exact path="/" component={Login} />
<Route component={NotFound} />
</Suspense>
</Switch>
)
}
export default AuthLayout(FrontendRoutes)
App.js
import React, { useEffect, useState } from "react";
import { hot } from "react-hot-loader/root";
import { Provider, useSelector } from "react-redux";
import { ThemeProvider } from "styled-components";
import { BrowserRouter as Router, Redirect, Route } from "react-router-dom";
import { ConfigProvider } from "antd";
import store from "./redux/store";
import Admin from "./routes/admin";
import Auth from "./routes/auth";
import "./static/css/style.css";
import config from "./config/config";
import ProtectedRoute from "./components/utilities/protectedRoute";
const { theme } = config;
const ProviderConfig = () => {
const { rtl, isLoggedIn, topMenu, darkMode } = useSelector(state => {
return {
darkMode: state.ChangeLayoutMode.data,
rtl: state.ChangeLayoutMode.rtlData,
topMenu: state.ChangeLayoutMode.topMenu,
isLoggedIn: state.Authentication.login,
};
});
const [path, setPath] = useState(window.location.pathname);
useEffect(() => {
let unmounted = false;
if (!unmounted) {
setPath(window.location.pathname);
}
// eslint-disable-next-line no-return-assign
return () => (unmounted = true);
}, [setPath]);
return (
<ConfigProvider direction={rtl ? "rtl" : "ltr"}>
<ThemeProvider theme={{ ...theme, rtl, topMenu, darkMode }}>
<Router basename={process.env.PUBLIC_URL}>
{!isLoggedIn ? <>{console.log("INSIDE PUBLIC")}<Route path="/" component={Auth} /></> : <ProtectedRoute path="/admin" component={Admin} />}
{isLoggedIn && (path === process.env.PUBLIC_URL || path === `${process.env.PUBLIC_URL}/`) && (
<Redirect to="/admin" />
)}
</Router>
</ThemeProvider>
</ConfigProvider>
);
};
function App() {
return (
<Provider store={store}>
<ProviderConfig />
</Provider>
);
}
export default hot(App);
Whenever I change the URL to another route as I described above in Frontend Routes. Then it will always print console statements like these:
INSIDE PUBLIC
NOT FOUND
INSIDE PUBLIC
NOT FOUND
Expected Behaviour: Whenever I update the URL it will render the component according to the switch case and return it back
Actual Behaviour: Whenever I update the URL it will render the component as well as the default component. I think Switch here renders multiple components, but I don't know why.
I just resolved the issue by moving the Switch Tag inside the Suspense tag in the auth.js file.
The problem should be in the order of your pages: the root path works as a collector of all the pages, you should try to add the exact keyword to the Router path. Here the reference for the differences between the different notations.
<Route exact path="/" component={Login} />
I am using a tawk.io chat on my reactjs app:-
This is content of my index.js file:-
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import BookRead from "./pages/BookRead";
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Switch>
<Route exact path="/view/:id/:section/:part" component={BookRead} />
<Route component={App} />
</Switch>
</BrowserRouter>
</React.StrictMode>,
document.getElementById("root")
);
App.js component file content :-
import React, { useEffect, useState } from "react";
import { Route } from "react-router-dom";
import Login from "./pages/Login";
import Account from "./pages/Account";
import Contact from "./pages/Contact";
import Home from "./pages/Home";
function App(props) {
useEffect(() => {
var Tawk_API = Tawk_API || {},
Tawk_LoadStart = new Date();
(function () {
var s1 = document.createElement("script"),
s0 = document.getElementsByTagName("script")[0];
s1.async = true;
s1.src = "https://embed.tawk.to/5a624e/default";
s1.charset = "UTF-8";
s1.setAttribute("crossorigin", "*");
s0.parentNode.insertBefore(s1, s0);
})();
}, []);
return (
<div className="content">
<div className="container">
<Route exact path="/" component={Home} />
<Route path="/contact-us" component={() => <Contact user={user} />} />
)}
/>
<Route path="/account" component={Account} />
</div>
</div>
);
}
export default App;
How can i show the chat widget in all components inside the App.js route and hide/remove it from the route <Route exact path="/view/:id/:section/:part" component={BookRead} /> ?
Solved by adding the following to BookRead.js component :-
useEffect(() => {
if (window.Tawk_API) {
window.Tawk_API.hideWidget();
}
return () => {
if (window.Tawk_API) {
window.Tawk_API.showWidget();
}
};
}, []);
The API docs at https://developer.tawk.to/jsapi/ suggest you could use Tawk_API.showWidget(); and Tawk_API.hideWidget(); to show and hide the widget.
React-Router provides an useLocation hook you can use to figure out when the location has changed.
Put those two together and you should be able to get the effect you want.
I am using Router and customHistory to help me redirect the pages, but the pages not render correctly.
The code works like this: if the user is authorized or log in, then the user should be redirected to "localhost:8080/dashboard" and see the dashboard(with data fetching from firebase) & header; if the use is log out, then the user should be redirect to "locahost:8080/" and see the log in button with the header.
However, after I successfully log in, the url is "localhost:8080/dashboard" without any data fetched from firebase, only things I can see are the header and login button. But if I hit "RETURN" with the current url which is "localhost:8080/dashboard", it will redirect to correct page with all data fetching from firebase, and no login button.
This is the github_link to the code.
I have spent times searching online, but do not find any positive result except this one. After reading the stackoverflow I feel my code has some problems with asynchronization. Any thoughts?
I really appreciate for your help! Thanks!
This is my AppRouter.js:
export const customHistory = createBrowserHistory();
const AppRouter = () => (
<Router history={customHistory}>
<div>
<Header />
<Switch>
<Route path="/" exact component={LoginPage} />
<Route path="/dashboard" component={ExpenseDashboardPage} />
<Route path="/create" component={AddExpensePage} />
<Route path="/edit/:id" component={EditExpensePage} />
<Route path="/help" component={HelpPage} />
<Route component={LoginPage} />
</Switch>
</div>
</Router>
);
This is my app.js
import React, { Children } from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import "normalize.css/normalize.css"; //Normalize.css makes browsers render all elements more consistently and in line with modern standards.
import "./styles/styles.scss";
import AppRouter, { customHistory } from "./routers/AppRouter";
import configureStore from "./redux/store/configStore";
import { startSetExpenses } from "./redux/actions/expenses";
import { login, logout } from "./redux/actions/auth";
import "react-dates/lib/css/_datepicker.css";
import { firebase } from "./firebase/firebase";
//for testing: npm test -- --watch
const store = configureStore();
const jsx = (
<Provider store={store}>
<AppRouter />
</Provider>
);
ReactDOM.render(<p>Loading...</p>, document.getElementById("app"));
let hasRendered = false;
const renderApp = () => {
if (!hasRendered) {
ReactDOM.render(jsx, document.getElementById("app"));
hasRendered = true;
}
};
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log("log in");
store.dispatch(login(user.uid));
store.dispatch(startSetExpenses()).then(() => {
renderApp();
if (customHistory.location.pathname === "/") {
customHistory.push("/dashboard");
}
});
} else {
console.log("log out");
store.dispatch(logout());
renderApp();
customHistory.push("/");
}
});
This is the header.js
import React from "react";
import { BrowserRouter, Route, Switch, Link, NavLink } from "react-router-dom";
import { connect } from "react-redux";
import { startLogout } from "../redux/actions/auth";
export const Header = ({ startLogout }) => (
<header>
<h1>Expensify</h1>
<NavLink to="/" activeClassName="is-active">
Dashboard
</NavLink>
<NavLink to="/create" activeClassName="is-active">
CreateExpense
</NavLink>
<button onClick={startLogout}>Logout</button>
</header>
);
const mapDispatchToProps = (dispatch) => ({
startLogout: () => dispatch(startLogout()),
});
export default connect(undefined, mapDispatchToProps)(Header);