I have a simple demo application where you can view quotes individually (QuoteDetail) and view comments under them. I need the url parameters in both components, but I can only get them in the parent component.
App.tsx:
<Switch>
// ...
<Route path="/quotes" exact>
<AllQuotes />
</Route>
<Route path="/quotes/:quoteId">
<QuoteDetail />
</Route>
// ...
</Switch>
QuoteDetail.tsx:
export interface QuoteDetailParams {
quoteId: string;
}
const QuoteDetail: React.FC<QuoteDetailProps> = (props) => {
const match = useRouteMatch();
const { quoteId } = useParams<QuoteDetailParams>();
// ...
console.log(quoteId); // <-- THIS ONE WORKS
// ...
return (
<React.Fragment>
{quote}
<Route path={match.path} exact>
<div className="centered">
<Link className="btn--flat" to={`${match.url}/comments`}>
Show Comments
</Link>
</div>
</Route>
<Route path={`${match.url}/comments`} exact>
<div className="centered">
<Link className="btn--flat" to={`/quotes/${quoteId}`}>
Hide Comments
</Link>
</div>
<Comments />
</Route>
</React.Fragment>
);
};
As you can see, when I click a button to load comments, the url changes from /quotes/1/ to /quotes/1/comments which should load the Comments component as well. However, in Comments.tsx, I can't access the url parameter.
Comments.tsx:
const Comments = () => {
const params = useParams<QuoteDetailParams>();
const { quoteId } = params;
// ...
console.log(params); <-- THIS ONE RETURNS undefined
// ...
return (
<section className={classes.comments}>
<h2>User Comments</h2>
{comments}
</section>
);
};
I have no idea what I'm doing wrong here. Of course, I can pass the parameter using a component prop as well, but this is not desired in my case. Help is appreciated.
I'm using react-router-dom#5.3.0
match
match.url is the portion of the URL, it's useful for building nested <Link>s.
match.path is the path pattern used to patch, it's useful for building nested <Route>s.
Your QuoteDetail component is using match.url for building the nested route when it should use match.path.
<Route path={`${match.url}/comments`} exact> // <-- incorrect route path
...
</Route>
It should be:
<Route path={`${match.path}/comments`} exact>
...
</Route>
Related
I am trying to pass a symbol in the state so that I can render a slug page based on the prop. I am havving issues passing the state in as it breaks everything. I am using react-router-dom to navigate the pages. I have a Link in another file to connect the slug pages.
App.js
function App() {
return (
<div>
<AuthContextProvider>
<Navbar/>
<Routes>
<Route path='/' element={<Home/>}/>
<Route path='/showinvestments' element={<Protected><ShowInvestments/></Protected>}/>
<Route path='/addinvestments' element={<Protected><Add/></Protected>}/>
<Route path='/sell/:stockSell' element={<Protected><Sell ticker={this.state.symbol}/></Protected>}/>
<Route path='/login' element={<Login/>}/>
</Routes>
</AuthContextProvider>
</div>
);
}
The Link to the slug page
....
<div className='col text-center d-flex ' >
<Link to={{pathname: sellLink, state: {symbol: ticker}}}>
<AiFillEdit className='mt-2'/>
</Link>
</div?
....
The page that I am trying to render based on the props passed in
const Sell = ({ticker}) => {
// const {state} = useLocation();
// const { tic } = state;
// console.log(tic);
return (
<div className='container'>
Hello
{ticker}
</div>
)
}
Any help would be appreciated.
I have tried different variants inside of <Route>. If I pass a string as the ticker, it prints that, I just cannot get it to pass the current state in.
So recently I found out two ways of creating private routes in react.
With a HOC (higher-order component):
const PrivateRoute = ({ user, children }) => {
if (!user) {
return <Navigate to="/home" replace />;
}
return children;
};
const App = () => {
...
return (
<>
...
<Routes>
<Route path="/home" element={<Home />} />
<Route
path="/privateroute"
element={
<PrivateRoute user={user}>
<PrivateComponent />
</PrivateRoute >
}
/>
...
</Routes>
</>
);
};
With substituting routes completely
const App = () => {
...
return (
<>
{user ? (
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/privateroute" element={<PrivateComponent />} />
...
</Routes>
) : (
<Routes>
<Route path="/home" element={<Home />} />
...
</Routes>
)}
</>
);
}
My fellow colleague told me that the second way is quite bad since it completely erases some routes (if user is falsy then there is no route to /privateroute). But on my question why might that be bad he had no definitive answer. I couldn't find anything on the internet either. Any thoughts on which way is the best?
Between these two options, the first is the preferred solution since it keeps all routes mounted so they there will be no race condition between setting the user state and issuing an imperative navigation action to one of the protected routes. In other words, with the second implementation you have to wait for the user state to update and trigger a component rerender so the protected routes are mounted and available to be navigated to.
The second method also duplicates unauthenticated routes if it's all one or the other. Code duplication should be avoided.
Note however though that the first example isn't a Higher Order Component, it's just a wrapper component.
Note also that it's more common to create a PrivateRoute component as a Layout Route instead of as a Wrapper component. The change is trivial but it makes the component a little more wieldy. Render an Outlet component for nested routes instead of the children prop for a single wrapped child component.
import { ..., Outlet } from 'react-router-dom';
const PrivateRoute = ({ user }) => {
return user ? <Outlet /> : <Navigate to="/home" replace />;
};
Now instead of wrapping each individual route you want to protect you render a layout route that wraps an entire group of routes you want to protect. It makes your code more DRY.
const App = () => {
...
return (
<>
...
<Routes>
<Route path="/home" element={<Home />} />
... other unprotected routes ...
<Route element={<PrivateRoute />}>
<Route path="/privateroute" element={<PrivateComponent />} />
... other protected routes ...
</Route>
... other unprotected routes ...
</Routes>
</>
);
};
Target
I'm using react router v6.
I have a parent route with a static header with a back button that when I click it needs to go one path above, exp. /shop/1/item/create to /shop/1/item, when i click the back button i call the function navigate from useNavigate()
example
Red is the root page, yellow is where the static header is and green is content that i want to change
Problem
When i call navigate regardless of using "." or ".." or "./" the page never change correctly, it either redirects from /shop/1/items/create to /shop or to /shop/1
the only way of redirecting correctly is using -1 but it causes the problem that if someone copy's the URL when going back they get redirected to whatever page they were before pasting the URL.
Code
const ShopPage = () => {
const [ state, setState ] = React.useState<IShopPageState>({ shop: { name: "" }, isInvalid: false })
const { id } = useParams()
const navigate = useNavigate()
return (
<div id="shop-page">
<Card>
<div id="shop-page-header">
<Icon canHover onClick={() => navigate("./")} icon="BiArrowBack"/>
<Title text={state.shop ? state.shop.name : ""}/>
</div>
</Card>
<div id="shop-page-content">
<Routes>
<Route path="/*" element={<div onClick={() => navigate('./items')}>wddw</div>}/>
<Route path="/items/*" element={<ItemsPage shop={state.shop}/>}/>
<Route path="/items/create" element={<ItemPage/>}/>
</Routes>
</div>
</div>
)
}
Here is the code of the yellow part with only the important stuff, the header is static and always visible and where the back button is.
I believe this code is enough to understand, the green part only redirects the url from /items/ to /items/create using navigate('./create')
I think a possible solution would be simply copying pasting the header for each page but i find that to be bad practice and it is a last resort solution
Here is a example of the problem
EDIT
As asked here is some extra code showing the problem
App.js
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="*" element={<Link to="/shop">Shop</Link>} />
<Route path="shop/*" element={<Shop />} />
</Routes>
</BrowserRouter>
);
}
Shop.js
const Shop = () => {
return (
<>
<div>
<Link to="./">Back</Link>
</div>
<Routes>
<Route path="*" element={<Link to="./items">All</Link>} />
<Route path="items/*" element={<Link to="./create">Item</Link>} />
<Route path="items/create" element={<>Create</>} />
</Routes>
</>
);
};
if you are using a nested route then better to use
<Route path="/" >
<Route index element={<Items/>} />
<Route path="item:id" element={<Item/>} />
</Route>
Hey Dear #pekira you can see just a simple hint in the code below for React Router v6
import { Routes, Route, Link } from 'react-router-dom';
const App = () => {
return (
<>
<h1>React Router</h1>
<nav>
<Link to="/home">Home</Link>
<Link to="/user">User</Link>
</nav>
<Routes>
<Route index element={<Home />} />
<Route path="home" element={<Home />} />
<Route path="user/:Id" element={<User />} />
<Route path="*" element={<NoMatch />} />
</Routes>
</>
);
};
Please see sandbox.
I'm having this code:
<Router history={createBrowserHistory()}>
<Switch>
<Route path="/items/:itemId">
<Item />
</Route>
<Route exact path="/items">
<Items />
</Route>
<Redirect to="/items" />
</Switch>
</Router>
Expecting /items to be default route. Items is loading links for available items:
const { url } = useRouteMatch();
return (
<div>
<h1>Items</h1>
{items.map((item) => (
<Link to={`${url}/${item}`}>{item}</Link>
))}
</div>
);
Once an item link is clicked, I'm expecting the url to have the item in it, and to render Item.
url is /items, so everything should work out for Switch to render:
<Route path="/items/:itemId">
<Item />
</Route>
However, it's staying at /items, and not even rendering Items component anymore. No error is shown. Any idea why this is happening?
For router you are missing an exact props which is really important for your case
<Route exact path="/items/:itemId" />
and inside your Item component you misspelled the param name, because in router it's itemId where the component has a different one item, so fixing it will make the param accessible(they should be exact match)
export const Item = () => {
const { itemId } = useParams();
return (
<div>
<h1>Item</h1>
{itemId}
</div>
);
};
That because when it reaches the /items/:itemId Page the component get redirect back to /items , So the solution to this is just remove the redirect component
<Switch>
<Route path="/items/:itemId">
<Item />
</Route>
<Route exact path="/items">
<Items />
</Route>
</Switch>
Also there few other mistake which i saw in your code.In Item.jsx , your using
const { item } = useParams();
This isn't going to the render the anything since it is undefined , that is because your are passing the /:itemId in Route. So try this instead
const { itemId:item } = useParams();
here we are renaming the itemId to item inorder to accessable down in the code.
I'm practicing nested routing using react-router-dom.
In Room001Container, there is a tab navigation bar that will be displayed on all components, and there is a button that navigates to a detailed post page when the button is pressed.
When I enter the room, I should see the post page first by default.
However, if I output postId as useParams in Room001Container component, undefined is returned.
In the PostDetail component, params are printed well, but why is it not output in the Room001Container? I know that the container is unconditionally rendered even if I move to each component, but it's really strange. Is there any way to know the params from the top container?
function Container() {
return (
<Switch>
<Route
path="/channel/:channelId/lobby/:lobbyId"
component={LobbyContainer}
/>
</Switch>
);
}
function LobbyContainer() {
return (
<Switch>
<Route
exact
path="/channel/:channelId/lobby/001"
component={Room001Container}
/>
<Route
exact
path="/channel/:channelId/lobby/002"
component={Room002Container}
/>
<Route
exact
path="/channel/:channelId/lobby/002"
component={Room003Container}
/>
</Switch>
);
}
function Room001Container() {
const history = useHistory();
const { pathname } = history.location;
const { channelId, postId } = useParams();
console.log(postId) // undefined
return (
<>
<S_TabBar>
<Link to="/channel/:channelId/lobby/001/post">
<S_TabCell>post</S_TabCell>
</Link>
<Link to="/channel/:channelId/lobby/001/chat">
<S_TabCell>chat</S_TabCell>
</Link>
</S_TabBar>
<S_Button onClick={() => history.push('/channel/:channelId/lobby/001/post/1')}>
go to detail
</S_Button>
<Switch>
<Route
exact
path="/channel/:channelId/lobby/001/post"
component={Post}
/>
<Route
exact
path="/channel/:channelId/lobby/001/post/:postId"
component={PostDetail}
/>
<Route
exact
path="/channel/:channelId/lobby/001/chat"
component={Chat}
/>
<Redirect
exact
from="/channel/:channelId/lobby/001"
to="/channel/:channelId/lobby/001/post"
/>
</Switch>
</>
);
}
There is no postId parameter in the route that mounts Room001Container.
You'll want e. g.
<Route
exact
path="/channel/:channelId/lobby/:postId"
component={Room001Container}
/>