Why child route won't render without the parent route rendering? - reactjs

I am trying to navigate to /movie/:title on which I am rendering a new page. But it only renders if my movie page i.e /movie renders, but I don't want my movie page to be mounted on navigating to /movie/:title. I only want it as part of the path. Making the /movie path exact doesn't work as /movie/:title wouldn't render with /movie as exact.
App.js
class App extends React.Component {
render() {
return (
<div>
<Header />
<Route path='/movies' component={Movies} />
<Route path='/tvshows' component={TvShow} />
</div>
)}
Movie.js
return (
<div className="movies">
<Route path={`${match.path}`} render={(props) => <CollectionGrid movies/>} />
<Route path={`${match.path}`} render={(props) => <CollectionOverview movies/>} />
<Route path={`${match.path}/:title`} component={ItemPage} />
</div>
);

A couple of notes. Conventionally, you should keep all your routes in a single router, thus making it easier to navigate and identify your application.
Organize your Router like this:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./Home";
import Movies from "./Movies";
import ItemPage from "./ItemPage";
const App = () => {
return (
<BrowserRouter>
<div>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/movies/:title" component={ItemPage} />
<Route path="/movies" component={Movies} />
</Switch>
</div>
</BrowserRouter>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Additionally, to resolve your solution, you would need the Switch component, which is available in react-router-dom. The Switch component, will look through a list of Routes and will only render the FIRST Route who's path string is included within the URL string.
In the list above we have 3 Routes. If your URL is "/movies/blah", it will only render the ItemPage component, because that Route's path was matched first within the list.
We moved the "/movies/:title" Route before the regular "/movies" for this very reason. If the "/movies" Route appeared first in the list and if your URL was "/movies/blah", it would still satisfy the "/movies" Route. That means Switch would only render Movies component, which is not what you want.
See working sandbox: https://codesandbox.io/s/hopeful-bogdan-lzl76

you are not able to access to match direct in this app the match is found in this .props.match
you cand used it in
const App({match})
then when you need to used it should
<Route path={`${match.path}`} render={(props) => <CollectionGrid movies/>} />
look to this example and explain

Related

Route open as a full page instead of opening in same page

I am using react router for a SPA Dashboard. I have these two routes(Routes.js) in dashboard right now. Also, a separate route in App.js for landing page.
Now it works fine as expected when it loads and when I click different routes. For example, When I click customers route(in sidebar), it open the customers section on the same page in right side. But, when I refresh the customers page localhost:5000/customers, now it opens as a separate page. How can I solve this.
I tried debugging it but still no luck.
Here's an example : https://github.com/gouravthakur39/MRE
Unfortunately can't reproduce it in code sandbox
Routes.js
import React from "react";
import { Route, Switch } from "react-router-dom";
import Dashboard from "../pages/Dashboard";
import Customers from "../pages/Customers";
const Routes = () => {
return (
<Switch>
<Route path="/home" exact component={Dashboard} />
<Route path="/customers" exact component={Customers} />
</Switch>
);
};
export default Routes;
Part of App.js
import { Fragment } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import PrivateRoute from "./private/PrivateRoute";
import Landing from "./pages/Landing";
import Home from "./pages/Home";
import Customers from "./pages/Customers";
function App() {
return (
<Fragment>
<div className="App">
<BrowserRouter>
<Switch>
<Route exact path="/" component={Landing} />
<PrivateRoute exact path="/home" component={Home} />
<PrivateRoute exact path="/customers" component={Customers} />
</Switch>
</BrowserRouter>
</div>
</Fragment>
);
}
export default App;
Question/Issue
Based on your code and the description on the Dashboard component it sounds like you want Dashboard and Customers to render "nested" in the "/home" path.
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
<p>I want to open dashboard and customers here when clicked</p>
<p>
Right now, when you click customers, it will open it in new page and not
here.
</p>
</div>
);
};
This is because Home is rendering the Dashboard on the same "/home" path, and the sidebar is linking to a "/customers" path at the root router level. This Customers component is outside the nested home/dashboard component.
Solution
App
For this the root "/home" in App needs to not exactly match in order for it to render nested routes. Remember also that in the Switch component that path order and specificity matter. Order the paths from more specific to less specific, i.e. "/home" is more specific than "/" and should be ordered higher/before. The "/customers" route/path should be removed since it will be rendered by Home.
function App() {
return (
<Fragment>
<BrowserRouter>
<Switch>
<Route path="/home" component={Home} />
<Route path="/" component={Landing} />
</Switch>
</BrowserRouter>
</Fragment>
);
}
Routes (rendered by Home)
Use the useRouteMatch hook to gather the current matched path value and build the nested routes. Remember the path order and specificity rule for the Switch.
import { Route, Switch, useRouteMatch } from "react-router-dom";
const Routes = () => {
const { path } = useRouteMatch();
return (
<Switch>
<Route path={`${path}/customers`} component={Customers} /> // "/home/customers"
<Route path={path} component={Dashboard} /> // "/home"
</Switch>
);
};
Sidebar (rendered by Home)
Use the useRouteMatch hook to gather the current matched url value and build the nested links.
import { Link, useRouteMatch } from "react-router-dom";
const Sidebar = () => {
const { url } = useRouteMatch();
return (
<div>
<ul>
<li>
<Link to={url}>Dashboard</Link> // to "/home"
</li>
<li>
<Link to={`${url}/customers`}>Customers</Link> // to "/home/customers"
</li>
</ul>
</div>
);
};

Prevent Navigation Component Rendering on Single Route but Render on every other Route

So I have a single Route /reminder, where I do not want my Navigation component, but I want the Navigation component to render on every other Route and I'm not sure how to go about this?
This is my App.js where I am using react-router-dom for my routing.
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Navigation from "./components/navigation/Navigation";
import Reminder from "./components/reminder/Reminder ";
const App = () => {
return (
<>
<Switch>
<Route path="/reminder" component={Reminder} exact />
</Switch>
<Navigation />
<MainContent>
<Router>
<Route path={"/"} component={Dashboard} exact />
</Router>
</MainContent>
</>
);
}
I had a look around for similar issues, but it's mostly for authenticated pages, this isn't for an authenticated page.
I don't want to go down the route of having to add the Navigation component into each of my 21 current routes, it seems like there should a better method?
With my current code above, it renders the Reminder component but still renders the Navigation component.
You can access the match data using a custom hook from react-router. All you need to do is
import { useRouteMatch } from "react-router-dom";
function App() {
let match = useRouteMatch("/reminder");
// Do whatever you want with the match...
return (
<>
<Switch>
<Route path="/reminder" component={Reminder} exact />
</Switch>
{!match && <Navigation />} <---Conditional rendering
<MainContent>
<Router>
<Route path={"/"} component={Dashboard} exact />
</Router>
</MainContent>
</>
);
}
Also, while I didn't change this in your example, it's not generally a good idea to have a route outside of the router component. I'm actually a little surprised react-router-dom didn't throw an error for you.

How to change the page and change only one component, not the entire html

I'm starting in React and I'm curious about about if have any way to change a page without reload all the html, changing only a content component for example.
I know that there is a way to change the component without change the url but I thought that if the url change too the application would be better.
React Router is the exact thing you're looking for
Here, how you can achieve what you're looking for.
First, wrap your app with BrowserRouter
import { BrowserRouter } from "react-router-dom";
import React from 'react';
class App extends React.Component {
return (){
return (
<BrowserRouter>
<SomeComponent />
</BrowserRouter>
)
}
}
Now just use the Route and Link. Route told the application which component to render on the basis of the current route and Link changes the URL without reloading the whole page
import { Route, Link, Switch } from "react-router-dom";
import React from 'react';
import {Circle, Square} from './someFileWithComponents';
class SomeComponent extends React.Component {
render(){
return (
<div>
<Link to='/circle' >Circle</Link>
<Link to='/square' >Square</Link>
<Switch>
<Route path='/circle' component={Circle} />
<Route path='/square' component={Square} />
</Switch>
</div>
)
}
}
React Router is what you looking for
const AppRouter =()=>(
<BrowserRouter>
<div>
<Header/>//where Header components contains the navigation
<Switch>
<Route path="/" component={BookListPage} exact={true} />
<Route path="/create" component={AddBookItem} />
<Route path="/edit/:id" component={EditBookItem} />
<Route path="/help" component={HelpPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
</BrowserRouter>
);
export default AppRouter;

React router - <Route /> does not render when <Link> not in same component

im trying to format my HTML like so within the body tag:
<header id="header"></header>
<main id="app"></main>
<footer id="footer"></footer>
reason why is so that i have my navigation out of <main></main> and in <header></header>
Im also rendering the corresponding React component individually i.e: document.getElementById("header"), document.getElementById("app") ...:
ReactDOM.render(
<Header />,
document.getElementById("header")
);
When clicking <Link to="/log-in"></Link> in <Header /> it breaks out of SPA and jumps to /log-in page.
What am i missing here?
Using ReactDOM.render multiple times will create separate instances unaware of each other source.
Let's go on about restructuring that a bit to make your app feel better:
App.js
import React from 'react';
import { Main } from './components';
const App = () => (
<Main />
)
ReactDOM.render(<App />, document.getElementById("app"));
Main.js
import React from 'react';
import { Router, Route } from 'react-router-dom';
import { Header, Login, Register, Home } from './components'
const Main = () => (
<Router>
<React.Fragment>
<Header />
<Route exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
<Footer />
</React.Fragment>
</Router>
)
export { Main };
So this way, we're ever only really rendering one instance. Your header/footer would be placed outside of the router, so whenever the route changes, they remain unaffected. Keep in mind that this will present a challenge, eg if you want your Header to highlight which route is active, the simples way, since it's outside of router and doesn't receive props from it, is to check the url. Also note that we're using <React.Fragment> here, but you're free to use a <div> or anything else you like. Router expect one child only, so if you don't want additional html elements, you can use a fragment!

React Router mount routes from external package

I am trying to develop an app that takes other "apps" as "plugins". This base app would include only basic auth routes and other apps would define their own routes within them.
How can I accomplish this with React? I suppose React Router could have something, but I have not been able to find it.
I come from a Ruby on Rails world where I could have a gem as an engine and on the base app I would just mount the engine on a given path. I was looking for something similar to that, such that on my base App.js I could simply import ModuleARoutes from 'module-a' and somehow insert it into the base app's <Router> component like:
<Router>
<ModuleARoutes path="/module_a" />
</Router>
Any guidance is much appreciated! Thanks!
UPDATE
Using the answer from #felipe-lanza I had my ModuleA like this:
import React from 'react';
import { Route } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
const Example1 = () => (<div>test 1</div>);
const Example2 = () => (<div>test 2</div>);
const App = () => (
<BrowserRouter>
<div>
<Route exact path="/" component={Example1} />
<Route exact path="/example1" component={Example2} />
</div>
</BrowserRouter>
);
export default App;
export { App as ExampleApp };
And on my base app I have
import MainStore from './stores/MainStore';
import AuthStore from './stores/AuthStore';
import App from './App';
import ExampleApp from '#module-platform/example';
const stores = { MainStore, AuthStore };
const Routes = () => (
<Provider { ...stores }>
<Router>
<Switch>
<Route exact path="/" component={ Login } />
<Route path="/dashboard" component={ App } />
<PrivateRoute path="/example_app" component={ ExampleApp } />
<Route component={ NotFound } />
</Switch>
</Router>
</Provider>
);
And now if I navigate to localhost/example_app I do get the expected result (a div with "test 1"). However, I would expect that navigating to localhost/example_app/example_1 would render the div with "test 2", however it still renders "test 1". As a matter of fact, any location with localhost/example_app (e.g. localhost/example_app/asdfasdfa) will get me the "test 1" div rendered.
What am I doing wrong?
If I understood correctly, wouldn't that be akin to rendering the child apps below the base app as different routes?
I.e. (inside your index.js):
<Router>
<Route path='/' component={BaseApp}/>
<Switch>
<Route path='/child-path-1' component={ChildApp1}/>
<Route path='/child-path-2' component={ChildApp2}/>
<Route path='/child-path-n' component={ChildAppN}/>
</Switch>
</Router>
Then each child app could have its own routes, and so forth.

Resources