tj/react-enroute

No "indexRoute" equivalent

Closed this issue · 7 comments

Should it exist in react-enroute? I think it should as it's quite cheap to implement, and makes components easier to write (saves use of React.Children.count):

// without indexRoute
class Index extends Component {
  render() {
    const { children } = this.props
    if (React.Children.count(children) > 0) {
      return <div><Menu /><p>Please select a category</p></div>
    } else {
      return <div><Menu />{ children }</div>
    }
  }
}

ReactDOM.render(<Router {...state}>
  <Route path="/" component={Index} /></Router>, document.querySelector('#app'))


// with indexRoute
class Index extends Component {
  render() {
    const { children } = this.props
    return <div><Menu />{ children }</div>
  }
}

class IndexHome extends Component {
  render() {
    return <p>Please select a category</p>
  }
}

ReactDOM.render(<Router {...state}>
  <Route path="/" component={Index} />
  <IndexRoute path="/" component={IndexHome} /></Router>, document.querySelector('#app'))

What do you think?

tj commented

yep! I think though I'd prefer to have <Route component={...} /> or maybe just path="/" for the nested one, but that looks redundant

<Route …> with no path would only work in nested mode, it made me think about a new attribute to Route instead of a new component:

ReactDOM.render(<Router {...state}>
  <Route path="/" component={Index} index={IndexHome} /></Router>, document.querySelector('#app'))

What about this proposal?

Code is rather easy so I could propose a PR as soon as you think the API sounds cool.

tj commented

HmmMm when would you use it without nesting? Seems like it would be arbitrary since it only allows you to have one parent. Maybe component={...} should always be the child (or IndexRoute here) and parent={...}

Route without path looks simple and clean. I can provide PR (two lines change only). But we need to remove this assert. I think it's ok.

Workaround

My current workaround for this is to use a little HOC to render a default child if none exist.

defaultChild.js

/**
 * Render `DefaultChild` component as the child of `Container` if no other children.
 */

export default function defaultChild (Container, DefaultChild) {
  return function (props, context) {
    return (
      <Container {...props}>
        {
          React.Children.count(props.children)
          ? props.children
          : <DefaultChild {...props} />
        }
      </Container>
    )
  }
}

Router Usage Example

export default function AppRouter ({ history }) {
    return (
      <Router location={history.location.pathname}>
        <Route path='/' component={defaultChild(AppLayout, IndexPage)}>
          <Route path='user' component={defaultChild(UserLayout, UsersPage)} />
            <Route path=':userID' component={UserPage} />
          </Route>
        </Route>
        <Route path='*' component={defaultChild(AppLayout, NotFoundPage)} />
      </Router>
  )
}

Note: The catch-all * Route must be configured as a sibling (not a child) of the root Route. It can't be nested inside / since the * will match, thus the NotFound component will the child rendered instead of the intended IndexPage component.

Another alternative approach could be a HOC around the * component that toggles on

@timoxley Can I provide you more elegant solution? 😉

<Router location={...}>
  <Route path='/games' component={Container}>
    <Route path='' component={pages.GameList} />
    <Route path=':id' component={pages.Game} />
  </Route>
...
</Router>

@farwayer nice, ok so '' as the index route appears to work fine so long as the parent page isn't /.

So this doesn't seem to work:

<Route path='/' component={Container}>
    <Route path='' component={pages.GameList} />
</Route>

But this does:

<Route path='' component={Container}>
    <Route path='' component={pages.GameList} />
</Route>

as does:

<Route path='/games' component={Container}>
    <Route path='' component={pages.GameList} />
</Route>

Also doesn't affect wildcard:

<Route path='' component={Container}>
    <Route path='' component={pages.GameList} />
    <Route path='*' component={pages.NotFound} />
</Route>

Nice. Thanks.