HyperRouter
HyperRouter is a DSL wrapper for ReactRouter v4.x to provide client-side routing for Single Page Applications (SPA). As the user changes "pages" instead of reloading from the server your App will mount different components.

This Page Under Construction

Usage

1
class AppRouter
2
include Hyperstack::Component
3
include Hyperstack::Router::Helpers
4
include Hyperstack::Router
5
6
render(DIV) do
7
UL do
8
LI { Link('/') { 'Home' } }
9
LI { Link('/about') { 'About' } }
10
end
11
Route('/', exact: true, mounts: Home)
12
Route('/about', mounts: About)
13
end
14
end
15
16
class Home
17
include Hyperstack::Component
18
render(DIV) do
19
H2 { 'Home' }
20
end
21
end
Copied!

DSL

Router

This is the Router module which you include in your top level component:
1
class MyRouter
2
include Hyperstack::Component
3
include Hyperstack::Router
4
end
Copied!
With the base Router class, you can also specify the history you want to use.
This can be done either using a macro:
1
class MyRouter
2
include Hyperstack::Component
3
include Hyperstack::Router
4
5
history :browser # this is the default option if no other is specified
6
end
Copied!
The macro accepts three options: :browser, :hash, or :memory.
Or by defining the history method:
1
class MyRouter
2
include Hyperstack::Component
3
include Hyperstack::Router
4
5
def history
6
self.class.browser_history
7
end
8
end
Copied!

Rendering a Router

Use the render macro as normal. Note you cannot redefine the render instance method in a Router componenent
1
class MyRouter
2
...
3
4
render(DIV) do
5
H1 { 'Hello world!' }
6
end
7
end
Copied!

Routes

Routes are defined with special pseudo components you call inside the router/components. The router determines which of the routes to actually mount based on the current URL.
1
class MyRouter
2
...
3
4
render(DIV) do
5
Route('/', mounts: HelloWorld)
6
end
7
end
8
9
class HelloWorld
10
render do
11
H1 { 'Hello world!' }
12
end
13
end
Copied!
The Route method takes a url path, and these options:
  • mounts: Component The component you want to mount when routed to
  • exact: Boolean When true, the path must match the location exactly
  • strict: Boolean When true, the path will only match if the location and path both have/don't have a trailing slash
The Route method can also take a block instead of the mounts option.
1
class MyRouter
2
...
3
4
render(DIV) do
5
Route('/', exact: true) do
6
H1 { 'Hello world!' }
7
end
8
end
9
end
Copied!
The block will be given the match, location, and history data:
1
class MyRouter
2
...
3
4
render(DIV) do
5
Route('/:name') do |match, location, history|
6
H1 { "Hello #{match.params[:name]} from #{location.pathname}, click me to go back!" }
7
.on(:click) { history.go_back }
8
end
9
end
10
end
Copied!
  • The Hyperstack::Router::Helpers is useful for components mounted by the router.
  • This automatically sets the match, location, and history params,
    and also gives you instance methods with those names.
  • You can use either params.match or just match.
    and gives you access to the Route method and more.
  • This allows you to create inner routes as you need them.
1
class MyRouter
2
include Hyperstack::Component
3
include Hyperstack::Router::Helpers
4
include Hyperstack::Router
5
6
render(DIV) do
7
Route('/:name', mounts: Greet)
8
end
9
end
10
11
class Greet
12
include Hyperstack::Component
13
include Hyperstack::Router::Helpers
14
15
render(DIV) do
16
H1 { "Hello #{match.params[:foo]}!" }
17
Route(match.url, exact: true) do
18
H2 { 'What would you like to do?' }
19
end
20
Route("#{match.url}/:activity", mounts: Activity)
21
end
22
end
23
24
class Activity
25
include Hyperstack::Component
26
include Hyperstack::Router::Helpers
27
include Hyperstack::Router
28
29
render(DIV) do
30
H2 { params.match.params[:activity] }
31
end
32
end
Copied!
Normally routes will always render alongside sibling routes that match as well.
1
class MyRouter
2
...
3
4
render(DIV) do
5
Route('/goodbye', mounts: Goodbye)
6
Route('/:name', mounts: Greet)
7
end
8
end
Copied!

Switch

Going to /goodbye would match /:name as well and render Greet with the name param with the value 'goodbye'. To avoid this behavior and only render one matching route at a time, use a Switch component.
1
class MyRouter
2
...
3
4
render(DIV) do
5
Switch do
6
Route('/goodbye', mounts: Goodbye)
7
Route('/:name', mounts: Greet)
8
end
9
end
10
end
Copied!
Now, going to /goodbye would match the Goodbye route first and only render that component.
Links are provided by both the Hyperstack::Router and Hyperstack::Router::Helper modules.
The Link method takes a url path, and these options:
  • search: String adds the specified string to the search query
  • hash: String adds the specified string to the hash location
    it can also take a block of children to render inside it.
1
class MyRouter
2
...
3
4
render(DIV) do
5
Link('/Gregor Clegane')
6
7
Route('/', exact: true) { H1() }
8
Route('/:name') do |match|
9
H1 { "Will #{match.params[:name]} eat all the chickens?" }
10
end
11
end
12
end
Copied!
NavLinks are the same as Links, but will add styling attributes when it matches the current url
  • active_class: String adds the class to the link when the url matches
  • active_style: String adds the style to the link when the url matches
  • active: Proc A proc that will add extra logic to determine if the link is active
1
class MyRouter
2
...
3
4
render(DIV) do
5
NavLink('/Gregor Clegane', active_class: 'active-link')
6
NavLink('/Rodrik Cassel', active_style: { color: 'grey' })
7
NavLink('/Oberyn Martell',
8
active: ->(match, location) {
9
match && match.params[:name] && match.params[:name] =~ /Martell/
10
})
11
12
Route('/', exact: true) { H1() }
13
Route('/:name') do |match|
14
H1 { "Will #{match.params[:name]} eat all the chickens?" }
15
end
16
end
17
end
Copied!

Pre-rendering

Pre-rendering is automatically taken care for you under the hood.

Setup

To setup HyperRouter:
  • Install the gem
  • Your page should render your router as its top-level-component (first component to be rendered on the page) - in the example below this would be AppRouter
  • You will need to configure your server to route all unknown routes to the client-side router (Rails example below)

With Rails

Assuming your router is called AppRouter, add the following to your routes.rb
1
root 'Hyperstack#AppRouter' # see note below
2
match '*all', to: 'Hyperstack#AppRouter', via: [:get] # this should be the last line of routes.rb
Copied!
Note:
root 'Hyperstack#AppRouter' is shorthand which will automagically create a Controller, View and launch AppRouter as the top-level Component. If you are rendering your Component via your own COntroller or View then ignore this line.

Example

Here is the basic JSX example that is used on the react-router site
1
import React from 'react'
2
import {
3
BrowserRouter as Router,
4
Route,
5
Link
6
} from 'react-router-dom'
7
8
const BasicExample = () => (
9
<Router>
10
<div>
11
<ul>
12
<li><Link to="/">Home</Link></li>
13
<li><Link to="/about">About</Link></li>
14
<li><Link to="/topics">Topics</Link></li>
15
</ul>
16
17
<hr/>
18
19
<Route exact path="/" component={Home}/>
20
<Route path="/about" component={About}/>
21
<Route path="/topics" component={Topics}/>
22
</div>
23
</Router>
24
)
25
26
const Home = () => (
27
<div>
28
<h2>Home</h2>
29
</div>
30
)
31
32
const About = () => (
33
<div>
34
<h2>About</h2>
35
</div>
36
)
37
38
const Topics = ({ match }) => (
39
<div>
40
<h2>Topics</h2>
41
<ul>
42
<li><Link to={`${match.url}/rendering`}>Rendering with React</Link></li>
43
<li><Link to={`${match.url}/components`}>Components</Link></li>
44
<li><Link to={`${match.url}/props-v-state`}>Props v. State</Link></li>
45
</ul>
46
47
<Route path={`${match.url}/:topicId`} component={Topic}/>
48
<Route exact path={match.url} render={() => (
49
<h3>Please select a topic.</h3>
50
)}/>
51
</div>
52
)
53
54
const Topic = ({ match }) => (
55
<div>
56
<h3>{match.params.topicId}</h3>
57
</div>
58
)
59
60
export default BasicExample
Copied!
And here is the same example in Hyperstack:
1
class BasicExample
2
include Hyperstack::Component
3
include Hyperstack::Router::Helpers
4
include Hyperstack::Router
5
6
render(DIV) do
7
UL do
8
LI { Link('/') { 'Home' } }
9
LI { Link('/about') { 'About' } }
10
LI { Link('/topics') { 'Topics' } }
11
end
12
13
Route('/', exact: true, mounts: Home)
14
Route('/about', mounts: About)
15
Route('/topics', mounts: Topics)
16
end
17
end
18
19
class Home
20
include Hyperstack::Component
21
include Hyperstack::Router::Helpers
22
23
render(DIV) do
24
H2 { 'Home' }
25
end
26
end
27
28
class About
29
include Hyperstack::Component
30
include Hyperstack::Router::Helpers
31
32
render(:div) do
33
H2 { 'About' }
34
end
35
end
36
37
class Topics
38
include Hyperstack::Component
39
include Hyperstack::Router::Helpers
40
41
render(DIV) do
42
H2 { 'Topics' }
43
UL() do
44
LI { Link("#{match.url}/rendering") { 'Rendering with React' } }
45
LI { Link("#{match.url}/components") { 'Components' } }
46
LI { Link("#{match.url}/props-v-state") { 'Props v. State' } }
47
end
48
Route("#{match.url}/:topic_id", mounts: Topic)
49
Route(match.url, exact: true) do
50
H3 { 'Please select a topic.' }
51
end
52
end
53
end
54
55
class Topic
56
include Hyperstack::Component
57
include Hyperstack::Router::Helpers
58
59
render(:div) do
60
H3 { match.params[:topic_id] }
61
end
62
end
Copied!