I have the following ProductThumbnail component where as i click on the link it updates the URL and changes the route to redirect toward ProductDesc component. It generates the URL /productDescRedux/itemId properly.
const ProductThumbnail = (props) => {
const itemId = props.product
return(
<div>
<Link to={`/productDescRedux/${itemId.id}`}>
<div>
<h1>{props.product.headline}</h1>
<img src={props.product.images[0].imagesUrls.entry[1].url} alt="Thumbnail small pic"/>
<p></p>
</div>
</Link>
</div>
)
}
ProductThumbnail.propTypes = {
product: React.PropTypes.object
};
export default ProductThumbnail;
However despite URL changes, it does not call the component ProductDesc and i have to reload the page to display the component ProductDesc. Below the routes and the component ProductDesc
const Routes = function(){
return(
<Provider store={store}>
<Router history={ hashHistory }>
<Route path="/" component={ Container }>
<IndexRoute component={ Home } />
<Route path="/productredux" component={ App } >
<IndexRoute component={ ProductsGrid }/>
<Route path="/productDescRedux/:id" component={ ProductDesc } />
</Route>
<Route path="*" component={ NotFound } />
</Route>
</Router>
</Provider>
)
}
export default Routes;
const ProductDesc = ()=>({
render(){
return (
<div>
<h1>hello</h1>
<p>Yeah</p>
</div>
)
}
})
And here for completion the App component which uses connect() as well as the Main component
function mapStateToProps(state){
return {
products:state.products
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(actionCreators,dispatch);
}
const App = connect(mapStateToProps,mapDispatchToProps)(Main);
export default App;
//in a different file
const Main = (props) => ({
render(){
return (
<div>
{React.cloneElement(props.children, this.props)}
</div>
)
}
})
export default Main;
So I don't see why when changing the URL , routing is not calling the component ProductDesc. any insight ?
Syntax issue on the Main component which is the parent of all. I needed
{React.cloneElement(this.props.children, this.props)}
instead of
{React.cloneElement(props.children, this.props)}
Ref issue
Related
I have an app that I want to add an admin page to. I want the admin page to have its own layout seperate from the client layout. With what I have what's the current and 'best' way to implement this?
app.js
import './App.css';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import HomePage from './pages/HomePage';
import ItemDetailPage from './pages/ItemDetailPage';
import Header from './components/Header';
import Footer from './components/Footer';
import CollectionPage from './pages/CollectionPage';
import AdminPage from './pages/AdminPage';
function App() {
return (
<Router>
{/* Admin app routes */}
{/* <Route exact path="/admin" component={AdminPage}/> */}
{/* Client app routes */}
<div className="app">
<Header />
<Switch>
<Route exact path="/" component={HomePage}/>
<Route exact path="/item/:itemID" component={ItemDetailPage}/>
<Route exact path="/collections/:collection" component={CollectionPage}/>
</Switch>
<Footer />
</div>
</Router>
);
}
export default App;
HOC
export default function ClientLayoutHOC(props) {
const {component: Component, ...rest} = props;
return (
<div className="app">
<Header />
{/*<Component {...rest}/> */}
{props.children}
<Footer />
</div>
)
}
I found this. Should I create an AdminLayout and ClientLayout components and filter the pages through?
You could create a High Order Component and add it to your non-admin pages like this:
The HOC can contain your div wrapper and the Header and Footer.
Then all of your routes stay clean in the Router.Switch
The anonymous functions for HOC, HomePage, ItemDetailPage, and CollectionPage below are meant to be samples of the changes you'll make to those components. The HOC component will be a separate component too.
const HOC = (props) => {
const {component: Component, ...rest} = props;
return (
<div className="app">
<Header/>
<Component {...rest}/>
<Footer/>
</div>
)
}
const HomePage = (props) => {
return (
<HOC>
{/* replace with HomePage content*/}
</HOC>
)
}
const ItemDetailPage = (props) => {
return (
<HOC>
{/* replace with ItemDetailPage content*/}
</HOC>
)
}
const CollectionPage = (props) => {
return (
<HOC>
{/* replace with CollectionPage content*/}
</HOC>
)
}
function App() {
return (
<Router>
<Switch>
{/* Admin app routes */}
<Route exact path="/admin" component={AdminPage}/>
{/* Client app routes */}
<Route exact path="/" component={HomePage} />
<Route exact path="/item/:itemID" component={ItemDetailPage}/>
<Route exact path="/collections/:collection" component={CollectionPage}/>
</Switch>
</Router>
);
}
In looking at your added HOC code I would suggest the following changes:
export default function ClientLayoutHOC(props) {
const {component: Component, children, ...rest} = props;
return (
<div className="app">
<Header />
<Component {...rest}>
{children}
</Component>
<Footer />
</div>
)
}
The component tree i want is as below
- Login
- Home
- Contact
- About
Contact and About are children of Home.
This is my App.js ,
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Route exact path="/home" component={HomeView} />
</div>
</BrowserRouter>
);
}
}
render(<App />, document.getElementById('root'));
This is Home,
export const HomeView = ({match}) => {
return(
<div>
<NavBar />
Here i want to render the contact component, (Navbar need to stay)
</div>
)
}
This is my Navbar,
export const NavBar = () => {
return (
<div>
<Link to="/home">Home</Link>
<Link to="/home/contact">Contact</Link>
<hr/>
</div>
)
}
Contact component just need to render "hello text".
To make nested routes you need to remove exact:
<Route path="/home" component={HomeRouter} />
And add some routes:
export const HomeRouter = ({match}) => {
return(
<div>
<NavBar />
{/* match.path should be equal to '/home' */}
<Switch>
<Route exact path={match.path} component={HomeView} />
<Route exact path={match.path + '/contact'} component={HomeContact} />
<Switch>
</div>
)
}
You don't need use match.path in nested routes but this way it will be easier to move everything from "/home" to "/new/home" in case you decide to change your routing.
I am trying to gain access to params in ownProps. Is there a way to solve this?
Current structure is
const Router = () => (
<router>
<Switch>
<Route path='/signup' component={Signup} />
<Route path='/browse' component={Browse}/>
<Route path='/detail/:id' component={Detail} />
</Switch>
</router>
)
i want ownProps to pass on to these pages in Detail
export default class App extends Component {
render() {
return (
<div>
<Info />
<Message/>
</div>
);
}
}
After messing around I found a way which is that I passed the required information as props to nested components.
export default class App extends Component {
render() {
return (
<div>
<Info idInfo={this.props}/>
<Message idInfo={this.props}/>
</div>
);
}
}
after this I got information in ownProps.
I have a single page React app. I have a BaseLayout component which serves as the container for the app. BaseLayout displays the header and footer and also the content as shown below:
class App extends Component {
render() {
return (
<div>
<BaseLayout>
</BaseLayout>
</div>
);
}
}
BaseLayout
export default class BaseLayout extends Component {
render() {
return (
<div>
<NavBar />
{this.props.children}
<Footer />
</div>
)
}
}
index.js
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route path="/page_one" component={PageOne} />
<Route path="/page_two" component={PageTwo} />
<Route path="/" component={App} />
</Switch>
</BrowserRouter>,
document.getElementById('root')
)
All the navigation is inside the navBar.js component.
navBar.js
export default class NavBar extends Component {
render() {
return (
<div className="nav-bar">
<button><Link to="/">Home</Link></button>
<button><Link to="/page_one">Page One</Link></button>
<button><Link to="/page_two">Page Two</Link></button>
</div>
)
}
}
pageOne.js and pageTwo.js
export default class PageOne extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<h1>Page One </h1>
</div>
);
}
}
The problem is that when I go to page_one or page_two URL it only shows the content of the component and not the navigation bar.
How can I make the navigation bar and footer visible on all the pages using react router and react framework?
UPDATE:
App.js
class App extends Component {
render() {
return (
<div>
<BaseLayout>
</BaseLayout>
</div>
);
}
}
export default App;
I changed my index.js to the following:
ReactDOM.render(
<BrowserRouter>
<Route path="/" component={App}>
<Switch>
<Route path="/page_one" component={PageOne} />
<Route path="/page_two" component={PageTwo} />
</Switch>
</Route>
</BrowserRouter>,
document.getElementById('root')
)
Now, I get the following:
And the page does not go anywhere if I use the above code:
I think you should "lift" the App as the root Route.
By the way, Is App the same as BaseLayout? hard to tell from your example.
Example:
const Index = () => {
return (
<BrowserRouter>
<div>
<Route component={App} />
<Switch>
<Route path="/page_one" component={PageOne} />
<Route path="/page_two" component={PageTwo} />
</Switch>
</div>
</BrowserRouter>
);
};
A running code example, i'm not using a stack snippet as i can't use react-route with push state here.
I'm posting the entire code in case the url will be broken in the future:
import React from "react";
import { render } from "react-dom";
import { BrowserRouter, Route, Link, Switch } from "react-router-dom";
const Footer = () => <div>Footer...</div>;
class BaseLayout extends React.Component {
render() {
return (
<div>
<NavBar />
{this.props.children}
<hr/>
<Footer />
</div>
);
}
}
class NavBar extends React.Component {
render() {
return (
<div className="nav-bar">
<button>
<Link to="/">Home</Link>
</button>
<button>
<Link to="/page_one">Page One</Link>
</button>
<button>
<Link to="/page_two">Page Two</Link>
</button>
</div>
);
}
}
const PageOne = () => <h1>Page One</h1>;
const PageTwo = () => <h1>Page Two</h1>;
class App extends React.Component {
render() {
return (
<div>
<BaseLayout>{this.props.children}</BaseLayout>
</div>
);
}
}
const Index = () => {
return (
<BrowserRouter>
<div>
<Route component={App} />
<Switch>
<Route path="/page_one" component={PageOne} />
<Route path="/page_two" component={PageTwo} />
</Switch>
</div>
</BrowserRouter>
);
};
render(<Index />, document.getElementById("root"));
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>
);