Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do you create a Master Detail page? (v4) #3928

Closed
acpower7 opened this issue Sep 22, 2016 · 22 comments

Comments

@acpower7
Copy link

commented Sep 22, 2016

I went through the docs multiple times but I'm still not sure how to create a layout that can share some common components.

How do you create a layout that always has a header and footer, and only body changes? And how do you create more routes within that body? (bonus)

thanks

@acpower7 acpower7 changed the title How do you create a Master Detail page? delete Sep 22, 2016

@acpower7 acpower7 changed the title delete How do you create a Master Detail page? Sep 22, 2016

@acpower7 acpower7 changed the title How do you create a Master Detail page? How do you create a Master Detail page? (v4) Sep 22, 2016

@ryanflorence

This comment has been minimized.

Copy link
Contributor

commented Sep 22, 2016

the basic example does this, doesn't it?

@timdorr timdorr closed this Sep 22, 2016

@LukeAskew

This comment has been minimized.

Copy link

commented Feb 1, 2017

Not exactly.

I want to do something like:

<Router>
  <div className={'app-container'}>
    <Switch>
      <DefaultLayout>
        <Route exact path={'/'} component={Dashboard} />
      </DefaultLayout>

      <EmptyLayout>
        <Route component={NotFound} />
      </EmptyLayout>
    </Switch>
  </div>
</Router>

Where the app uses a different container component based on which route matches.

EDIT:

This works, but I'm not sure it's the best solution:

const routes = [
  {
    exact: true,
    path: '/',
    component: Dashboard,
    layout: DefaultLayout,
  }, {
    path: '/advertisers',
    component: Advertisers,
    layout: DefaultLayout,
  },
];

const App = props => (
  <Router history={history}>
    <div className={'app-container'}>
      <Switch>
        {routes.map(route => (
          <Route path={route.path} key={route.path} exact={route.exact}>
            <route.layout>
              <route.component />
            </route.layout>
          </Route>
        ))}

        <Route>
          <EmptyLayout>
            <Route component={NotFound} />
          </EmptyLayout>
        </Route>
      </Switch>
    </div>
  </Router>
);
@kachkaev

This comment has been minimized.

Copy link

commented Feb 25, 2017

@LukeAskew Have you found a better solution so far? I've got the same issue here. The page layout is expected to contain a header and a sidebar in all cases except when it's 404. The trick with routes.map would work, but it would also generate multiple instances of the header and the sidebar, so when a user is switching between routes, the layout DOM goes through a deep refresh :–(

@kachkaev

This comment has been minimized.

Copy link

commented Feb 25, 2017

@ryanflorence how would you solve this? It'd be great to have this case covered in the docs.

@ryanflorence

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2017

const routes = [
  { path: '/one',
    component: One
  },
  { path: '/two',
    component: Two
  },
  { path: '/three',
    component: Three
  }
]

<Router>
  <div>
    <Switch>
      {routes.map(route => (
        <Route path={route.path} component={Header}/>
      ))}
    </Switch>
    <Switch>
      {routes.map(route => (
        <Route path={route.path} component={Sidebar}/>
      ))}
    </Switch>

    <Switch>
      {routes.map(route => (
        <Route path={route.path} component={route.component}/>
      ))}
      <Route component={NoMatch}/>
    </Switch>
  </div>
</Router>

There's a bug right now #4578 that will avoid the remounting of Header and Sidebar as the routes change, but does this do what you want?

@ryanflorence

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2017

@LukeAskew

const routes = [
  { path: '/one',
    Component: One,
    Layout: Layout1
  },
  { path: '/two',
    Component: Two,
    Layout: Layout2
  },
  { path: '/three',
    Component: Three,
    Layout: Layout2
  }
]

<Router>
  <div>
    <Switch>
      {routes.map({ path, Layout, Component } => (
        <Route path={route.path} render={(props) => (
          <Layout {...props}>
            <Component {...props}/>
          </Layout>
        )}/>
      ))}
    </Switch>
  </div>
</Router>
@kachkaev

This comment has been minimized.

Copy link

commented Feb 25, 2017

Thanks for your reply @ryanflorence – this is close to what I was looking for, assuming that the componets are reused. What would be good as well is to be able to control that top-level <div>, because someone might want to end up with something like this:

// /
<LayoutForPageWithContent>
  <Header />
  <Sidebar />
  <Home />
</LayoutForPageWithContent>

// /about
<LayoutForPageWithContent>
  <Header />
  <Sidebar />
  <About />
</LayoutForPageWithContent>

// /cart
<LayoutForPageWithContent>
  <Header />
  <Sidebar />
  <Cart />
</LayoutForPageWithContent>

// ...

// /404
<LayoutForPageWithError>
  <Error404 />
</LayoutForPageWithError>

UPD: Header and Sidebar can be omitted, because they can be a part of <LayoutForPageWithContent />

@ryanflorence

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2017

@kachkaev

const routes = [
  { path: '/',
    exact: true,
    component: Home
  },
  { path: '/about',
    component: About,
  },
  { path: '/cart',
    component: Three,
  }
]

<Router>
  <Switch>
    {routes.map({ path, exact, component: Comp } => (
      <Route path={path} exact={exact} render={(props) => (
        <LayoutWithSidebarAndHeader {...props}>
          <Comp {...props}/>
        </LayoutWithSidebarAndHeader>
      )}/>
    ))}
    <Route component={Error404}/>
  </Switch>
</Router>
@kachkaev

This comment has been minimized.

Copy link

commented Feb 25, 2017

@ryanflorence this solution wont reuse LayoutWithSidebarAndHeader as far as I understand, so their states and fetched will be lost each time the url changes.

@ryanflorence

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2017

there's a bug in switch #4578

@ryanflorence

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2017

fixed by

b556060
32a5bbb
7a02cc3

next beta release will have it fixed, you'll need to pull from github and build yourself for now.

@kachkaev

This comment has been minimized.

Copy link

commented Feb 25, 2017

I'm not sure if that bug is related. Reusing component={Sidebar} is one thing, because it's just a reference to a component type. But reusing render={(props) => (...)} inside map is probably a completely different story, because here we have subtrees of independently existing component instances, not just references to their names.

I don't see any solution in the current API, but I may be wrong. Looks like the only place the wrapper can be toggled is in <Switch />:

<Switch wrapperComponent={({whatever}) => (whatever ? LayoutWithSidebarAndHeader : LayoutForPageWithError)}>
  <Route exactly path="/" component={Home} />
  <Route exactly path="/about" component={About} />
  <Route exactly path="/cart" component={Cart} />
  <Route component={Error404} />
</Switch>

It's ugly, but at least it gives a chance to really reuse the layout in the situation above, which is not the same as reusing peer neighbours like Header and Sidebar. Once again, I can be totally wrong here.

@ryanflorence

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2017

It's not a different story. It's fixed on the v4 branch.

@kachkaev

This comment has been minimized.

Copy link

commented Mar 4, 2017

Just updated to v4.0.0-beta.7 and can confirm that shared layout wrappers are reused perfectly now! Checked this by rendering a random instanceId in the layout, which is came from here: componentDidMount() { this.isntanceId = instance ${Math.random()}`; }

Two caveats to share:

  • When running yarn add react-router@v4.0.0-beta.7, it is important not to forget yarn add react-router-dom@v4.0.0-beta.7, because otherwise some weird context-errors will show up.

  • When looping through routes using map, do not assign key to them as this is done elsewhere, because otherwise the layout will not be reused:

{routes.map(({ path, exact, content: ContentComponent }) => (
  <Route
    // NO key={path} etc. here!
    path={path}
    exact={exact}
    render={(props) => (
      <Layout {...props}>
        <ContentComponent {...props} />
      </Layout>
  )}
  />
))}

Thanks for your previous responses @ryanflorence! I enjoy V4 API more and more!

UPD: Absence of key was causing a react warning the browser console. I simply applied key={0} and the warning was gone, still leaving the routes workable. Not sure this is the best practice though.

@ryanflorence

This comment has been minimized.

Copy link
Contributor

commented Mar 8, 2017

@kachkaev you shouldn't be installing react-router yourself at all. react-router-dom re-exports everything from react-router that you need.

@ankibalyan

This comment has been minimized.

Copy link

commented Apr 6, 2017

How the approach, is their any backlog issue if I'll try like this.
I want a couple of different layouts for Anonymous users, Logged in users, then Admin users, Addtional layout like Page Layouts.

<Switch>
  <Route exact path="/login" render= { ({ ...rest }) => (
    <Login { ...rest } onLogin={ (props) => { // do something on login } } />
  ) } />

  <Route exact path="/register" component={Register} />

  <Route path="/admin" render={ ({ ...rest }) => (
    <AuthLayout { ...rest }>
      <Switch>
        <Route exact path="/admin" render={ (props) => (
          <Dashboard />
        ) } />
      </Switch>
    </AuthLayout>
  ) } />

  <Route path="/">
    <MainLayout>
      <Switch>
        <Route exact path="/" >
          <Home />
        </Route>
        <Route exact path="/about" >
          <About />
        </Route>
        <Route exact path="/faqs" >
          <Faqs />
        </Route>
        <Route exact path="/terms-of-service" >
          <TermsOfService />
        </Route>
      </Switch>
    </MainLayout>
  </Route>
</Switch>

or how can I achieve this in a better way.

@ankibalyan

This comment has been minimized.

Copy link

commented Apr 6, 2017

while this was pretty easy in previous versions

<Router history={browserHistory}>
  <Route path="/" component={MainLayout}>
    <IndexRoute component={Home} />
    <Route path="/about" component={About} />
    <Route path="/terms-of-service" component={TermsOfService} />
    <Route path="/faqs" component={Faqs} />
  </Route>
  <Route path="/" component={AdminLayout}>
    <IndexRoute component={Dashboard} />
    <Route path="/users" component={Users} />
  </Route>
</Router>
@Anima-t3d

This comment has been minimized.

Copy link

commented Apr 17, 2017

@ankibalyan Have you tried this:

Define one Route pointing to a component that will decide what to wrap the route in

<Router>
  <Route component={AppWrap} />
</Router>

In AppWrap do something like the following

 var isPrivateLayout;
 for (var path of listOfPrivateLayoutPaths) {
   if (this.props.location.pathname == path) {
     isPrivateLayout= true;
   }
 }
 if (isPrivateLayout) {
   return <PrivateLayout>
        (routes)
      </PrivatelyLayout>
 } else {
   return <PublicLayout>
        (routes)
      </PublicLayout>;
 }

@timdorr it does not seem that this issue should be closed. Based on the basic example one would need to create PrivateLayout, PublicLayout and in each of those layouts define the routes? Whereas in the older version you could just define everything in 1 file. In v4 it seems you can only go 1 level deep each time when defining a route/template?

@eashish93

This comment has been minimized.

Copy link

commented Apr 27, 2017

I tried with this approach:

const DefaultLayout = ({children}) => (
  <div>
     <Header/>
     {children}
 </div>
);
<Router>
<Switch>
                <Route path="/checkout" exact component={Checkout}/>
                <DefaultLayout>
                    <Switch>
                        <Route path="/" exact component={Home}/>
                        <Route path="/products" component={Products}/>
                        <Route path="/product/:item" component={ProductSingle}/>
                        <Route path="/cart" component={Cart}/>
                        <Route path="/login"  component={Login}/>
                        <Route path="/register" component={Register}/>
                        <Route path="/forgetpass" component={ForgetPass}/>
                    </Switch>
                </DefaultLayout>
            </Switch>
</Router>
@viiiprock

This comment has been minimized.

Copy link

commented May 30, 2017

I used this approach

  <Provider store={store}>
    <ConnectedRouter history={history}> // this is from connected-react-router for my sagas 
      <Switch>
        {routes.map(({ path, exact, component: Component }) => (
          <Route
            key={path}
            path={path}
            exact={exact}
            render={props => (
              <App {...props}>
                <Component {...props} />
              </App>
            )}
          />
        ))}
      </Switch>
    </ConnectedRouter>
  </Provider>

And add side bar in App component with sidebars in routes.js
https://reacttraining.com/react-router/web/example/sidebar

Excellent !!!

@noinkling

This comment has been minimized.

Copy link

commented Jun 22, 2017

@eashish93 Unfortunately there doesn't seem to be a way to make that approach work with a layoutless fallthrough (e.g. 404), or with 2 or more different layouts. It works nicely for some use cases though.

@EricFries

This comment has been minimized.

Copy link

commented Jul 7, 2017

@kachkaev Thanks for the tip about not using keys. I was using keys and components were getting remounted on every route change. What's the best practice for .map() to generate <Route> components in terms of assigning keys?

@ReactTraining ReactTraining deleted a comment from steelx Apr 26, 2018

@lock lock bot locked as resolved and limited conversation to collaborators Jun 25, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.