react-router v4 nesting routing issue - javascript

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.

Related

Render React page dynamically using URL parameter

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>
);
}

React Router. How to check if URL: ID exists in functional component?

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.

Routes not working as desired in React JS

I am trying to build a full stack application with User login/logout functionality.
I want to protect certain pages such that they can only be viewed when the user is logged in. For login I have created a REST API and I am using session storage to keep track of whether the user is logged in or not.
validateUser = () => {
let user = {
username: this.state.email,
password: this.state.password,
//status: "LOGGED_IN"
};
UserService.authenticateUser(user).then((res) => {
if(res.data === 'SUCCESS') {
window.sessionStorage.setItem("isUserLogged", true);
} else if(res.data === 'FAILURE') {
window.sessionStorage.setItem("isUserLogged", false);
this.resetLoginForm();
this.setState({"error":"Invalid username or password"});
}
})
};
Tis is my App.js
function App() {
return (
<div>
<Router>
<HeaderComponent/>
<div className="container">
<Switch>
<Route path="/" exact component={LandingPageComponent}></Route>
{/* <Route path ="/customers" component = {ListCustomerComponent}></Route> */}
{/* <Route path ="/add-customer/:id" component = {CreateCustomerComponent}></Route> */}
<Route path = "/view-customer/:id" component = {ViewCustomerComponent}></Route>
<Route path = "/admin-login" component = {AdminLoginComponent}></Route>
<Route path = "/admin-register" component = {AdminResgisterComponent}></Route>
<Route path="/customers" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <ListCustomerComponent />
: <Redirect to='/admin-login' />
)} />
<Route path="/add-customer/:id" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent />
: <Redirect to='/admin-login' />
)} />
</Switch>
</div>
<FooterComponent/>
</Router>
</div>
);
}
export default App;
Everything works fine if I don't check my session storage. But when I try to implement the conditional routes as shown above I start getting errors.
If I just put simple routes, then I don't encounter this error.
Any help would be highly appreciated.
You didn't pass Route props into your component. So history does not included in props, you can console.log(this.props) to check what this.props contains.
To fix it, let's pass Route props into your components
<Route path="/add-customer/:id" exact render={(props) => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent {...props} /> // ADD PROPS HERE
: <Redirect to='/admin-login' />
} />
You didn't show what you did on ListCustomerComponent.
You could try to encapsulate your component using HOC withRouter or if you are using Functional component, use useHistory hook.
// on export class component
export default withRouter(YourComponent)
in functional component, you can use
const YourComponent = ()=>{
const history = useHistory();
// then you can say something such as
// history.push(...)
return <>...your view here...</>
}
export default YourComponent;
<Switch>
{/* Login Sections goes Here */}
<Route exact path='/' component={MainPage} />
<Route exact path='/login' component={Login} />
<Route exact path='/admin/' component={LoginAdmin} />
<Route exact path='/register' component={Register} />
{/* AdminUser ROutes goes here */}
<SuperUserDashboard>
<Route exact path='/admin/dashboard' component={Dashboardpage} />
<Route exact path='/admin/users' component={UsersAdmin} />
</SuperUserDashboard>
<Route exact path='' component={Notfound} />
</Switch>
in superuser dashboard check if user is authenticated if not redirect to admin login page else all the routes will be visible

Why React router v4 makes incorrect redirect?

I have a simple React app and I'm trying to implement auth logic. This is how my application looks like:
class App extends Component {
render() {
return (
<Router history={history}>
<div className="App">
<Switch>
<PrivateRoute path="/account" component={Account} />
<Route component={PublicLayout} />
</Switch>
</div>
</Router>
)
}
}
My auth logic is: if user isAuthenticated === true render /account page else redirect to /signin. So for this I created my own simple PrivateRoute functional component:
export default ({ component: Component, ...args }) => {
return (
<div>
<Route {...args} render={(props) => {
return fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to='/signin' />
}} />
</div>
)
}
Because PrivateRoute path="/account" is NOT exact path I expect that if user goes to /account-setting it will redirect him to /sign-in. But it's not working. The user successfully goes to /account-setting and see PublicLayout.
Why is that? There is no exact key in route, it has to grab all that starts from "/account" and use my logic in the PrivateRoute functional component.
Not providing exact props doesn't mean that it will match /account, /account-settings but it will match /account, /account/other, /account/other/paths. So, you'll need to provide both path:
<PrivateRoute path="/account" component={Account} />
<PrivateRoute path="/account-setting" component={Account} />
Or, you may use regex:
<PrivateRoute path="/account(?=\S*)([a-zA-Z-]+)" component={Account} />

Passing props to component in React Router 4

I am new to react-router and I just started writing an app using react-router V4. I would like to to pass props to components rendered by <Match /> and I am wondering about what's the 'best' or 'proper' way to do so.
Is it by doing something like this?
<Match pattern="/" render={
(defaultProps) => <MyComponent myProp = {myProp} {...defaultProps} />
}/>
Is this (passing props to components to be rendered by <Match />) even a good practice to do so with react-router or is it an antipattern or something; and if so, why?
You must use the render prop instead of component to pass on custom props, otherwise only default Route props are passed ({match, location, history}).
I pass my props to the Router and child components like so.
class App extends Component {
render() {
const {another} = this.props
return <Routes myVariable={2} myBool another={another}/>
}
}
const Routes = (props) =>
<Switch>
<Route path="/public" render={ (routeProps) =>
<Public routeProps={routeProps} {...props}/>
}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/" render={ (routeProps) =>
...
}/>
</Switch>
render() {
return (
<Router history={browserHistory}>
<Switch>
<Route path="/"
render={ () => <Header
title={"I am Title"}
status={"Here is my status"}
/> }
/>
<Route path="/audience" component={Audience}/>
<Route path="/speaker" component={Speaker}/>
</Switch>
</Router>
)
}
I'm fairly new to react-router and came across a similar issue. I've created a wrapper based on the Documentation and that seems to work.
// Wrap Component Routes
function RouteWrapper(props) {
const {component: Component, ...opts } = props
return (
<Route {...opts} render={props => (
<Component {...props} {...opts}/>
)}/>
)
}
<RouteWrapper path='/' exact loggedIn anotherValue='blah' component={MyComponent} />
So far so good
I use render in combination with a defined method like so:
class App extends React.Component {
childRoute (ChildComponent, match) {
return <ChildComponent {...this.props} {...match} />
}
render () {
<Match pattern='/' render={this.childRoute.bind(this, MyComponent)} />
}
}
The render prop is meant for writing inline matches, so your example is the ideal way to pass extra props.
I'll do it like the following to improve clarity.
const myComponent = <MyComponent myProp={myProp} {...defaultProps} />
<Match pattern="/" component={myComponent} />
By this your router code won't get messed up!.

Categories