I've got a page at <url>/machines which lists the IP addresses of a set of machines on a network. I want to be able to click on one in the list and link to a page <url>/machines/<machineid> to render a new page which which show information about that specific machine. I want the value specified in the URL as <machineid> to be passed into the rendered page as a usable value, e.g. in a prop/param etc.
I'm having trouble configuring react router to achieve this, and am wondering if anyone can see what I'm doing wrong? I've been following the React Router V6 docs, however can't seem to get it to work. When I render the page at <url>/machines/hello, I get a console error saying No routes matched location "/machines/hello". Can anyone see what I'm doing wrong?
I was initially thinking I'd just render a new page (using a different component) to render the Machine Info page, however looking at the React Router V6 docs, it seems like the <MachineInfo> component is now rendered as a child of <Machines>?
I have an alert() in the <MachineInfo> component which doesn't seem to be being run at all. I get no alert.
App.js
function App() {
const value = useContext(Context);
return (
<div className="App">
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="machines" element={<Machines />}>
<Route path="machines/:id" element={<MachineInfo />} /> // I've tried this using just path=":id" as well with no luck
</Route>
<Route path="topology" element={<Topology />} />
<Route path="settings" element={<Settings />} />
</Routes>
</div>
);
}
MachineInfo.js
export default function MachineInfo(props) {
const [state, dispatch] = useContext(Context);
let { id } = useParams<"id">([]);
alert("info: " + id)
return (
<p>hello</p>
);
}
First, you'll want a Layout component that will have your Outlet
export function Layout() {
return (
<>
<Outlet />
</>
);
Now wrap your other routes within this Layout component & you'll notice you can now get to your nested route by saying /machines/1
The Dashboard component is the index so / should match this route.
function App() {
// Not sure why you have value since it doesn't seem to be getting used
const value = useContext(Context);
return (
<div className="App">
<Routes>
<Route path="/*" element={<Layout />}>
<Route index element={<Dashboard />} />
<Route path="machines" element={<Machines />}>
<Route path=":id" element={<MachineInfo />} />
</Route>
<Route path="topology" element={<Topology />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</div>
);
}
Related
Register and login components need to be added to the container class. I followed a react course on Udemy. They are using an older version of react-router-dom. For this i used v6 react router dom and made changes, but this one I don't know what to do. This code is new to me, please assist me
function App() {
return (
<Router>
<Fragment>
<Navbar />
<Routes>
<Route exact path='/' element={<Landing />} />
<section className='container'>
<Route exact path='/register' element={Register} />
<Route exact path='/login' element={Login} />
</section>
</Routes>
</Fragment>
</Router>
);
}
error in console
[section] is not a <Route> component. All component children of <Routes> must be a <Route>
As the error is informing you, only Route or React.Fragment are valid children of the Routes component.
If you want to render several routed components into a specific layout, i.e. common UI/layout, then create a layout route for them to be nested into.
Make sure to also render Register and Login as JSX!
Example:
import { Outlet } from 'react-router-dom';
const SectionLayout = () => (
<section className='container'>
<Outlet /> // <-- nested routes render content here
</section>
);
export default SectionLayout;
...
import SectionLayout from '../path/to/SectionLayout';
...
<Routes>
<Route path='/' element={<Landing />} />
<Route element={<SectionLayout />}>
<Route path='/register' element={<Register />} />
<Route path='/login' element={<Login />} />
</Route>
</Routes>
For more information see:
Layout Routes
I think the error is quite descriptive in itself. That the children of <Routes /> can only be <Route /> and <section /> doesn't satisfy that.
If you need both Register and Login components to have a wrapper of section with .container class.
We can achieve it through different approaches, here are a few of them.
For eg.:
/**
* 1. Putting them inside the components itself
*/
const Register = () => {
return (
<section className="container">
// your other codes here
</section>
)
}
const Login = () => {
return (
<section className="container">
// your other codes here
</section>
)
}
/**
* 2. As a reusable Layout wrapper or Higher Order Component or
* Useful when you have many shared contents and styling
*/
const Container = (props) => {
return (
<section className="container">
// shared contents
{props.children}
// other shared contents
</section>
);
}
const Register = () => {
return (
<Container>
// your other codes here
</Container>
)
}
const Login = () => {
return (
<Container>
// your other codes here
</Container>
)
}
Hope that helps.
The code below is responsible for building the sidebar on the site. In this case, the sidebar is 2 filter options, made in the form of a drop-down list.
This side bar is located on all pages of the site. However, the problem is that when switching between pages, the selected filter options are not saved. That is, if on the first page, the user checks something in the filters, then when going to the second page, the values in the filters will not be saved. And I would like the selected parameters for filtering to be saved when moving from page to page.
export default function App() {
return (
<ThemeProvider theme={theme} style="width: 100vw; height: 100vh">
<Header/>
<BrowserRouter>
<Routes>
<Route exact path='/test' element={<Test filterKey='device'/>}/>
<Route exact path='/devices' element={<DeviceList/>}/>
<Route exact path='/devices/:deviceId' element={<DeviceId/>}/>
<Route exact path='/devices/:deviceId/:userId' element={<UserId/>}/>
<Route exact path='/devices/:deviceId/:userId/:sessionId' element={<SessionId/>}/>
<Route exact path='/devices/:deviceId/:userId/:sessionId/:pocketId' element={<PocketId/>}/>
<Route path="*" element={<Navigate to="/devices" replace />}/>
</Routes>
<Footer/>
</BrowserRouter>
</ThemeProvider>
);
}
Since you are saying Sidebar component should be in each page with same functionality, I would place it in App component then it will be always in pages without changing it's filters
export default function App() {
return (
<ThemeProvider theme={theme} style="width: 100vw, height: 100vh">
<BrowserRouter>
<Header/>
<div style={{width: '100%', display: 'flex'}}>
<Routes>
<Route exact path='/test' element={<Test filterKey='device'/>}/>
<Route exact path='/devices' element={<DeviceList/>}/>
<Route exact path='/devices/:deviceId' element={<DeviceId/>}/>
<Route exact path='/devices/:deviceId/:userId' element={<UserId/>}/>
<Route exact path='/devices/:deviceId/:userId/:sessionId' element={<SessionId/>}/>
<Route exact path='/devices/:deviceId/:userId/:sessionId/:pocketId' element={<PocketId/>}/>
<Route path="*" element={<Navigate to="/devices" replace />}/>
</Routes>
<Sidebar />
</div>
<Footer/>
</BrowserRouter>
</ThemeProvider>
);
}
to share the State between React Components you can initiate your state in the main App and then pass it to all components via props.
for example, your "Component A" :
// pass the state & setState in the component as props :
export const ComponentA = ({
isFilterMethodExpanded,
setIsFilterMethodExpanded,
isFilterDateTimeExpanded,
setIsFilterDateTimeExpanded
}) => {
// whatever you want to do with the state
return <div>...</div>;
};
and your "Component B" :
// pass the state & setState in the component as props :
export const ComponentB = ({
isFilterMethodExpanded,
setIsFilterMethodExpanded,
isFilterDateTimeExpanded,
setIsFilterDateTimeExpanded
}) => {
// whatever you want to do with the state
return <div>...</div>;
};
then your main App will look like :
// Initiate the state in the main App :
export default function App() {
const [isFilterMethodExpanded, setIsFilterMethodExpanded] = useState(false);
const [isFilterDateTimeExpanded, setIsFilterDateTimeExpanded] = useState(
false
);
// pass the state & setState in the component props in the return part :
return (
<div className="App">
<ComponentA
isFilterMethodExpanded={isFilterMethodExpanded}
setIsFilterMethodExpanded={setIsFilterMethodExpanded}
isFilterDateTimeExpanded={isFilterDateTimeExpanded}
setIsFilterDateTimeExpanded={setIsFilterDateTimeExpanded}
/>
<ComponentB
isFilterMethodExpanded={isFilterMethodExpanded}
setIsFilterMethodExpanded={setIsFilterMethodExpanded}
isFilterDateTimeExpanded={isFilterDateTimeExpanded}
setIsFilterDateTimeExpanded={setIsFilterDateTimeExpanded}
/>
</div>
);
}
This will keep the State when navigating between React Components, but not on the browser refresh, If you want to Persist the State on page reload then you can check this article
Component is loaded on the correct path like /characters/kabal -- (kabal it`s ID)
But its loaded if you just enter any text after /characters/ for example /characters/548fnufndf or /characters/548fnufndf/dnbsdnhdj/dfmd
How to check the correct path in a functional component before loading the component and, if the path is wrong, redirect to another page?
//App.js
<Switch>
<Route
path="/characters"
exact
component={Characters}/>
<Route
exect
path="/characters/:id"
render={(props) => <CharacterPage {...props}/>}
/>
<Route
exect
path="/settings"
component={Settings}/>}
/>
<Route exect insecure component={Error} />
</Switch>
//Link to component and array of IDs
const item = [
{charId:'fujin'},
{charId:'scorpion'},
{charId:'kabal'}
]
<Link
exact="true"
to={{
pathname:`/characters/${item.charId}`,
}}
</Link>
//A component that should be loaded only if a link with this id exists.
const Scrollable = ({match}) => {
useEffect(() => {
let id = data[match.params.id]
if(!id) {
return <Redirect to="/" />
}
}, [])
}
What version of React-router are you using?
Here's a similar question: React-Router: No Not Found Route?
In summary
If you're using v4 or v5 then:
Keep the path (url stays unchanged)
<Switch>
<Route exact path="/users" component={MyComponent} />
<Route component={GenericNotFound} />
</Switch>
Redirect to another route (change url)
<Switch>
<Route path="/users" component={MyComponent} />
<Route path="/404" component={GenericNotFound} />
<Redirect to="/404" />
</Switch>
Pay attention to the order as well, NotFoundComponent must be last for that path. Also like what #cbr said, exect should be exact.
Setup
I have an App component rendering following routes:
<Route path="/items/:id" component={ItemDetail} />
<Route path="/items" component={AllItems} />
In my AllItems component I render a list of all items and the option to create a new item or update an existing one. Doing either one of those actions opens a popup. To do this I render following routes in AllItems:
<Route path="/items/add" component={AddItemModal} />
<Route path="/items/edit" component={EditItemModal} />
Note: It's important that these modals are actually linked to these routes, I can't change that. Neither can I render those routes outside of AllItems as I need to pass soms props to the modals.
Problem
When I go to a route like /items/1: ItemDetail renders (as expected).
When I go to /items/add: ItemDetail renders with add as :id.
I need it to render AddItemModal here as defined in AllItems.
What I tried:
I tried adding exact to the /items/:id route and I also tried adding it to /items/add & /items/edit. Neither of those solutions worked. Either only ItemDetail rendered, or only the modals.
I tried defining /items before /items/:id to hopefully give higher priority to the nested routes. ItemDetail never rendered in this case.
Is there a solution to this so I can prioritise items/add & items/edit over items/:id
Try nesting the routes under /items
<Route
path="/items"
render={() => (
<>
<Route path="" component={AllItems} exact />
<Route path="/add" component={AddItemModal} />
<Route path="/edit" component={EditItemModal} />
<Route path="/:id" component={ItemDetail} />
</>
)}
/>
If you want to have an independent views for ItemDetail and AllItems but at the same time have /items/add and /items/:id/edit (took a little liberty with the url, you need and id to edit an item right?) as modals over AllItems so the structure of the routes would be something like this:
AllItemsView (/items)
AddItemModal (/items/new)
EditItemModal (/items/:id/edit)
ItemDetailView (/items/:id)
You need a little tweak of Tnc Andrei response:
<Route
path="/items"
render={({ match: {url, isExact}, location: {pathname} }) => {
let pathnameArray = pathname.split("/")
let lastChunk = pathnameArray[pathnameArray.length - 1]
if (isExact || lastChunk === "new" || lastChunk === "edit") {
return (
<>
<Route path={`${url}/`} component={CompetitionsView} />
<Switch>
<Route path={`${url}/new`} component={CompetitionFormModal} />
<Route path={`${url}/:competitionId/edit`} component={CompetitionFormModal} />
</Switch>
</>
)
}
return (
<>
<Route path={`${url}/:competitionId`} component={CompetitionView} />
</>
)
}}
/>
I have a following situation:
<Wrapper>
<Container>
<Route exact path="/" component={UserListing} />
<Route path="/user/:id" component={UserDetails} />
<Route exact path="(/|/user/\d+)" component={StaticComponent} />
</Container>
<Route path="/create-account" component={CreateAccount}/>
</Wrapper>
Okey, so my case is simple: I don't want the Container component to render if path does not equal to "/" or "/user/:id".
So if path is "/create-account" only Wrapper with CreateAccount as child should appear, without Container.
Looking for some help. Thank you
You can write a custom component that decides whether or not to render Container like
const RouterRenderer = ({ location, children }) => {
if(location && location.state && location.state.noMatch) {
return null;
}
return children;
}
And a sub component which tells this container that nothing matched like
const NoMatch = () => {
return <Redirect to={{state: {noMatch: true}}} />
}
export default withRouter(NoMatch);
Now you can use them like
<Wrapper>
<RouterRenderer>
<Container>
<Switch>
<Route exact path="/" component={UserListing} />
<Route path="/user/:id" component={UserDetails} />
<Route exact path="(/|/user/\d+)" component={StaticComponent} />
<NoMatch />
</Switch>
</Container>
</RouterRenderer>
<Route path="/create-account" component={CreateAccount}/>
</Wrapper>
P.S. Note that its important to use Switch when you are using NoMatch component since you want to render it only when no other
route matched. Also you can write RouteRenderer function in the form
of an HOC instead of a functional component.