Hi I have seen this question and this is exactly what i am looking for but i can't get it to work.
React Router V4 Routers with Master Pages / Templates
I am using version 5.2
App component includes the navigation and wraps two paths with two template components, 'Public' and 'Private' and when i click each link i expect that page to be wrapped by it's template but instead only the first link works and shows the template. The second link only changes the url in the browser address bar and only the content of first template remains on the page and the content of the second page doesn't show. If i change the order of the links always only the top link gets the correct template.
This is where i define the routes and links to them.
class App extends Component {
render() {
return (
<Router>
<div>
<ul><li><Link to="/">Home</Link></li>
<li><Link to="/members">Members</Link></li>
</ul>
</div>
<Switch>
<Public>
<Route exact path="/"><Home /></Route>
</Public>
<Private>
<Route path="/members"><Members /></Route>
</Private>
</Switch>
</Router>
);
}
}
export default App;
My private and public templates are the same except for the titles one say private and the other public.
class Private extends Component {
render() {
return (
<div className="container">
<div className="row">
<div className="col-md-2"><h2>PRIVATE</h2></div>
<div className="col-md-8">{this.props.children}</div>
<div className="col-md-2"></div>
</div>
</div>
);
}
}
export default Private;
I have been looking for something like this for a while as it looks like a potentially neat solution. If anyone knows how to fix this i would really appreciate it.
Thanks
Here is what Switch Component does:
React.Children.forEach(this.props.children, child => {
if (match == null && React.isValidElement(child)) {
element = child;
const path = child.props.path || child.props.from;
match = path
? matchPath(location.pathname, { ...child.props, path })
: context.match;
}
});
as you see the Switch component only cares about the first level of its children and looks for a prop called path or from in that. if it doesn't find it it assumes that every path will match. so it always shows the first children no matter what.
what you need to do is change the order of the children like this:
<Switch>
<Route exact path="/">
<Public>
<Home />
</Public>
</Route>
<Route path="/members">
<Private>
<Members />
</Private>
</Route>
</Switch>
Related
Expected: when clicking on the image from one component(PhotosList), it routes to another page with the specified url to view the other component(Image details)
Reality: what happens that when clicking on the image, both components are rendered on the same page but with the different url.
using import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
Here's the return of the functional component(PhotosList)
return (
<Router>
<div className="layout">
<Masonry gutter={"5 px"} columnsCount={3}>
{photosList.map((photo: any) => (
<Link to={"/details"}>
<img src={photo.download_url} />
</Link>
))}
</Masonry>
<button onClick={loadMore} className="btn-grad">
{isLoading ? "Loading..." : "Load More"}
</button>
<Switch>
<Route exact path={"/details"} component={ImageDetails}></Route>
</Switch>
</div>
</Router>
);
and the app file is
const App = () => {
return (
<div className="App">
<header>Album</header>
<PhotosList />
</div>
);
};
export default App;
return (
<div className="layout">
<Masonry gutter={"5 px"} columnsCount={3}>
{photosList.map((photo: any) => (
<Link to={"/details"}>
<img src={photo.download_url} />
</Link>
))}
</Masonry>
<button onClick={loadMore} className="btn-grad">
{isLoading ? "Loading..." : "Load More"}
</button>
</div>
);
and in the app file
const App = () => {
return (
<Router>
<div className="App">
<header>Album</header>
<PhotosList />
<Switch>
<Route exact path={"/photilist"} component={PhotosList}></Route>
<Route exact path={"/details"} component={ImageDetails}></Route>
</Switch>
</div>
</Router>
);
};
export default App;
From what I can gather, you have rendered the PhotoList component which then renders a list of photos each with a link to /details.
In the same component you set up the /details route.
Whenever you click the Link which redirects to /details, the PhotoList component will (should) unmount. Once this component is unmounted, so is the Route which sets up the component which should be rendered on /details route. So you are on /details but the router has no idea what component should be rendered because the following code:
<Switch>
<Route exact path={"/details"} component={ImageDetails}></Route>
</Switch>
no longer exists.
To avoid this, don't put Routes this far down in your application. Routes should always be close to the top level (children/grandchildren of Router). This makes projects way easier to manage. I do have projects with upwards of 100 routes and I do split up my routes into seperate files (for example each module will have a ModuleRoutes.js file in which I set up the routes for that module), then you can create a RootRouter components which renders these seperate JS files (Which are really React components) and you render the RootRouter inside of your <BrowserRouter> / <Router>.
You can abstract this even higher by creating an array of objects which hold details about each route (route name, path, component for the route, function / data you want to pass down to the component, access control (true/false) defined as a logical expression of your choosing - i.e. user.isLoggedIn === true). By employing this abstraction you minimize the room for error because errors and bugs are much easier to spot when they are in this kind of form (object) as opposed to JSx which takes up more lines of code, has messy indentation and is not very clean in general.
I'm working on an user-list project made with React/ React Router for a community I'm part of and I've been running into a problem I can not find the root of.
I have a nested Router set up, to have a userlist appear on the click of a button (url/player) and then - in the userlist - have the profile of that user appear on a click on the name (url/player/:id). This works fine so far!
BUT:
When I'm on a user profile (url/player/:id) and click the link to get back to the userlist, it does not render the userlist-component - though the url in the browser itself changes back to (url/player).
I can't figure out how to make the general userlist reappear again and would surely appreciate some input.
Since the project has multiple components, I separated them into different files, where my problem my lay.
I still tried to reconstruct the instructions of different tutorials for nested Routes. Maybe I'm just overlooking something basic, but I cant seem to find it.
Main Navigation Router in the index.js
<Router>
<Navigation />
<div className="contentBox">
<Route path="/" exact component={Home} />
<Route path="/player" exact component={Playerlist} />
</div>
</Router>;
Userlist Router in the Playerlist-Component
<Router>
<Route path="/player" exact component={Playerlist} />
<Route path="/player/:id" component={Playerprofile} />
</Router>;
The weird thing is, only the Playerlist-Link does not work anylonger. If I click on either the "Home"-Link or any other Navigation, it works. So I assume it has something to do with the rerendering.
I'm sorry if this question seems obvious, I'm still a beginner and appreciate any help! Thank you!
You should improve the nested routing of your app:
No need to wrap PlayerList component with Router. Use Router only in root component.
Don't use Route to PlayerList component within the component itself.
Do use Route to PlayerList component in index.js, but without exact (so routing to '/player/:id' routes work).
Here are snippets of updated code:
App.js (in your case index.js):
export default function App() {
return (
<Router>
<Navigation />
<Route path="/" exact component={ Home } />
<Route path="/player" component={ PlayerList } />
</Router>
);
}
PlayerList.js
export default function PlayerList({ match }) {
return (
<div>
<h1>Player List</h1>
<Route path={`${match.path}/:id`} component={ PlayerProfile } />
</div>
);
}
PlayerProfile.js
export default function PlayerProfile({ match }) {
return <div>Player Profile { match.params.id }</div>;
}
Navigation.js (draft):
export default function Navigation() {
return <nav>
<Link to="/">Home</Link>
<Link to="/player">PlayerList</Link>
<Link to="/player/1">Player 1</Link>
<Link to="/player/2">Player 2</Link>
<Link to="/player/3">Player 3</Link>
</nav>;
}
Though I'd recommend to move the Links to "/player/:id" into PlayerList component.
You can find official notes on nested routing here. Hope that helps.
Another react question here, I have a solution for my problem but to me it doesn't seem very "React" so I was hoping for another solution.
I'm using react router so the bottom of my app.js(entry point) is:
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={Layout}>
<IndexRoute component={Login} ></IndexRoute>
<Route path="searches" component={searches}></Route>
<Route path="notifications" component={notifications}></Route>
</Route>
</Router>
, app);
Now as you can see my overarching component is Layout so, in my mind I want to configure my (reusable) components, for example I want my Layout to pass the title of the menu items to the header component, and then if I'm for example loading a search then I might want to pass functions etc to the search component to hook into it's functionality, so I have the following in layout:
export default class Layout extends React.Component {
constructor() {
super();
}
render() {
const containerStyle = {
paddingRight: '5px'
}
// Figure out which props we want based on location.pathname
const {pathname} = this.props.location;
switch(pathname){
case "/searches":
// So now I need to add some props, functions or anything else to this component
theProps = {someProp: "value"}
const theComponent = React.cloneElement(this.props.children, {theProps})
break;
}
return (
< div style={containerStyle} class="container">
< Header menuItems={this.getMenuItemsForScreen()}/ >
{ theComponent}
< Footer / >
< /div>
);
}
}
So basically in order to pass props from my overarching Layout I have to clone the component and then give it some more props?? It just feels a bit dirty but I can't seem to find a way of embedding this type of logic otherwise?
Thanks
Marc
I think the great thing about these routing components is that they save you from those ugly switches in your components.
I'm not sure which kind of props you want to send to the searches component. In your question is not clear what is the actual problem you are trying to solve instead of using one of the standard approaches in the react-router documentation.
I recommend considering these alternatives:
Refactor your searches component to avoid receiving any props. Try to have each route to have a component that doesn't receive any props. So you move that code that define the props (theProps = {someProp: "value"}) inside the searches component. Or if you need the searches component to be reused with those props and other props in another time, then make a new parent component that defines those props and calls the searches component then. But if those props are to complex and dependent on your app state then maybe you can consider using flux, redux or another state container, and get those from the app state.
If you really need the routing parameters, then make sure the props can be serialized so they can be part of the URL. Check the message route in the code below (copied from RouteConfiguration sample):
import React from 'react'
import { render } from 'react-dom'
import { Router, Route, Link } from 'react-router'
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
{this.props.children}
</div>
)
}
})
const About = React.createClass({
render() {
return <h3>About</h3>
}
})
const Inbox = React.createClass({
render() {
return (
<div>
<h2>Inbox</h2>
{this.props.children || "Welcome to your Inbox"}
</div>
)
}
})
const Message = React.createClass({
render() {
return <h3>Message {this.props.params.id}</h3>
}
})
render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)
In this case your code will have <a href={"/inbox/message/"+id} ...> somewhere in your code and those will provide the props by setting the id parameter in this case.
you use functional component in child component with this code :
<Route path="/:id" component={Child} />
function Child({ match }) {
return (
<div>
<h2>ID:{match.params.id}</h2>
</div>
);
}
I have an AngularJS background, and started to play around with React. I'm using the react-router and want have basically this setup:
Template
export default ( props ) => {
return (
<div>
<Navbar></Navbar>
{ props.children }
</div>
);
}
Routing in App.js
<Router>
<Route path="/" component={Template}>
<IndexRoute component={RestaurantRoulette} />
<Route name="workspace" path="workspace(/:workspace_id)" component={RestaurantRoulette}>
<Route name="run" path="run" component={Run}></Route>
</Route>
</Route>
</Router>
Navbar
<ul className="navigation">
<li>RestaurantRoulette</li>
<Link to="run">Run</Link>
<li>Save</li>
</ul>
What I want
When I am in localhost:8080/workspaces/444 I want to click on the Run Link, and navigate to localhost:8080/workspaces/444/run
What is the current status
If I manually type the url localhost:8080/workspaces/444/run, everything works fine. But when I click on the Link in the navbar, I get to localhost:8080/run.
Can somebody tell me how this works? How this is all connected?
I think the Link tag should be like that:
<Link to={'workspaces/' + this.props.workspace_id + '/run'}>Run</link>
you should get the workspace_id from path in Template, and pass it to the NavBar component
I have a component called Header which exists across all routes, while the rest of the app changes. So to accomplish this, my main render code looks about like this (using ES6):
render(){
return (
<div>
<Header></Header>
<Router>
<Route path="/" component={Home} />
<Route path="/details/:id" component={Details} />
</Router>
</div>
);
}
The challenge is that the contents of the <Header> should vary slightly depending on the route, for example a unique title per route.
How can this be achieved?
Thanks for all the great answers! Still mulling them over.
To throw another solution into the mix, I found that I can actually put arbitrary properties on the Route, so I added title:
<Route title="My Title" component={App} />
And I re-shuffled around my route hierarchy to include the header in Router (in the top-level Route component instead of outside any route as before), so my main render code looks like this:
<Router>
<Route component={App}>
<Route path="/" component={Home} title="Home" />
<Route path="/detail/:id" component={Detail} title="Details" />
</Route>
</Router>
And my App contains the header and is passed the current route's title:
class App extends React.Component {
render(){
var route = this.props.routes[this.props.routes.length - 1];
return (
<div>
<Header title={route.title} />
{this.props.children}
</div>
)
}
}
But I can't say this is the best solution. I do like that I can just put title on each route now, but I worry about the coupling and the way I have to extract the properties from the routes array.
This is a good use case for flux. You have route handlers that create an action when they mount. This action goes into the HeaderStore. The Header component listens to the Header store and renders based on it.
You can see an example of this here:
CurrentBoardStore holds the current page info
BoardPage updates the store when it mounts/updates
SubBoardHeaderWrapper renders the header with data from CurrentBoardStore
The way I do it (I'm pretty sure there is a better way, but this might help you out) looks like the following:
index.js
// define routes
const routes = (
<Route component={App} path="/">
<IndexRoute component={Home} />
<Route path="/" component={Home} />
<Route path="/details/:id" component={Details} />
</Route>
);
ReactDOM.render(<Router>{routes}</Router>, document.getElementById('your_id'));
App.js
render() {
return (
<div>
<Header routerProps={this.props.children} />
{this.props.children}
</div>
);
}
Header.js
componentWillReceiveProps(nextProps) {
// Get access to the pathname, it contains a string like "/details/"
console.log(nextProps.routerProps.props.location.pathname);
}
instead of putting header there.. put the header in a layout component. each view should use the layout component and you can pass whatever you want the header to be.
export class Layout extends React.Component{
render() {
return <div>
<Header title={this.props.title} />
{this.props.children}
</div>
}
}
all of your views can use this same component like so
export class SomeComponent extends React.Component{
render() {
return <Layout title="Some Component Title">
<div>my elements here</div>
</Layout>
}
}
NOTE: the beauty of using something like this, is you can set up any other default messaging like for instance lets say you want to have a flash message appear... someone clicks on something and you want a message to say 'You've successfully registered!' (in this example). You can include your flash messaging in the layout and simply dispatch an event to show the message. This can be done with modals too and really whatever your app requirements are :)