hyperapp-router icon indicating copy to clipboard operation
hyperapp-router copied to clipboard

Hyperapp#2 support

Open sergey-shpak opened this issue 6 years ago • 2 comments

Hyperapp#2 Support (Updating)

Implemented with state-first in mind, simplicity and consistency to hav#1 router.

This Router follows simple concept of state-driven components (as hyperapp itself), the only one possible way to route component is to properly update application state (location). Router doesn't tightly depend on window.history anymore, that means you can use multiple applications at once.

Router internally uses 'path-to-regexp' syntax to compile paths this brings more routing power and consistency with other frameworks.

Quick start

Since routing depends on state, 'state.location' should be defined at first. Each 'Route' component should receive 'location' property to process routes. Link component is used to properly update 'state.location'.

import { app, h } from 'hyperapp'
import { location, Link, Route } from '@hyperapp/router'

app({
  init: [location, '/foo'],
  view: state => <div>
    <nav>
      <Link to='/foo'>Foo</Link>
      <Link to='/bar'>Bar</Link>
    </nav>
    <main>
      <Route path='/foo' location={ state.location }>
        <div>Foo</div>
      </Route>
      <Route path='/bar' location={ state.location }>
        <div>Bar</div>
      </Route>
    </main>
  </div>,
  container: document.body
})
  • Route 'render' property is more commonly used to render nested routes, more details at Route Params

Usage Examples

Base example

Using Route 'render' property and nested 'Route' component

import { app, h } from 'hyperapp'
import { location, Link, Route } from '@hyperapp/router'

const Home = () =>
  <div>Home</div>
const Blog = () =>
  <div>Blog</div>
const About = () =>
  <div>About</div>

app({
  init: [location, '/home'],
  view: state => <div>
    <nav><li>
      <Link to='/home'>Home</Link>
    </li><li>
      <Link to='/blog'>Blog</Link>
    </li><li>
      <Link to='/about'>About</Link>
    </li></nav>
    <main>
      <Route render={(props, Route) =>
        <Route path='/home' render={ Home } />
        || <Route path='/blog' render={ Blog } />
        || <Route path='/about' render={ About } />
      } location={ state.location } />
    </main>
  </div>,
  container: document.body
})
  • Please notice proper 'switch' usage with logical operator ||

Route Parameters

Using nested 'Route' component for nested routes, route context (to pass properties to nested components and routes), using route params

import { app, h } from 'hyperapp'
import { location, Link, Route } from '@hyperapp/router'

const Home = () =>
  <div>Home</div>

const Blog = ({ posts }) => {
  const list = Object.entries(posts)
  return <div>
    <h5>Blog posts</h5>
    <ul>{ list.map(([id, post]) =>
      <li><Link to={`/blog/${id}`} >{ id }</Link></li>
    )}</ul>
  </div>
}

const Post = ({ route }) => <div>
  <Link onClick={location.back}>Back</Link> Post: { route.params.id }
</div>

app({
  // State composition
  init: () => {
    const state = {
      posts: {
        'post1': { text: 'Post#1 Text' },
        'post2': { text: 'Post#1 Text' },
      }
    }
    return location(state, '/home')
  },
  view: state => <div>
    <nav><li>
      <Link to='/home'>Home</Link>
    </li><li>
      <Link to='/blog'>Blog</Link>
    </li></nav>
    <main>
      <Route render={(props, Route) =>
        <Route path='/blog' render={ (props, Route) => [
          <Blog posts={ props.route.context.state.posts } />,
          <Route path='/:id' render={ Post } />
        ]} />
        || <Route path='/home' render={ Home } />
      } location={ state.location } state={ state } />
    </main>
  </div>,
  container: document.body
})
  • Please notice 'location.back' action usage`

Redirects

Redirect as subscription

import { app, h } from 'hyperapp'
import { Redirect, location, Link, Route } from '@hyperapp/router'

const Home = () =>
  <div>Home</div>

const Blog = () =>
  <div>Blog</div>

app({
  init: [location, '/home'],
  view: state => <div>
    <nav><li>
      <Link to='/home'>Home</Link>
    </li><li>
      <Link to='/blog'>Blog</Link>
    </li></nav>
    <main>
      <Route render={(props, Route) =>
        <Route path='/home' render={ Home } />
        || <Route path='/blog' render={ Blog } />
      } location={ state.location } />
    </main>
  </div>,
  subscriptions: state => [
    <Redirect from='/home' to='/blog' location={ state.location } />
  ],
  container: document.body
})

No Match (Default)

import { app, h } from 'hyperapp'
import { Redirect, location, Link, Route } from '@hyperapp/router'

const Home = () =>
  <div>Home</div>

const NotFound = () =>
  <div>404</div>

app({
  init: [location, '/home'],
  view: state => <div>
    <nav><li>
      <Link to='/home'>Home</Link>
    </li><li>
      <Link to='/some-path'>Lost</Link>
    </li></nav>
    <main>
      <Route render={(props, Route) =>
        <Route path='/home' render={ Home } />
        || <Route render={ NotFound } />
      } location={ state.location } />
    </main>
  </div>,
  container: document.body
})

History

If you want to expose state.location and subscribe to window.history, use 'history' as subscription

import { app, h } from 'hyperapp'
import { history, location, Link, Route } from '@hyperapp/router'

const Home = () =>
  <div>Home</div>

const Blog= () =>
  <div>Blog</div>

app({
  init: [location, '/home'],
  view: state => <div>
    <nav><li>
      <Link to='/home'>Home</Link>
    </li><li>
      <Link to='/blog'>Blog</Link>
    </li></nav>
    <main>
      <Route render={(props, Route) =>
        <Route path='/home' render={ Home } />
        || <Route path='/blog' render={ Blog } />
      } location={ state.location } />
    </main>
  </div>,
  subscriptions: state => [
    history(state.location)
  ],
  container: document.body
})

Router#v2 API

location

location.back

location.go

history

Link

Route

Redirect

sergey-shpak avatar Dec 22 '18 10:12 sergey-shpak

For one I Really like The Consistency with the V1 API.

Good Job @sergey-shpak.

ThobyV avatar Jan 04 '19 18:01 ThobyV

  • installing router from github as "@hyperapp/router": "github:sergey-shpak/router#V2" builds package (npm prepare) but doesn't keep router dependencies, so if you're importing router as a 'module' please install required deps ('path-to-regexp')

sergey-shpak avatar Jan 07 '19 23:01 sergey-shpak