Skip to content

Instantly share code, notes, and snippets.

@BenjaminVerble
Forked from HenrikJoreteg/README.md
Created December 29, 2015 00:50
Show Gist options
  • Save BenjaminVerble/ea693d134059d80a518a to your computer and use it in GitHub Desktop.
Save BenjaminVerble/ea693d134059d80a518a to your computer and use it in GitHub Desktop.

Revisions

  1. @HenrikJoreteg HenrikJoreteg revised this gist Aug 11, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ Instead, with this approch, your app's current `pathname` is just another piece

    This also means that when doing server-side rendering of a redux app, you can just do:

    ```
    ```javascript
    var app = require('your/redux/app')
    var React = require('react')

  2. @HenrikJoreteg HenrikJoreteg revised this gist Aug 11, 2015. 3 changed files with 32 additions and 2 deletions.
    23 changes: 23 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    Why would you want to do this? Because you often don't need more. It's nice to not have to think about your "router" as this big special thing.

    Instead, with this approch, your app's current `pathname` is just another piece of state, just like anything else.

    This also means that when doing server-side rendering of a redux app, you can just do:

    ```
    var app = require('your/redux/app')
    var React = require('react')
    // pass the URL like any other piece of data
    React.renderToString(app.render({url: '/about'}))
    ```

    The [react-internal-nav](https://www.npmjs.com/package/react-internal-nav) component makes sure internal navigation clicks are captured, then we turn it into an `SET_URL` action dispatch.

    This example has extremely simple url matching with just a few `if` statements, but this could easily be elaborated on if you needed it, using some kind of lightweight string route matcher, such as:
    - https://www.npmjs.com/package/routes
    - https://github.com/Matt-Esch/http-hash
    - https://github.com/glassresistor/i40
    - https://github.com/bevacqua/ruta3

    While standalone history management tools exist, such as https://browserstate.github.io/history.js/demo/ or https://github.com/rackt/history they're concerned with proper handling the `state` part of `pushState` APIs. Personally, I don't want to put state there anyway and if you don't care about that part, the actual [browser support for plain pushState](http://caniuse.com/#search=pushState) is quite good.
    6 changes: 4 additions & 2 deletions containers - App.js
    Original file line number Diff line number Diff line change
    @@ -7,8 +7,10 @@ import * as reducers from '../reducers'
    import * as UrlActions from '../actions/UrlActions'

    export default class App {
    render(data) {
    let store = createStore(createStore(reducers))
    // note that we take data as an argument here
    render (data) {
    // ...and pass it through as initial state for server rendering
    let store = createStore(createStore(reducers), data)

    if (typeof window !== 'undefined') {
    window.addEventListener('popstate', () => {
    5 changes: 5 additions & 0 deletions containers - FooApp.js
    Original file line number Diff line number Diff line change
    @@ -22,6 +22,11 @@ export default class FooApp {
    }

    let page

    // more sophisticated url matching could easily be done with any of these:
    // https://github.com/Matt-Esch/http-hash
    // https://github.com/glassresistor/i40
    // https://github.com/bevacqua/ruta3

    if (url === '/') {
    page = (<h1>Home</h1>)
  3. @HenrikJoreteg HenrikJoreteg revised this gist Aug 11, 2015. 1 changed file with 1 addition and 12 deletions.
    13 changes: 1 addition & 12 deletions containers - FooApp.js
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,6 @@ import InternalNav from 'react-internal-nav'
    import AboutPage from '../components/about-page'
    import DataPage from '../components/data-page'
    import PersonDetail from '../components/person-detail'
    import OverlayPage from './overlay-page'
    import * as UrlActions from '../actions/UrlActions'

    export default class FooApp {
    @@ -23,11 +22,9 @@ export default class FooApp {
    }

    let page
    let style

    if (url === '/') {
    // render to URL as well
    setQueryString({q: query})
    page = (<h1>Home</h1>)
    } else if (url === '/about') {
    page = (<AboutPage/>)
    } else if (url === '/data') {
    @@ -47,14 +44,6 @@ export default class FooApp {
    }
    }

    if (page) {
    page = (
    <OverlayPage setUrl={actions.setUrl}>
    {page}
    </OverlayPage>
    )
    }

    return (
    <InternalNav onInternalNav={actions.setUrl}>
    <header>
  4. @HenrikJoreteg HenrikJoreteg revised this gist Aug 11, 2015. 4 changed files with 5 additions and 7 deletions.
    2 changes: 1 addition & 1 deletion url-actions.js → actions - UrlActions.js
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import { SET_URL } from '../constants/action-types'
    import { SET_URL } from '../constants/ActionTypes'

    function pushState(url) {
    if (url !== window.location.pathname) {
    File renamed without changes.
    8 changes: 3 additions & 5 deletions app.js → containers - App.js
    Original file line number Diff line number Diff line change
    @@ -2,9 +2,9 @@
    import React from 'react'
    import { createStore, combineReducers } from 'redux'
    import { Provider } from 'react-redux'
    import FooApp from './foo-app'
    import FooApp from './FooApp'
    import * as reducers from '../reducers'
    import * as UrlActions from '../actions/url-actions'
    import * as UrlActions from '../actions/UrlActions'

    export default class App {
    render(data) {
    @@ -15,9 +15,7 @@ export default class App {
    store.dispatch(UrlActions.setUrl(window.location.pathname))
    })

    // grab last known URL from localStorage if in iOS standalone mode
    const start = (navigator.standalone && localStorage.lastUrl) || window.location.pathname
    store.dispatch(UrlActions.setUrl(start))
    store.dispatch(UrlActions.setUrl(window.location.pathname))
    }

    return (
    2 changes: 1 addition & 1 deletion foo-app.js → containers - FooApp.js
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@ import AboutPage from '../components/about-page'
    import DataPage from '../components/data-page'
    import PersonDetail from '../components/person-detail'
    import OverlayPage from './overlay-page'
    import * as UrlActions from '../actions/url-actions'
    import * as UrlActions from '../actions/UrlActions'

    export default class FooApp {
    render() {
  5. @HenrikJoreteg HenrikJoreteg revised this gist Aug 11, 2015. 1 changed file with 1 addition and 4 deletions.
    5 changes: 1 addition & 4 deletions foo-app.js
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,6 @@ import InternalNav from 'react-internal-nav'
    import AboutPage from '../components/about-page'
    import DataPage from '../components/data-page'
    import PersonDetail from '../components/person-detail'
    import PersonList from '../components/person-list'
    import OverlayPage from './overlay-page'
    import * as UrlActions from '../actions/url-actions'

    @@ -60,15 +59,13 @@ export default class FooApp {
    <InternalNav onInternalNav={actions.setUrl}>
    <header>
    <nav role='navigation'>
    <a href='/'>HubFlow.pro</a>
    <a href='/'>Example.com</a>
    <span>
    <a href="/data">Notes</a>
    <a href="/about">About</a>
    <a href="/login">Login</a>
    </span>
    </nav>
    </header>
    <PersonList persons={persons} userData={userData} query={query}/>
    {page}
    </InternalNav>
    )
  6. @HenrikJoreteg HenrikJoreteg revised this gist Aug 11, 2015. No changes.
  7. @HenrikJoreteg HenrikJoreteg created this gist Aug 11, 2015.
    1 change: 1 addition & 0 deletions action-types.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    export const SET_URL = 'SET_URL'
    29 changes: 29 additions & 0 deletions app.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,29 @@
    /* global __IS_DEV__*/
    import React from 'react'
    import { createStore, combineReducers } from 'redux'
    import { Provider } from 'react-redux'
    import FooApp from './foo-app'
    import * as reducers from '../reducers'
    import * as UrlActions from '../actions/url-actions'

    export default class App {
    render(data) {
    let store = createStore(createStore(reducers))

    if (typeof window !== 'undefined') {
    window.addEventListener('popstate', () => {
    store.dispatch(UrlActions.setUrl(window.location.pathname))
    })

    // grab last known URL from localStorage if in iOS standalone mode
    const start = (navigator.standalone && localStorage.lastUrl) || window.location.pathname
    store.dispatch(UrlActions.setUrl(start))
    }

    return (
    <Provider store={store}>
    {() => <FooApp />}
    </Provider>
    )
    }
    }
    76 changes: 76 additions & 0 deletions foo-app.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,76 @@
    import React from 'react'
    import { bindActionCreators } from 'redux'
    import { Connector } from 'react-redux'
    import InternalNav from 'react-internal-nav'
    import AboutPage from '../components/about-page'
    import DataPage from '../components/data-page'
    import PersonDetail from '../components/person-detail'
    import PersonList from '../components/person-list'
    import OverlayPage from './overlay-page'
    import * as UrlActions from '../actions/url-actions'

    export default class FooApp {
    render() {
    return (
    <Connector>
    {this.renderChild}
    </Connector>
    )
    }

    renderChild({ personData: {persons}, dispatch, url }) {
    const actions = {
    ...bindActionCreators(UrlActions, dispatch)
    }

    let page
    let style

    if (url === '/') {
    // render to URL as well
    setQueryString({q: query})
    } else if (url === '/about') {
    page = (<AboutPage/>)
    } else if (url === '/data') {
    page = (<DataPage persons={persons}/>)
    } else {
    let match
    persons.some(person => {
    if (person.slug === url.slice(1)) {
    match = person
    return true
    }
    })
    if (match) {
    page = (<PersonDetail person={match} {...actions}/>)
    } else {
    page = (<h1>404</h1>)
    }
    }

    if (page) {
    page = (
    <OverlayPage setUrl={actions.setUrl}>
    {page}
    </OverlayPage>
    )
    }

    return (
    <InternalNav onInternalNav={actions.setUrl}>
    <header>
    <nav role='navigation'>
    <a href='/'>HubFlow.pro</a>
    <span>
    <a href="/data">Notes</a>
    <a href="/about">About</a>
    <a href="/login">Login</a>
    </span>
    </nav>
    </header>
    <PersonList persons={persons} userData={userData} query={query}/>
    {page}
    </InternalNav>
    )
    }
    }
    15 changes: 15 additions & 0 deletions url-actions.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    import { SET_URL } from '../constants/action-types'

    function pushState(url) {
    if (url !== window.location.pathname) {
    window.history.pushState({}, '', url)
    }
    }

    export function setUrl(text) {
    pushState(text)
    return {
    type: SET_URL,
    text
    }
    }