I am simply trying to retrieve the params from a dynamic route ("/articles/:articleId") in React using react-router-dom v 6.8
"react-router-dom": "^6.8.0",
"#types/react-router-dom": "^5.3.3",
I tried to follow the Docs, but I must be missing something here.
App.tsx
function App() {
return (
<Router>
<Routes>
<Route path={"/articles/:articleId"} element={Article()}/>
<Route path={"/articles"} element={Articles()}/>
<Route path={"/404"} element={NotFoundPage()}/>
<Route path={"/"} element={HomePage()}/>
<Route path={"*"} element={NotFoundPage()}/>
</Routes>
</Router>
);
}
Article.tsx
import { useParams } from "react-router-dom";
import { useEffect } from "react";
const Article = () => {
const { articleId } = useParams()
const params = useParams()
useEffect(() => {
console.log('even inside useEffect', params);
}, [])
console.log("Article ", useParams(), articleId)
return (
<div>ID: {articleId}</div>
)
}
export default Article
Output
Couple of issues:
You are directly calling the React functions instead of rendering them as JSX. The components aren't called as part of the normal React component lifecycle, so the params are not populated.
react-router-dom#6 is written entirely in Typescript, so there is no need for any external typings, especially those from react-router-dom#5 which will be woefully incorrect for RRDv6.
The Route component's element prop takes a React.ReactNode, a.k.a. JSX, value.
function App() {
return (
<Router>
<Routes>
<Route path="/articles/:articleId" element={<Article />} />
<Route path="/articles" element={<Articles />} />
<Route path="/404" element={<NotFoundPage />} />
<Route path="/" element={<HomePage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Router>
);
}
Uninstall the unnecessary dependency.
npm uninstall --save #types/react-router-dom
Related
I have a component that I am using in React Router v6 for managing private routes, that does some checks on an auth token, and will either render the Outlet component or will redirect to a login page.
I have -
import { Outlet } from 'react-router-dom';
export const CheckAuth = (props) => {
const valid = ...;
if (!valid) {
window.location.replace(loginUrl);
return null;
}
return <Outlet />;
};
and using it like -
<Route element={<CheckAuth token={authToken} />}>
// ... private routes ...
</Route>
I can mock out window.location.replace with Jest
delete window.location;
window.location = { replace: jest.fn() };
...
render(<CheckAuth token={token} />)
expect(window.location.replace).toHaveBeenCalledWith(loginUrl);
but how can I test the Outlet component using Testing Library?
If it helps anyone, I ended up just wrapping the components in the test with a react router components, and passed a dummy component as a child to Route and asserted that some fake text in that component was or was not rendered
Outside the test block -
const FakeComponent = () => <div>fake text</div>;
and for a failure scenario, where the outlet should not render -
render(
<MemoryRouter initialEntries={['/']}>
<Routes>
<Route element={<CheckAuth />}>
<Route path="/" element={<FakeComponent />} />
</Route>
</Routes>
</MemoryRouter>
);
expect(screen.queryByText('fake text')).not.toBeInTheDocument();
and for a success scenario, assert that the text is present -
render(
<MemoryRouter initialEntries={['/']}>
<Routes>
<Route element={<CheckAuth token={correctToken}/>}>
<Route path="/" element={<FakeComponent />} />
</Route>
</Routes>
</MemoryRouter>
);
expect(screen.queryByText('fake text')).toBeInTheDocument();
I've looked online and most questions uses a different version of react-router-dom than what I'm using making the answer hard to find. What I want to do is simple, let's say a user is logged in then I wouldn't want that user to access the "sign-up" page and redirect them to the "home" page.
Here's the code I'm using that isn't working.
import { useAuth } from '../contexts/AuthContext';
import "firebase/auth";
import {Route, Navigate} from 'react-router-dom'
function AuthRoute ({element: Element, ...rest}) {
const { currentUser } = useAuth()
return (
<Route
{...rest}
render={props => {
return !currentUser ? <Element {...props} /> : <Navigate to="/" />
}}
></Route>
)
}
export default AuthRoute;
Here's how it's being called in App.js
return (
<Router>
<div className = "App">
<AuthProvider>
<Routes>
<Route path="/" element={<Home/>}/>
<AuthRoute exact path = "/sign_up" element= {SignUp} />
<Route exact path="/about" element={<About/>}/>
<Route exact path="/login" element={<SignIn/>}/>
</Routes>
</AuthProvider>
</div>
</Router>
);
It routes to sign_up but it doesn't matter if the user exists or not.
I don't know if you still need help with this but I found how to do it,
first your home route should be an exact path, then in AuthRoute() component you can do this:
export default function AuthRoute ({chidren}) {
const { currentUser } = useAuth()
if (currentUser){
return (children)
}else{
return <Navigate to='/sign_up' replace/>
}
}
then in App.js:
<AuthRoute exact path = "/sign_up" element= {SignUp} /> // should be :
<Route path='/sign_up' element={<AuthRoute> <Home/> </AuthRoute>}
</Route>
hope this can help anyone struggling with react-router v6 (like I did)
I currently have all the Routes in my app defined in App.js. Would like to be able to pass state (as props) from the Alignment component down to the GPfSOA component.
function App() {
return (
<Router>
<div className="App">
<Nav />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" exact component={About} />
<Route path="/alignments" exact component={Alignments} />
<Route path="/alignments/:id" exact component={Alignment} />
<Route path="/alignments/segmentinfo/:id" exact component={Segments} />
<Route path="/alignments/segmentinfo/:id/:segid" exact component={Segment} />
<Route path="/alignments/getpoint/:id" exact component={GPfSOA} />
<Route path="/alignments/getstatoff/:id" exact component={GSOfPA} />
<Route path="/alignments/getalsfromxy/:x/:y" exact component={AlignList} />
<Route path="/alignments/getsegsfromxy/:x/:y" exact component={SegmentList} />
<Route path="/alignments/post/create" exact component={AddAlignment} />
<Route path="/alignments/put/update/:id" exact component={EditAlignment} />
<Route path="/alignments/ptso/list" exact component={TogglePoints} />
<Route path="/alignments/ptso/list/:ptid" exact component={Point} />
<Route path="/" render={() => <div>404</div>} />
</Switch>
</div>
</Router>
);
}
The order from parent on down to the greatest grandchild would be App > Alignments > Alignment > GPfSOA. Trying to pass item.alignment (the alignment's name) from the Alignment component down (or over) to the GPfSOA component so that it can be rendered there. item.alignment is a property of the Alignmnet component's state.
Do I need to set these up as nested routes in order to accomplish this (a.k.a. cut and paste all the Routes from App.js that are children of the Alignment component and paste them into the Alignment component)?
Having a hard time understanding how to define a particular component as being a parent and another component as being a child of that component. All the examples I see assume you want to pass props from App.js down to some other component. Looking for examples with React Hooks and React Router in play (functions rather than classes) where you're passing props from a component 'below' App.js down to another component that's further down in the hierarchy. Hope this makes sense.
Found lots of examples such as this one for 'passing function as a render props in Route component' (supposedly the recommended way to do this)
const PropsPage = () => {
return (
<h3>Props Page</h3>
);
};
const App = () => {
return (
<section className="App">
<Router>
...
<Link to="/404-not-found">404</Link>
<Link to="/props-through-render">Props through render</Link>
<Switch>
...
<Route exact path="/props-through-render" render={(props) => <PropsPage {...props} title={`Props through render`} />} />
<Route component={NoMatchPage} />
</Switch>
</Router>
about with browser reload
</section>
);
};
export default App;
But like I stated before, this example and every other one I've found assume you want to pass props from App.js down to another component.
Your issue can be handle with creating Alignment context
import React, { createContext, useState } from "react";
const AlignmentContext = createContext();
const AlignmentContextProvider = ({ children }) => {
const [num, setNum] = useState(1);
};
return (
<AlignmentContext.Provider value={{ num, setNum }}>
{children}
</AlignmentContext.Provider>
);
};
export { AlignmentContext, AlignmentContextProvider };
now wrap your routes needed to be in same context with AlignmentContextProvider
import { AlignmentContextProvider } from 'pathto/context'
<AlignmentContextProvider>
<Route path="/alignments/:id" exact component={Alignment} />
<Route path="/alignments/segmentinfo/:id" exact component={Segments} />
<Route path="/alignments/segmentinfo/:id/:segid" exact component={Segment} />
<Route path="/alignments/getpoint/:id" exact component={GPfSOA} />
</AlignmentContextProvider>
and use useContext hooks for reach values
import React, { useContext } from "react";
import { AlignmentContext } from 'pathto/context';
const GPfSOA = () => {
const { num, setNum } = useContext(AlignmentContext);
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>
Is there a way to nest routes in React Router v4?
This works:
<Router basename='/app'>
<main>
<Route path='/' component={AppBar} />
<Route path='/customers' component={Customers} />
</main>
</Router>
This does not:
<Router basename='/app'>
<Route path='/' component={AppBar}>
<Route path='/customers' component={Customers} />
</Route>
</Router>
Customers Component:
import React, { Component, PropTypes } from 'react'
import styled from 'styled-components'
export default class Customers extends Component {
render () {
return (
<Container>
<h1>Customers</h1>
</Container>
)
}
}
const Container = styled.section`
height: 100%;
padding: 15px;
overflow: auto;
`
Best pattern I have found so far.
// main app
<div>
// not setting a path prop, makes this always render
<Route component={AppShell}/>
<Switch>
<Route exact path="/" component={Login}/>
<Route path="/dashboard" component={AsyncDashboard(userAgent)}/>
<Route component={NoMatch}/>
</Switch>
</div>
I can just keep nesting this inside a component and everything works nice including hmr(If using webpack, dont forget to set output.publicPath to "/")
// dashboard component
<div>
// the same way as before, not setting a path prop
// makes it render on every /dashboard/** request
<Route component={DashboardTAB}/>
<Switch>
// longer path (with same root) than others first
<Route path="/dashboard/graphs/longerpath" component={GraphForm}/>
<Route path="/dashboard/graphs" component={Graphs}/>
<Route path="/dashboard/workers" component={List}/>
<Route path="/dashboard/insert" component={InsertComponent}/>
</Switch>
</div>
I adapted this from the docs, seem to work so far. Probably missing something obvious, and yes it is not the v4 way but we need all the routes defined in one place.
function RouteNest(props){ return (
<Route exact={props.exact} path={props.path} render={ p => <props.component {...p} children={props.children}/> } />
)}
export const MainRoutes = props =>
<div className='content layout'>
<Route exact path="/" component={Landing}/>
<Route path={'/contact'} component={Contact}/>
<RouteNest path={'/thing'} component={CompoWithSub}>
<RouteNest path={'/thing/suba'} component={SubComponentA}/>
<RouteNest path={'/thing/subb'} component={SubComponentB}/>
</RouteNest>
</div>
export const CompoWithSub = props => <div>{props.children)</div>
You're AppBar component is in charge of rendering Customers. For customers to be called, you have to render the children of AppBar. Anything directly nested under AppBar is a child of AppBar.
import React from 'react';
const AppBar = ({ children }) => (
<div>
<header>
<h1> stuff </h1>
</header>
{children}
</div>
);
export default AppBar
Please note that only AppBar will render when you visit "/". AppBar and Customers will render when you visit "/customers".
If someone wants to have nested routes without typing prefix of wrapper route I've created something like this in TSX:
Imports:
import * as React from 'react';
import { Route, RouteComponentProps, RouteProps, Switch } from 'react-router-dom';
import Index from 'views/index';
import Login from 'views/login';
import NoMatch from 'views/no-match';
Interfaces:
interface INestedRoutes {
nested?: string;
}
interface INestedRoute extends RouteProps, INestedRoutes {}
NestedRoute and NestedRoutes wrapper:
class NestedRoutes extends React.Component<INestedRoutes> {
public render() {
const childrenWithProps = React.Children.map(this.props.children, (child) => {
return React.cloneElement(
child as React.ReactElement<any>, { nested: this.props.nested },
);
})
return childrenWithProps;
}
}
const NestedRoute: React.SFC<INestedRoute> = (props: INestedRoute) => {
return <Route path={`${props.nested}${props.path}`} component={props.component} />;
};
And routes with wrapper:
const MultiLanguage: React.SFC<RouteComponentProps<any>> = (props: RouteComponentProps<any>) => {
return (
<NestedRoutes nested={props.match.path} >
<NestedRoute path="/test" component={Login} />
<NestedRoute path="/no-match" component={NoMatch} />
</NestedRoutes>
);
};
export default (
<Switch>
<Route path="/:language" component={MultiLanguage}/>
<Route exact={true} path="/" component={Index} />
<Route path="/login" component={Login} />
<Route component={NoMatch} />
</Switch>
);
For nested routes there is a very simple way which i using.
Example main router is be like that
<Router history={history}>
<Switch >
<Route path="/" component={Home}></Route>
</Switch>
</Router>
Inside Home component using Nested Routing be like:
<div className="App">
<Navbar title="Home" links = { NavbarLinks }/>
{this.renderContentPage()}
</div>
renderContentPage will check the URL and render the nested route.
<Route exact path="/" component={Page1}></Route>
<Route exact path="/page1" component={Page1}></Route>
<Route exact path='/page2' component={Page2} />
So inside Home component page1 and page2 components rendered.
Route expects a single children i.e. a component.
It should not be a new Route.
What you can do is to include your nested routes inside your customers component.
Also make sure to remove exact inside the routes in customers component.