In my project, I have to navigate from one component to other without changing the url. For this, I used MemoryRouter which worked as expected. But now the same idea should work for different routes i.e /one and /two. For example,
/one --> BrowserRouter (url change visible)
/
/first
/second
/third
/two --> BrowserRouter (url change visible)
/
/first
/second
/third
For the new visible routes, i.e /one and /two, the already established working Memoryroutes (i.e /, /first, /second, /third) should work properly with respective data as per provided in /one and /two.
I am struggling to include the MemoryRoutes inside the BrowserRoutes with the following structure code:
<BrowserRouter>
<Switch>
<Route path="/one" render={() => <MemoryComp configFor="Dave"></MemoryComp>}></Route>
<Route path="/two" render={() => <MemoryComp configFor="Mike"></MemoryComp>}></Route>
</Switch>
</BrowserRouter>
and then the MemoryComp holds:
<MemoryRouter history={customHistory}>
<Switch>
<Route path="/" render={(props) => <InitPage configFor={this.props.configFor} history={props.history}></InitPage>}></Route>
<Route path="/first" component={FirstPage}></Route>
<Route path="/second" component={SecondPage}></Route>
<Route path="/third" component={ThirdPage}></Route>
</Switch>
</MemoryRouter>
What I am trying to achieve:
To make the screens work with BrowserRouter --> MemoryRouter configuration.
To pass data from memory route to another memory route based on the main browser route. (Trying to use history to achieve the same)
Note:
This is more likely better to handle the browser routing stuff with server routing instead. Also, this seems can be achieved with any react-stepper plugin. But trying to understand what I am doing wrong here, just for learning purpose.
Here is the whole minimized code, available on Stackblitz (not working):
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
import { BrowserRouter, MemoryRouter, Route, Switch } from 'react-router-dom';
import { createBrowserHistory } from "history";
const customHistory = createBrowserHistory();
class MemoryComp extends Component{
render(){
return(
<MemoryRouter history={customHistory}>
<Switch>
<Route path="/" render={(props) => <InitPage configFor={this.props.configFor} history={props.history}></InitPage>}></Route>
<Route path="/first" component={FirstPage}></Route>
<Route path="/second" component={SecondPage}></Route>
<Route path="/third" component={ThirdPage}></Route>
</Switch>
</MemoryRouter>
);
}
}
class InitPage extends Component{
render(){
return (
<>
<ul>
<li onClick={() => this.navigateTo("/")}>Init</li>
<li onClick={() => this.navigateTo("/first")}>First</li>
<li onClick={() => this.navigateTo("/second")}>Second</li>
<li onClick={() => this.navigateTo("/third")}>Third</li>
</ul>
<div>{this.props.configFor}</div>
</>
)
}
navigateTo(path){
this.props.history.push(path, {
data: {
configFor: this.props.configFor
}
})
}
}
class FirstPage extends Component{
constructor(props){
super(props);
this.data = this.props.history.location.state.data;
}
render(){
return (
<>
<ul>
<li onClick={() => this.navigateTo("/")}>Init</li>
<li onClick={() => this.navigateTo("/first")}>First</li>
<li onClick={() => this.navigateTo("/second")}>Second</li>
<li onClick={() => this.navigateTo("/third")}>Third</li>
</ul>
<div>first page</div>
</>
)
}
navigateTo(path){
this.props.history.push(path, {
data: {...this.data, ...{pageName: 'first'}}
})
}
}
class SecondPage extends Component{
constructor(props){
super(props);
this.data = this.props.history.location.state.data;
}
render(){
return (
<>
<ul>
<li onClick={() => this.navigateTo("/")}>Init</li>
<li onClick={() => this.navigateTo("/first")}>First</li>
<li onClick={() => this.navigateTo("/second")}>Second</li>
<li onClick={() => this.navigateTo("/third")}>Third</li>
</ul>
<div>second page</div>
</>
)
}
navigateTo(path){
this.props.history.push(path, {
data: {...this.data, ...{name: 'deducedValue'}}
})
}
}
class ThirdPage extends Component{
constructor(props){
super(props);
this.data = this.props.history.location.state.data;
}
render(){
return (
<>
<ul>
<li onClick={() => this.navigateTo("/")}>Init</li>
<li onClick={() => this.navigateTo("/first")}>First</li>
<li onClick={() => this.navigateTo("/second")}>Second</li>
<li onClick={() => this.navigateTo("/third")}>Third</li>
</ul>
<div>third page</div>
</>
)
}
navigateTo(path){
this.props.history.push(path, {
data: this.data
})
}
}
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
render() {
return (
<div>
<BrowserRouter>
<Switch>
<Route path="/one" render={() => <MemoryComp configFor="Dave"></MemoryComp>}></Route>
<Route path="/two" render={() => <MemoryComp configFor="Mike"></MemoryComp>}></Route>
</Switch>
</BrowserRouter>
</div>
);
}
}
render(<App />, document.getElementById('root'));
At first I thought there was something wrong with Switch in combination with MemoryRouter, but after some debugging I realized it's actually totally independent.
The problem you have is that your base memory route needs to be exact, otherwise all other routes will match that one and first ('/') will be returned. Just add exact to your base route.
<MemoryRouter>
<Switch>
<Route exact path="/" render={(props) => <InitPage configFor={this.props.configFor} history={props.history}></InitPage>}></Route>
<Route path="/first" component={FirstPage}></Route>
<Route path="/second" component={SecondPage}></Route>
<Route path="/third" component={ThirdPage}></Route>
</Switch>
</MemoryRouter>
Be careful if you have even more nested routes to always add exact to the root one e.g. /first needs to be exact in order for this to work correctly:
<Route exact path="/first" component={FirstPage}></Route>
<Route path="/first/nested-1" component={SecondPage}></Route>
<Route path="/first/nested-2" component={ThirdPage}></Route>
Related
I'm trying to have a Home Page and with differents links redirect to the differents pages of my app.
I used <Routes> and <Route> to redirect them but is not working. It stay in blank.
I want to Navebar be the layout here, so I read that it must contain the other Routes inside of it
import React from 'react';
import { Route, Routes } from 'react-router';
import './App.scss';
import Navbar from './components/Navbar/Navbar'
import Home from './components/Home/index'
import Contact from './components/Contact'
const App = () => {
return (
<>
<Routes>
<Route path='/' element={<Navbar/>}>
<Route exact={true} index element={<Home/>}></Route>
<Route exact={true} path='contact' element={<Contact/>}></Route>
</Route>
</Routes>
</>
);
}
export default App;
index.js
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
This is the Navbar (which is the onlyone is showing)
class Navbar extends Component{
state = { clicked: false}
handleClick = () =>{
this.setState({clicked: !this.state.clicked})
}
render(){
return(
<>
<nav className='navbar-items'>
<img alt='mRadio' className='navbar-logo' href="../Home/index" src={require('../../assets/images/mRadio.png')}></img>
<div className='menu-icon' onClick={this.handleClick}>
<i className={this.state.clicked ? 'fas fa-times' : 'fas fa-bars'}></i>
</div>
<ul className={this.state.clicked ? 'nav-menu active' : 'nav-menu'}>
{MenuItems.map((item, index)=>{
return (
<li key={index}>
<a className={item.cName} href={item.url}>{item.title}</a>
</li>
)
})
}
</ul>
</nav>
</>
)
}
}
And this is the Home page:
const Index = () => {
return (
<div className='main'>
<video src={videomRadio} autoPlay loop muted/>
</div>
);
}
And this is a Third page:
const Index = () => {
return (
<div>
<p>CONTACT PAGE</p>
</div>
);
}
If you re using the latest version 6, don't use Switch, now is become Routes, try to change the path like this for example if is nested component:
import { Route, Routes } from 'react-router-dom'; //just in case
//...
const App = () => {
return (
<>
<Routes>
<Route path='/' element={<Navbar/>}>
<Route index element={<Home/>} />
<Route path='contact' element={<Contact/>}/>
</Route>
</Routes>
</>
);
}
export default App;
create a child in home and third page.that gives access to that page directly.
My question might seem stupid because I don't have enough background in React JS.
I have this component:
import { BrowserRouter, Route, NavLink } from "react-router-dom";
import CourseInfo from "./CourseInfo";
import OneCourse from "./OneCourse";
class CourseList extends Component {
render() {
return (
<div>
<BrowserRouter>
<div className="row courses">
{this.props.corses.map(course => (
<NavLink key={course._id} to={`/courses/profile/${course._id}`}>
<OneCourse course={course} />
</NavLink>
))}
</div>
<Route
exact
path={`/courses/profile/:id`}
render={({ match }) => (
<CourseInfo
index={match.params.id}
course={
this.props.corses.filter(el => el._id === match.params.id)[0]
}
/>
)}
/>
</BrowserRouter>
</div>
);
}
}
When I click on OneCourse component, it shows me the CourseList component in the same page with the component CourseInfo added in the bottom.
How can I send user to a new page containing only CourseInfo, knowing that I have parameters to send from this component to CourseInfo?
I want to show CourseInfo component in a different page that doesn't contain the CourseList
<NavLink> is just fine. For the rest, you might use the <Switch> component:
import { BrowserRouter, Route, NavLink, Switch } from 'react-router-dom';
class CourseList extends Component {
render() {
return (
<div>
<BrowserRouter>
<Switch>
<Route
exact
path={`/`} render={({ match }) => (
<div className="row courses">
{this.props.corses.map(course => (
<NavLink key={course._id} to={`/courses/profile/${course._id}`}>
<OneCourse course={course} />
</NavLink>
))}
</div>
)}
/>
<Route
exact
path={`/courses/profile/:id`}
render={({ match }) => (
<CourseInfo
index={match.params.id}
course={
this.props.corses.filter(el => el._id === match.params.id)[0]
}
/>
)}
/>
</Switch>
</BrowserRouter>
</div>
);
}
}
Quoting from the docs:
Switch is unique in that it renders a route exclusively. In contrast, every that matches the location renders inclusively.
With complex routing the render props approach gets confusing. Moving the routing to a separate component is a better approach:
import React from 'react';
import { BrowserRouter, Route, Link, Switch } from "react-router-dom";
import CourseList from './CourseList';
import Course from './Course';
function App() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={CourseList} />
<Route exact path="/courses/profile/:id" component={Course} />
</Switch>
</BrowserRouter>
);
}
export default App;
Then your CourseInfo component looks like:
class CourseList extends Component {
render() {
return (
<div>
<div className="row courses">
{this.props.corses.map(course => (
<NavLink key={course._id} to={`/courses/profile/${course._id}`}>
<OneCourse course={course} />
</NavLink>
))}
</div>
</div>
);
} }
The official documentation provides plenty examples.
I have a problem with Link. Googled a lot of topics, but I did not get a correct answer. In one discussion, the problem is in the earlier version of the router, in the other in the wrong import of components, in the third the answer was not even announced.
Also, what's with the 'history'?
Versions of the components:
"react": "^15.4",
"react-dom": "^15.4",
"react-router": "^4.1.1",
"react-router-dom": "^4.1.1"
Errors are:
Warning: Failed context type: The context `router` is marked as required in `Link`,
but its value is `undefined`.
and
Uncaught TypeError: Cannot read property 'history' of undefined
The component where Link is used is quite primitive:
import React from 'react';
import { Link } from 'react-router-dom';
export default class Menu extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<ul>
<li><Link to="/one">1</Link></li>
<li><Link to="/two">2</Link></li>
<li><Link to="/three">3</Link></li>
<li><Link to="/four">4</Link></li>
</ul>
</div>
);
}
}
So the component with the router looks like:
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom'
import Page1 from './Page1';
import Page2 from './Page2';
import Page3 from './Page3';
import Page4 from './Page4';
export default class Routes extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<BrowserRouter text = {this.props.text}>
<Route path = "/one"
render={(props) => (
<Page1 text = {this.props.text.Page1} {...props} />)} />
<Route path = "/two"
render={(props) => (
<Page2 text = {this.props.text.Page2} {...props} />)} />
<Route path = "/three"
render={(props) => (
<Page3 text = {this.props.text.Page3} {...props} />)} />
<Route path = "/four"
render={(props) => (
<Page4 text = {this.props.text.Page4} {...props} />)} />
</BrowserRouter>
);
}
}
And the most root component of the App:
import Header from './pages/menu/Header';
import Routes from './Routes';
const texts = require('text.json');
sessionStorage.setItem('lang','UA');
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
text: texts.UA
};
this.langHandler = this.langHandler.bind(this);
}
langHandler() {
if (sessionStorage.lang === 'UA') {
sessionStorage.setItem('lang','RU');
this.setState({ text: texts.RU })
} else {
sessionStorage.setItem('lang','UA');
this.setState({ text: texts.UA })
}
}
render() {
return (
<div id="content">
<Header changeLang = {this.langHandler}
text = {this.state.text.header}/>
<Routes text = {this.state.text}/>
</div>
);
}
}
In short, the point is that the page always has a rendered Header, and below it, depending on the selected menu item, the corresponding component was rendered.
I will be grateful for any advice.
Your Menu component should be nested inside the BrowserRouter component for the Links to work within this Router context.
Please take a look at the basic sample:
https://reacttraining.com/react-router/web/example/basic
You should use <Link> only inside the <Router> tag; if it's outside of it, you will get this error.
const Links =()=>(
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
)
const App=()=>(
<Links/> <==== this will throw Error
<Router>
<div>
<Route path="/" component={Home} />
<Route path="/" component={About} />
</div>
</Router>
)
following is the right way...
const Links =()=>(
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
)
const App=()=>(
<Router>
<div>
<Links/> <==== this will through Error
<Route path="/" component={Home} />
<Route path="/" component={About} />
</div>
</Router>
)
We presume, that we have the following:
import { BrowserRouter as StaticRouter, Router, Switch, Route, Link } from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
const customHistory = createBrowserHistory();
Then, it looks like that it is necessary to wrap every nested block of links with
<Router history={customHistory}>
<div>
<Link to={'/.../' + linkName1}>
{itemName1}
</Link>
<Link to={'/.../' + linkName2}>
{itemName2}
</Link>
</div>
</Router>
I'm pulling my hair out trying to render multiple layouts with React Router v4.
For instance, I'd like pages with the following paths to have layout 1:
exact path="/"
path="/blog"
path="/about"
path="/projects"
and the following paths to have layout 2:
path="/blog/:id
path="/project/:id
Effectively what's being answered here but for v4: Using multiple layouts for react-router components
None of the other answers worked so I came up with the following solution. I used the render prop instead of the traditional component prop at the highest level.
It uses the layoutPicker function to determine the layout based on the path. If that path isn't assigned to a layout then it returns a "bad route" message.
import SimpleLayout from './layouts/simple-layout';
import FullLayout from './layouts/full-layout';
var layoutAssignments = {
'/': FullLayout,
'/pricing': FullLayout,
'/signup': SimpleLayout,
'/login': SimpleLayout
}
var layoutPicker = function(props){
var Layout = layoutAssignments[props.location.pathname];
return Layout ? <Layout/> : <pre>bad route</pre>;
};
class Main extends React.Component {
render(){
return (
<Router>
<Route path="*" render={layoutPicker}/>
</Router>
);
}
}
simple-layout.js and full-layout.js follow this format:
class SimpleLayout extends React.Component {
render(){
return (
<div>
<Route path="/signup" component={SignupPage}/>
<Route path="/login" component={LoginPage}/>
</div>
);
}
}
So, for this you should use render function (https://reacttraining.com/react-router/core/api/Route/render-func)
A really good article that helped me: https://simonsmith.io/reusing-layouts-in-react-router-4/
In the end you will be use something like this:
<Router>
<div>
<DefaultLayout path="/" component={SomeComponent} />
<PostLayout path="/posts/:post" component={PostComponent} />
</div>
</Router>
I solved this problem utilizing a bit of both of your solutions:
My Routes.js file
import BaseWithNav from './layouts/base_with_nav';
import BaseNoNav from './layouts/base_no_nav';
function renderWithLayout(Component, Layout) {
return <Layout><Component /></Layout>
}
export default () => (
<Switch>
{/* Routes with Sidebar Navigation */}
<Route exact path="/" render={() => renderWithLayout(Home, BaseWithNav)} />
{/* Routes without Sidebar Navigation */}
<Route path="/error" render={() => renderWithLayout(AppErrorMsg, BaseNoNav)} />
<Route path="/*" render={() => renderWithLayout(PageNotFound, BaseNoNav)} />
</Switch>
)
Base.js (where routes get imported)
export default class Base extends React.Component {
render() {
return (
<Provider store={store}>
<Router>
<Routes />
</Router>
</Provider>
)
}
}
Layouts
BaseWithNav.js
class BaseWithNav extends Component {
constructor(props) {
super(props);
}
render() {
return <div id="base-no-nav">
<MainNavigation />
<main>
{this.props.children}
</main>
</div>
}
}
export default BaseWithNav;
BaseNoNav.js
class BaseNoNav extends Component {
constructor(props) {
super(props);
}
render() {
let {classes} = this.props;
return <div id="base-no-nav">
<main>
{this.props.children}
</main>
</div>
}
}
export default BaseNoNav;
I hope this helps!
I know i am replying late but it's easy to do that, i hope it will helps to newbie.
i am using React 4
Layout.js
export default props => (
<div>
<NavMenu />
<Container>
{props.children}
</Container>
</div>
);
LoginLayout.js
export default props => (
<div>
<Container>
{props.children}
</Container>
</div>
);
Now finally we have our App
App.js
function renderWithLoginLayout(Component, Layout) {
return <LoginLayout><Component /></LoginLayout>
}
function renderWithLayout(Path, Component, Layout) {
return <Layout><Route path={Path} component={Component} /></Layout>
}
export default () => (
<Switch>
<Route exact path='/' render={() => renderWithLayout(this.path, Home, Layout)} />
<Route path='/counter' render={() => renderWithLayout(this.path, Counter, Layout)} />
<Route path='/fetch-data/:startDateIndex?' render={() => renderWithLayout(this.path, FetchData, Layout)} />
<Route path='/login' render={() => renderWithLoginLayout(Login, LoginLayout)} />
</Switch>
);
I have react router setup with route parameters :name
<Router>
<Switch>
<Route path="/" exact={true} component={Index} />
<Route path="/about/:name" component={About} />
<Route component={NotFound} />
</Switch>
</Router>
Using <Link to=, links from Index correctly route to eg /about/vinnie.
However, if the <Link to= component is on the About page, clicking the link merely updates the browser URL, but dies not re-render the correct page.
Any clues why this might be happening?
About.jsx
render () {
const id = this.props.match.params.name;
return (
<div className="Explore">
<div className="container">
<Api endpoint={[this._apiEndpoint(id, 'info')]} loadData={true}>
<CustomerInfo />
</Api>
...
Api.jsx
render () {
let showData = this.props.loadData ? null : <button onClick={() => this._loadButton()}>Show</button>;
return (
<span>
{this.props.children && React.cloneElement(this.props.children, {
apiData: this.state.rawData
})}
{showData}
</span>
);
};
CustomerInfo.jsx
class CustomerInfo extends React.Component {
constructor(props) {
super(props);
this.state = {
CustomerInfo: {}
}
}
componentWillReceiveProps(nextProps) {
if (this.props.apiData !== nextProps.apiData) {
this.setState({CustomerInfo: nextProps.apiData[0]});
}
}
render() {
...
I think you need to add the exact prop to your first <Route />. Otherwise your routes match Index and About and I think react-router intentionally renders both.