Skip to content

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

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

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

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

acpower7 opened this issue Sep 22, 2016 · 22 comments

Comments

@acpower7
Copy link

acpower7 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
Copy link
Member

ryanflorence commented Sep 22, 2016

the basic example does this, doesn't it?

@timdorr timdorr closed this as completed Sep 22, 2016
@LukeAskew
Copy link

LukeAskew 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
Copy link

kachkaev 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
Copy link

kachkaev commented Feb 25, 2017

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

@ryanflorence
Copy link
Member

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
Copy link
Member

@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
Copy link

kachkaev 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
Copy link
Member

ryanflorence 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
Copy link

@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
Copy link
Member

there's a bug in switch #4578

@ryanflorence
Copy link
Member

ryanflorence 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
Copy link

kachkaev 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
Copy link
Member

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

@kachkaev
Copy link

kachkaev 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
Copy link
Member

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

@ankibalyan
Copy link

ankibalyan 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
Copy link

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
Copy link

Anima-t3d 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
Copy link

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
Copy link

viiiprock 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
Copy link

noinkling 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
Copy link

@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?

@remix-run remix-run 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.
Labels
None yet
Projects
None yet
Development

No branches or pull requests