Testing a router component and when I call the screen.debug() in a test after rendering, the DOM output is not what I expected. Why?
Test:
import { render, userEvent as user, screen, getByRole } from '#testing-library/react'
import { Router } from 'react-router-dom'
import { createMemoryHistory } from 'history'
import AppRouter from '../router'
test('Renders AppRouter', () => {
const history = createMemoryHistory({ initialEntries: ['/post'] })
render(() => (
<Router history={history}>
<AppRouter />
</Router>
))
screen.debug()
})
Component:
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'
import { useState } from 'react'
import useLocalStorage from './hooks/useLocalStorage'
import * as Constants from './constants'
import Header from './layout/header/header'
import MainPage from './pages/mainPage/mainPage'
import PostPage from './pages/postPage/postPage'
import UserPage from './pages/userPage/userPage'
import LoginPage from './pages/loginPage/loginPage'
import SignupPage from './pages/signupPage/signupPage'
import NewPage from './pages/newPage/newPage'
import FeedbackPage from './pages/feedbackPage/feedbackPage'
import AdminPage from './pages/adminPage/adminPage'
import SettingPage from './pages/settingPage/settingPage'
import { WebContext } from './context/WebContext'
import Favicon from 'react-favicon'
const AppRouter = () => {
const [adminCode, setAdminCode] = useLocalStorage('admin', '')
const [isMenuOpen, setIsMenuOpen] = useState(false)
const [page, setPage] = useState(Constants.Page.Home)
return (
<BrowserRouter>
<div role="hello">
<Favicon url={require('../public/favicon.ico')} />
<WebContext.Provider
value={{
isMenuOpen,
setIsMenuOpen,
page,
setPage,
adminCode,
setAdminCode,
}}
>
<Header />
<h1>
hello
</h1>
<Switch>
<Route component={MainPage} path="/" exact={true} />
<Route component={PostPage} path="/post/:id" />
<Route component={UserPage} path="/user" />
<Route component={LoginPage} path="/login" />
<Route component={SignupPage} path="/signup" />
<Route component={NewPage} path="/new" />
<Route component={FeedbackPage} path="/feedback" />
<Route component={AdminPage} path="/admin" />
<Route component={SettingPage} path="/setting" />
<Route component={() => <Redirect to="/" />} />
</Switch>
</WebContext.Provider>
</div>
</BrowserRouter>
)
}
export default AppRouter
Code-Trace:
EDIT
Error when not passing in a function to render:
Favicon error:
You are passing a function to the test render function when it's expecting JSX.
Remove the function definition and just pass the Router and AppRouter as JSX.
Example:
test('Renders AppRouter', () => {
const history = createMemoryHistory({ initialEntries: ['/post'] });
render(
<Router history={history}>
<AppRouter />
</Router>
);
screen.debug();
});
Related
The problem is that when I start using React.lazy, input styles are not rendering correctly. With lazy I get default Antd input styles, not mine. What's the problem and how I can fix it? You can see my code and its results in the pictures below. Thanks!
This picture shows styles that this component should render
This picture shows what styles are applied when using lazy
Code with lazy
import { lazy, Suspense } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { useAppSelector } from '../../../hooks/redux-hooks';
import { selectCurrentUser } from '../../../store/slices/user/userSelectors';
import { Spinner } from '../../common/Spinner/Spinner';
const HomePage = lazy(() => import('../../../pages/HomePage'));
const ShopPage = lazy(() => import('../../../pages/ShopPage'));
const CheckoutPage = lazy(() => import('../../../pages/CheckoutPage'));
const AuthPage = lazy(() => import('../../../pages/AuthPage'));
export const AppRoutes = () => {
const currentUser = useAppSelector(selectCurrentUser);
return (
<Suspense fallback={<Spinner />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="shop/*" element={<ShopPage />} />
<Route path="checkout" element={<CheckoutPage />} />
<Route
path="auth"
element={currentUser ? <Navigate to="/" /> : <AuthPage />}
/>
</Routes>
</Suspense>
);
};
Code without lazy
import { Routes, Route, Navigate } from 'react-router-dom';
import { useAppSelector } from '../../../hooks/redux-hooks';
import { selectCurrentUser } from '../../../store/slices/user/userSelectors';
import HomePage from '../../../pages/HomePage';
import ShopPage from '../../../pages/ShopPage';
import AuthPage from '../../../pages/AuthPage';
import CheckoutPage from '../../../pages/CheckoutPage';
export const AppRoutes = () => {
const currentUser = useAppSelector(selectCurrentUser);
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="shop/*" element={<ShopPage />} />
<Route path="checkout" element={<CheckoutPage />} />
<Route
path="auth"
element={currentUser ? <Navigate to="/" /> : <AuthPage />}
/>
</Routes>
);
};
I was developing an App where I use Firebase as an Authentication system of the App, an when I try to implement the routes of the app, I start to get the above title error.
I'm using withRouter funtion, to encapsulate my App component.
So the code of my App.tsx file is the following:
import React, { FC, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
Route,
Switch,
useHistory,
withRouter,
BrowserRouter as Router,
} from "react-router-dom";
import "./App.css";
import Header from "./components/sections/Header";
import SignUp from "./components/pages/SignUp";
import SignIn from "./components/pages/SignIn";
import ForgotPassword from "./components/pages/ForgotPassword";
import Homepage from "./components/pages/Homepage";
import Dashboard from "./components/pages/Dashboard";
import PrivateRoute from "./components/auth/PrivateRoute";
import PublicRoute from "./components/auth/PublicRoute";
import Loader from "./components/UI/Loader";
import firebase from "./firebase/config";
import {
getUserById,
setLoading,
setNeedVerification,
} from "./store/actions/authActions";
import { RootState } from "./store";
const App: FC = () => {
const dispatch = useDispatch();
const { loading } = useSelector((state: RootState) => state.auth);
let history = useHistory();
// Check if user exists
// App.tsx
useEffect(() => {
dispatch(setLoading(true));
const unsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
await dispatch(getUserById(user.uid));
if (user.emailVerified) {
history.push("/homepage");
} else {
history.push("/signin");
dispatch(setNeedVerification());
}
}
dispatch(setLoading(false));
});
return () => {
unsubscribe();
};
});
if (loading) {
return <Loader />;
}
function Routes() {
return (
<Switch>
<PublicRoute path="/signup" component={SignUp} exact />
<PublicRoute path="/signin" component={SignIn} exact />
<PublicRoute path="/forgot-password" component={ForgotPassword} exact />
<PrivateRoute path="/dashboard" component={Dashboard} exact />
<PublicRoute path="/homepage" component={Homepage} exact />
</Switch>
);
}
return (
<Router>
<Header />
<Routes />
</Router>
);
};
export default withRouter(App);
`So I think that have to be realated with the configuration of Route library into the main component of the app.
What I missing??
Take thankss in advance !
App is the component rendering the Router so App itself can't use anything that requires a Router context higher up in the React tree.
The solution is to move the Router higher in the React tree, i.e. wrap App in the Router. Once App is being rendered by a Router and since you are using the useHistory hook there will be no need to decorate with the withRouter HOC.
App
const App: FC = () => {
...
function Routes() {
return (
<Switch>
<PublicRoute path="/signup" component={SignUp} exact />
<PublicRoute path="/signin" component={SignIn} exact />
<PublicRoute path="/forgot-password" component={ForgotPassword} exact />
<PrivateRoute path="/dashboard" component={Dashboard} exact />
<PublicRoute path="/homepage" component={Homepage} exact />
</Switch>
);
}
return (
<>
<Header />
<Routes />
</>
);
};
export default App;
index where App is rendered.
import { BrowserRouter as Router } from "react-router-dom";
import App from '../App';
...
<Router>
<App />
</Router>
I am trying to redirect from saga after a password change, and history.push() is just updating the URL not loading the component
Hisotry.js
import { createBrowserHistory as history } from "history";
export default history({
bforceRefresh: true,
});
App.js
import React from "react";
import { BrowserRouter } from "react-router-dom";
import history from "../../helpers/history";
import Navigation from "../Navigation";
import AppAuthRouter from "../Navigation/routes";
const App = () => (
<BrowserRouter history={history}>
<Navigation app_auth_router={AppAuthRouter} />
</BrowserRouter>
);
export default App;
AppAuthRouter.js
import * as ROUTES from "../../constants/routes";
import ProtectedRoute from "./protectedroute";
const AppAuthRouter = () => (
<Switch>
<ProtectedRoute path="/admin" component={AdminPage} />
<ProtectedRoute
path="/updatepwd"
component={PasswordChangePage}
/>
<ProtectedRoute path="/welcome" component={WelcomePage} />
<Route path={"/signup} component={SignUpPage} />
<Route path={"/signin} component={SignInPage} />
<Redirect path="*" to={{ pathname:"/signin"}} />
</Switch>
);
export default AppAuthRouter;
Saga.js
import history from "../helpers/history";
function* passwordChangeSaga({ creds }) {
try {
yield put(alertSuccess("Password has been changed successfully"));
history.push("/welcome");
} catch (gerror) {
const error = googleError2Error(gerror);
}
}
I'm trying to track each page but google only registers a view each time I click the refresh button. Not when iv'e routed to a new path. Anyone got an Idea on how to make this work?
import React, {useEffect} from 'react';
import { BrowserRouter, Switch, Route } from "react-router-dom";
import OurWork from "./ourWork/ourWork"
import Home from "./home/Home"
import Packages from "./packages/Packages"
import Contacts from "./contact/Contact"
import ReactGA from 'react-ga'
import createHistory from 'history/createBrowserHistory'
const Routes = () => {
const {
ContactPage
} = Contacts();
const history = createHistory()
history.listen(location => {
ReactGA.set({ page: location.pathname });
ReactGA.pageview(location.pathname);
});
useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search)
}, [history])
return(
<BrowserRouter history={history}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/ourwork" exact component={OurWork} />
<Route path="/packages" exact component={Packages} />
<Route path="/Contact" exact component={ContactPage} />
</Switch>
</BrowserRouter>
)
}
export default Routes;
Looks like the same issue which is described here. Try to change <BrowserRouter history={history}> to <Router history={history}> and import Router from the "react-router-dom", like this:
import React, {useEffect} from 'react';
import { Router, Switch, Route } from "react-router-dom";
import OurWork from "./ourWork/ourWork"
import Home from "./home/Home"
import Packages from "./packages/Packages"
import Contacts from "./contact/Contact"
import ReactGA from 'react-ga'
import createHistory from 'history/createBrowserHistory'
const Routes = () => {
const {
ContactPage
} = Contacts();
const history = createHistory()
history.listen(location => {
ReactGA.set({ page: location.pathname });
ReactGA.pageview(location.pathname);
});
useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search)
}, [history])
return(
<Router history={history}>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/ourwork" exact component={OurWork} />
<Route path="/packages" exact component={Packages} />
<Route path="/Contact" exact component={ContactPage} />
</Switch>
</Router>
)
}
export default Routes;
I have routes with single and multiple components.I am using authguard to restrict access to some routes.How can I apply the authguard to the routes having multiple components.
routes.js
import { BrowserRouter as Router,Route} from 'react-router-dom';
import React from 'react';
import { FirstConnectedComponent,SecondConnectedComponent } from './App.js';
import Header from './components/header.js';
import Footer from './components/footer.js';
import Login from './components/login.js';
import UserReg from './components/registration.js';
import Home from './components/home';
import requireAuth from './components/authentication';
import PrivateRoute from './components/privateroutes';
const routes=() => (
<Router>
<div>
<Header />
<Route exact path="/" render={ props => <div><FirstConnectedComponent /><SecondConnectedComponent /></div>} />
<Route path="/login" component={PrivateRoute(Login) } />
<Route path="/register" component={ UserReg } />
<Route path="/home" component={ requireAuth(Home)} />
<Footer />
</div>
</Router>
)
export default routes;
In the above code I already applying authguard to routes having single components.But I don't know how to be applied into routes having multiple components.
privateroute
import { connect } from 'react-redux';
import React from 'react';
import { withRouter } from 'react-router';
export default function PrivateRoute(Component) {
class AuthenticatedComponent extends React.Component {
componentWillMount() {
console.log(this.props.loginStatus);
this.checkAuth();
}
checkAuth() {
if (this.props.loginStatus==1) {
this.props.history.push(`/home`);
}
}
render() {
return this.props.loginStatus!=1
? <Component { ...this.props } />
: null;
}
}
function mapStateProps(state) {
return {
loginStatus:state.loginDetails.status
}
}
return connect(mapStateProps)(withRouter(AuthenticatedComponent));
}
Instead of using render prop use the component prop as you have used the same in PrivateRoute and wrap the render function with PrivateRoute HOC like
const routes=() => (
<Router>
<div>
<Header />
<Route exact path="/" component={PrivateRoute(props => <div><FirstConnectedComponent /><SecondConnectedComponent /></div>)} />
<Route path="/login" component={PrivateRoute(Login) } />
<Route path="/register" component={ UserReg } />
<Route path="/home" component={ requireAuth(Home)} />
<Footer />
</div>
</Router>
)
Solution for v6 newer version of react-router-dom
If useNavigate not working, use useHistory
App.js
import React from 'react'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import AuthGuard from "./Routes/AuthGuard";
function App() {
return (
<div className='App'>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/contact' element={<Contact />} />
<Route path='/guest-page' element={<AuthGuard isGuest={true}><h1>Guest Page</h1></AuthGuard>} />
<Route path='/protected-page' element={<AuthGuard requireToken={true}><h1>ProtectedPage</h1></AuthGuard>} />
</Routes>
</Router>
</div>
);
}
export default App;
AuthGuard.js
import { Route, useNavigate } from "react-router-dom";
import { useLayoutEffect } from "react";
const ProtectedRoute = ({requireToken, guest, children, ...rest}) => {
console.log(requireToken, guest, children, rest);
const navigate = useNavigate();
const hasToken = false;
let hasAccess = (!requireToken || (requireToken && hasToken));
let navigateTo = '/?login-rquired=true';
if(guest) {
hasAccess = !hasToken;
navigateTo = '/?guest=true';
console.log("Guest > hasAccess: " + hasAccess)
}
if(requireToken){
console.log("requireToken", requireToken)
}
useLayoutEffect(()=>{
if (!hasAccess) {
console.log("Not allowed");
navigate(navigateTo);
}
},[])
return (
<>{ hasAccess ? children : "Login required" }</>
)
}
export default ProtectedRoute;