I encouter a problem with my react app, using react-router-dom v5.
When I change the route manually or when I use , the component does not update, even when I'm refreshing the page.
Here is my code :
import React, { useEffect, useState } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import openSocket from "socket.io-client"
import ChannelSelection from './auth/ChannelSelection'
import Home from './home/Home'
import AppContext from '#context/AppContext'
const App = () => {
const [socket, setSocket] = useState()
const [channel, setChannel] = useState('')
const [pseudo, setPseudo] = useState('')
const store = {
user: {
pseudo: pseudo,
setPseudo: (pseudo) => setPseudo(pseudo)
},
app: {
channel: channel,
setChannel: (channel) => setChannel(channel)
}
}
useEffect(() => {
const host = '127.0.0.1'
const port = '8080'
setSocket(openSocket(host + ':' + port))
}, [])
return (
<AppContext.Provider value={store}>
<Router>
<Switch>
<Route exactPath="/" component={() => <ChannelSelection socket={socket} />} />
<Route path="/:channel" component={() => <Home socket={socket} />} />
</Switch>
</Router>
</AppContext.Provider>
)
}
export default App
I'm a little bit confused rigth now because I've already use react-router-dom in the past and never encouter this problem.
Thank you in advance !
You should be using path not exactPath I think?
<Route path="/" exact component={() => <ChannelSelection socket={socket} />}
The path will be useful if you have multiple paths with similar names. Like path="/some" and path="/some/path" which is where you'd need the exact keyword.
Add the exact props in the routes and change the exactPath to path.
There is no prop with exactPath name in react-router-dom.
<Route path="/" exact component={() => <ChannelSelection socket={socket} />} />
Related
How do I pass the id from /profile/:id to the function generateProfile(findProfile(id))
Below is my code
import { Routes, Route } from 'react-router-dom';
import Profile from './Profile';
import '../assets/styles/App.css';
import seedProfiles from '../seedProfiles';
import generateProfile from '../helpers/profileHelper';
function App() {
const findProfile = id => seedProfiles.find(profile => profile.id === id);
return (
<Routes>
<Route exact="true" path="/" element={<h1>PROFILES</h1>} />
<Route
exact="true"
path="/profile/:id"
element={<Profile profile={generateProfile(findProfile(???))} />}
/>
</Routes>
);
}
export default App;
Thanks
Route path parameters are only accessible via the useParams hook. If you need to access the route path parameters prior to rendering the routed component then you'd need to create a wrapping component that can "sniff" the parameters and apply any logic.
Example:
const ProfileWrapper = ({ getProfile }) => {
const { id } = useParams();
return <Profile profile={getProfile(id)} />
};
function App() {
const getProfile = id => generateProfile(
seedProfiles.find(profile => profile.id === id)
);
return (
<Routes>
<Route path="/" element={<h1>PROFILES</h1>} />
<Route
path="/profile/:id"
element={<ProfileWrapper profile={getProfile} />}
/>
</Routes>
);
}
However, it's more common to move this logic into the component being rendered since there are not really any parental dependencies.
Example:
import { Routes, Route } from 'react-router-dom';
import Profile from './Profile';
import '../assets/styles/App.css';
import seedProfiles from '../seedProfiles';
import generateProfile from '../helpers/profileHelper';
const Profile = () => {
const { id } = useParams();
const profile = generateProfile(seedProfiles.find(profile => profile.id === id));
return ...;
}
import { Routes, Route } from 'react-router-dom';
import Profile from './Profile';
import '../assets/styles/App.css';
function App() {
return (
<Routes>
<Route path="/" element={<h1>PROFILES</h1>} />
<Route path="/profile/:id" element={<Profile />} />
</Routes>
);
}
Apologies for this seemingly repated question but it is really biting me. I am trying to use the new ReactRouter v6 private routes and I think the best practice for me would be to make a call to the server to make sure the token is valid and has not been revoked. I am being badly beatean by an infinite loop entering the private route with the typical error
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
my private route looks like this:
import React, {useCallback, useEffect, useState} from "react"
import {Outlet, Navigate} from "react-router-dom"
import {Auth} from "../../api/authApi"
const PrivateRoute = () => {
const [auth, setAuth] = useState(false)
const checkAuth = useCallback(() => {
let authApi = new Auth()
authApi.isAuth().then(isAuthorized => (
setAuth(isAuthorized)
))
}, [])
useEffect(() => {
checkAuth()
}, [checkAuth])
return auth ? <Outlet /> : <Navigate to="/login" />;
}
export default PrivateRoute
and my routes are:
function App() {
return (
<HashRouter>
<Routes>
<Route exact path="/login" element={<LoginPage />} />
<Route exact path="/register" element={<RegisterPage />} />
<Route path="/" element={
<PrivateRoute><HomePage /></PrivateRoute>
} />
</Routes>
</HashRouter>
)
}
export default App
I changed the PrivateRoute component to this:
import React, {useEffect, useRef} from "react"
import {Outlet, Navigate} from "react-router-dom"
import {Auth} from "../../api/authApi"
const PrivateRoute = () => {
let auth = useRef(false)
useEffect(() => {
const checkAuth = () => {
let authApi = new Auth()
authApi.isAuth().then(isAuthorized => (
auth.current = isAuthorized
))
}
checkAuth()
}, [])
return (auth.current ? <Outlet /> : <Navigate to="/login" />)
}
export default PrivateRoute
but still have the same issue. Is it something I am missing?
I tried it and found two issues:
The initial state auth is false so it will navigate to /login and unmount PrivateRoute at the first time, and then the component PrivateRoute(unmounted) got api response, I think maybe that's why you got warning. (I made an solution at the bottom)
The Route and Outlet components are used in the wrong way.
<Route path="/" element={
<PrivateRoute><HomePage /></PrivateRoute>
} />
should be modified to
<Route element={<PrivateRoute />}>
<Route path="/" element={<Home />} />
</Route>
The Code Sample :
My main App component keeps track of the user that is currently logged in via the firebase onAuthStateChanged callback, which I can then use to redirect the user to the /login route if the user object is null. This works fine, but if you navigate to a different route while on the login page, you don't get redirected back, which causes errors as other routes require you to be logged in to function properly. Here is the code:
export function App() {
const auth = firebase.auth();
const [user, setUser] = useState(null);
useEffect(()=>{
auth.onAuthStateChanged(()=> {
setUser(auth.currentUser);
})
}, []);
return (
<BrowserRouter>
<Switch>
<Route path="/login" exact component={LoginPage}/>
<Route path="/" exact component={HomePage}/>
{!user ? <Redirect to="/login"/> : null}
</Switch>
</BrowserRouter>
);
}
I've tried moving !user ? <Redirect to="/login"/> to the top of the Switch component, but that just makes it so you log out every time the page is refreshed. Any ideas on how to solve this? Thanks.
Why not recompose your Route element to have private routers and public routes? Private routes will be those requiring authentication and public once will not require it. When someone tries to access a private route without authentication, they will automatically be sent away.
Create an element called PrivateRoute and put your firebase auth inside it. Example:
const PrivateRoute = ({children, ...props}) => {
const auth = firebase.auth();
const [user, setUser] = useState(null);
useEffect(()=>{
auth.onAuthStateChanged(()=> {
setUser(auth.currentUser);
})
}, []);
return (
<Route {...props} render={() => {
return valid === null ?
<div>Some kind of loader/spinner here...</div>
:
user ?
children
:
<Redirect to='/login' />
}} />
)
}
Then in your App, use it like so:
return (
<BrowserRouter>
<Switch>
<PrivateRoute exact path="/">
<HomePage />
</PrivateRoute>
<Route exact path="/login" component={LoginPage} />
</Switch>
</BrowserRouter>
);
This will redirect anybody trying to access / to /login if they are not authenticated.
Later any route you create can be wrapped like this if it requires authentication.
I am using the following approach and it works fine (just copy my existing project that works):
import React, {useState, useEffect} from 'react'
import {BrowserRouter as Router, Switch, Route, Redirect} from "react-router-dom"
import {connect} from "react-redux"
import useAuth from './hooks/useAuth'
import styles from './styles.js'
import Landing from './components/Landing'
import Login from './components/Login'
import Logout from './components/Logout'
import Post from './components/Post'
import Users from './components/Users'
import User from './components/User'
import Signup from './components/Signup'
import Profile from './components/Profile'
import AddSocieta from './components/AddSocieta'
import Constructor from './components/Constructor'
const mapStateToProps = state => ({
...state
});
function ConnectedApp() {
const [dimension, setDimention] = useState({windowWidth: 0, windowHeight: 0})
const currentStyles = {
...styles,
showFooterMenuText: styles.showFooterMenuText(dimension),
showSidebar: styles.showSidebar(dimension),
topMenuCollapsed: styles.topMenuCollapsed(dimension),
topMenuHeight: styles.topMenuHeight(dimension),
paddingLeftRight: styles.paddingLeftRight(dimension),
fullScreenMenuFontSize: styles.fullScreenMenuFontSize(dimension),
showSubLogoText: styles.showSubLogoText(dimension),
roundedImageSize: styles.roundedImageSize(dimension)
};
const [auth, profile] = useAuth()
const [isLoggedIn, setIsLoggedIn] = useState(false)
useEffect(() => {
if (auth && auth.uid) {
setIsLoggedIn(true)
} else {
setIsLoggedIn(false)
}
updateDimensions();
window.addEventListener("resize", updateDimensions);
return function cleanup() {
window.removeEventListener("resize", updateDimensions);
}
}, [auth, profile]);
function updateDimensions() {
let windowWidth = typeof window !== "undefined"
? window.innerWidth
: 0;
let windowHeight = typeof window !== "undefined"
? window.innerHeight
: 0;
setDimention({windowWidth, windowHeight});
}
return (<Router>
<Redirect to="/app/gare"/>
<div className="App">
<Switch>
<Route path="/constructor"><Constructor styles={currentStyles}/></Route>
<Route path="/post"><Post/></Route>
<Route path="/login"><Login styles={currentStyles}/></Route>
<Route path="/logout"><Logout styles={currentStyles}/></Route>
<Route path="/users"><Users styles={currentStyles}/></Route>
<Route path="/user/:uid"><User styles={currentStyles}/></Route>
<Route path="/app"><Landing styles={currentStyles}/></Route>
<Route path="/signup" render={isLoggedIn
? () => <Redirect to="/app/gare"/>
: () => <Signup styles={currentStyles}/>}/>
<Route path="/profile" render={isLoggedIn
? () => <Profile styles={currentStyles}/>
: () => <Redirect to="/login"/>}/>
<Route path="/add-societa"><AddSocieta styles={currentStyles}/></Route>
</Switch>
</div>
</Router>);
}
const App = connect(mapStateToProps)(ConnectedApp)
export default App;
I can't seem to get my functional components to pass a variable like I expect. I've tried passing props, didn't work, I'm also not sure if the syntax is the same with purse functional components. Any tips?
app.js:
const [showRecommender, setRecommenderVisible] = React.useState(true);
<Switch>
<Route
path='/'
render={() => <LandingPage showRecommender={showRecommender} />}
//ALSO TRIED: render={(props) => <LandingPage {...props} showRecommender={showRecommender} />}
/>
</Switch>
LandingPage.js:
const LandingPage = ({showRecommender}) => {
console.log("showRecommender val from landingPage:", showRecommender); //getting undefined????
It works fine. Can you please match your code with the code below and let me know if your issue is solved.
App.js
import React from "react";
import { Switch, Route, BrowserRouter as Router } from "react-router-dom";
import LandingPage from "./components/LandingPage";
function App() {
const [showRecommender, setRecommenderVisible] = React.useState(true);
return (
<Router>
<Switch>
<Route
path="/"
render={() => <LandingPage showRecommender={showRecommender} />}
/>
</Switch>
</Router>
);
}
export default App;
LandingPage.js
import React from "react";
const LandingPage = ({ showRecommender }) => {
console.log("showRecommender val from landingPage:", showRecommender);
return <div>landing page</div>;
};
export default LandingPage;
Browser Console
showRecommender val from landingPage: true LandingPage.js:4
I still had my original code 'commented' out, or so I thought...it was still executing the commented out line. Once removed, it worked as expected... bonehead move, sorry all.
<Switch>
// <Route path="/" exact component={LandingPage}/>
<Route
path='/'
render={() => <LandingPage showRecommenderVal={showRecommender} />}
/>
</Switch>
I've been trying to lazy load routes in React using React.lazy and Suspense. But some components are loading regardless of the current route, exactly: Feed, Profile and Settings.
Notice I don't actually want to lazy load Components like MenuAppBar and SnackAlert but if I import them normally and remove their Suspense, code-splitting straight doesn't even work and everything loads and the whole app is just a single chunk.
import {createMuiTheme, MuiThemeProvider} from "#material-ui/core";
import {yellow} from "#material-ui/core/colors";
import CssBaseline from "#material-ui/core/CssBaseline";
import axios from "axios";
import React, {lazy, Suspense, useEffect, useState} from "react";
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
import "./css/feed.css";
import "./css/style.css";
const Feed = lazy(() => import("./routes/Feed"));
const Profile = lazy(() => import("./routes/Profile"));
const Home = lazy(() => import("./routes/Home"));
const Settings = lazy(() => import("./routes/Settings"));
const NotFound = lazy(() => import("./routes/NotFound"));
const MenuAppBar = lazy(() => import("./components/MenuAppBar"));
const SnackAlert = lazy(() => import("./components/SnackAlert"));
const App: React.FC = () => {
const [isLogged, setIsLogged] = useState(localStorage.getItem("token") ? true : false);
const [user, setUser] = useState<User>(
isLogged ? JSON.parse(localStorage.getItem("userInfo") as string) : {admin: false}
);
const [openError, setOpenError] = useState<boolean>(false);
const [errorMsg, setErrorMsg] = useState<string>("");
const [severity, setSeverity] = useState<Severity>(undefined);
const [pwa, setPwa] = useState<any>(null);
const [showBtn, setShowBtn] = useState<boolean>(false);
const [isLight, setIsLight] = useState<boolean>(
(JSON.parse(localStorage.getItem("theme") as string) as boolean) ? true : false
);
const theme: customTheme = {
darkTheme: {
palette: {
type: "dark",
primary: {
main: yellow[600]
}
}
},
lightTheme: {
palette: {
type: "light",
primary: {
main: yellow[700]
}
}
}
};
window.addEventListener("beforeinstallprompt", (event) => {
event.preventDefault();
setPwa(event);
setShowBtn(true);
});
window.addEventListener("appinstalled", (e) => {
setShowBtn(false);
setErrorMsg("App installed!");
setSeverity("success");
setOpenError(true);
});
const handleClick = () => {
if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
setErrorMsg(`Please, open the share menu and select "Add to Home Screen"`);
setSeverity("info");
setOpenError(true);
} else {
if (pwa) {
pwa.prompt();
pwa.userChoice.then((choiceResult: {outcome: "accepted" | "refused"}) => {
if (choiceResult.outcome === "accepted") {
setErrorMsg("App downloading in the background..");
setSeverity("info");
setOpenError(true);
}
setPwa(null);
});
}
}
};
useEffect(() => {
const token: string | null = localStorage.getItem("token");
let userInfo: User = JSON.parse(localStorage.getItem("userInfo") as string);
if (userInfo && token && !userInfo.admin) {
setUser(userInfo);
setIsLogged(true);
}
if (isLogged) {
axios
.get("/api/auth/user", {
headers: {
"x-auth-token": `${token}`
}
})
.then((res) => {
if (!userInfo || !token) {
setUser(res.data as User);
}
localStorage.setItem(`userInfo`, JSON.stringify(res.data as User));
setIsLogged(true);
})
.catch((err) => {
if (err) {
setIsLogged(false);
}
});
} else {
localStorage.removeItem("token");
localStorage.removeItem("userInfo");
}
}, [isLogged]);
return (
<MuiThemeProvider theme={isLight ? createMuiTheme(theme.lightTheme) : createMuiTheme(theme.darkTheme)}>
<CssBaseline />
<Router>
<Suspense fallback={<div></div>}>
<Route
path="/"
render={() => (
<>
<MenuAppBar
isLogged={isLogged}
setIsLogged={setIsLogged}
user={user}
setUser={setUser}
isLight={isLight}
setIsLight={setIsLight}
/>
<SnackAlert severity={severity} errorMsg={errorMsg} setOpenError={setOpenError} openError={openError} />
</>
)}
/>
</Suspense>
<Suspense fallback={<div></div>}>
<Switch>
<Route exact path="/" render={() => <Home />} />
<Route exact path="/profile/:id" render={() => <Profile />} />
<Route exact path="/feed" render={() => <Feed isLogged={isLogged} user={user} />} />
<Route
exact
path="/settings"
render={() => (
<Settings isLight={isLight} setIsLight={setIsLight} handleClick={handleClick} showBtn={showBtn} />
)}
/>
<Route render={() => <NotFound />} />
</Switch>
</Suspense>
</Router>
</MuiThemeProvider>
);
};
export default App;
You are wrapping your entire Switch in a single Suspense, so all components will be lazily loaded at the same time. You probably only want each to be fetched/loaded when the specific route is rendered the first time.
<Switch>
<Route
exact
path="/"
render={props => (
<Suspense fallback={<div>Loading...<div>}>
<Home {...props} />
</Suspense>
)}
/>
<Route
exact
path="/profile/:id"
render={props => (
<Suspense fallback={<div>Loading...<div>}>
<Profile {...props} />
</Suspense>
)}
/>
<Route
exact
path="/feed"
render={() => (
<Suspense fallback={<div>Loading...<div>}>
<Feed isLogged={isLogged} user={user} {...props} />
</Suspense>
)}
/>
<Route
exact
path="/settings"
render={() => (
<Suspense fallback={<div>Loading...<div>}>
<Settings
isLight={isLight}
setIsLight={setIsLight}
handleClick={handleClick}
showBtn={showBtn}
{...props}
/>
</Suspense>
)}
/>
<Route
render={() => <NotFound />}
/>
</Switch>
There is a lot of repetition here, so it is practical to factor out the suspense into a HOC.
const withSuspense = (WrappedComponent, fallback) => props => (
<Suspense fallback={fallback}>
<WrappedComponent {...props} />
</Suspense>
);
You can either decorate each perspective default export, i.e.
export default withSuspense(Home, <div>Loading...<div>);
App.js
...
<Switch>
<Route exact path="/" render={props => <Home {...props} />} />
or decorate them in your App
const HomeWithSuspense = withSuspense(Home, <div>Loading...<div>);
...
<Switch>
<Route
exact
path="/"
render={props => <HomeWithSuspense {...props} />}
/>
...
</Switch>
In case someone is having the same problem, the actual problem was that some of the components had other components within them which weren't exported as default and that's why they weren't being lazy-loaded.
So if you're having the same problem, you should check the import tree of the component you're trying to lazy-load and make sure every component in this tree is exported as default.
For more information refer to the named exports section in the react docs.
Thanks everyone for your help!
That should work, I would look other problems, like build scripts, or some other piece of code using those same bundles. (e.g. the inheritance thing you mentioned in comments)
Please try this once if above are worked
import React, { Suspense, lazy } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
const Home = lazy(() => import("./components/Home"));
const About = lazy(() => import("./components/About"));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route exact path='/' element={<Home/>}/>
<Route exact path='/about' element={<About/>}/>
</Routes>
</Suspense>
</Router>
);
export default App
There's no need for the whole tree that you're trying to lazy load to have default imports and exports. The component tree with its unique dependencies will be bundled into lazy chunk by default.
For eg.
Component.js
import { x, y } from z
.....
export default Component
main.js
const Component = React.lazy(() => import('Component.js')
Here the main.js chunk will not include code any code from z or
any of the code from Component.js and its unique dependencies
https://webpack.js.org/guides/code-splitting/#dynamic-imports
https://create-react-app.dev/docs/code-splitting/#appjs