Skip to content
Rudy Router - MVC style route async controller for react-redux applications.
JavaScript TypeScript CSS Shell
Branch: master
Clone or download

Latest commit

hedgepigdaniel Publish
 - @respond-framework/boilerplate@0.1.1-test.8
 - integration-tests@1.0.1-test.8
 - @respond-framework/rudy@0.1.1-test.8
 - @respond-framework/scroll-restorer@0.1.0-test.3
 - @respond-framework/types@0.1.1-test.4
Latest commit 7097b7a Dec 20, 2019

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode dx: set VSCode to automatically run eslint --fix on js/jsx/ts/tsx fil… Oct 23, 2019
config feat: scroll restoration (#65) Nov 13, 2019
docs/development docs(development): update the release instructions to something that … Sep 19, 2019
examples/react Chore: fix CI (#16) Jan 20, 2019
packages Publish Dec 20, 2019
scripts build: add ability to publish to git instead of NPM (#34) Jun 7, 2019
.editorconfig Lift eslint/prettier configurations up to the root package, set up sc… Aug 20, 2018
.eslintignore chore: move all eslint configuration into root configuration file Oct 23, 2019
.eslintrc.js chore: move all eslint configuration into root configuration file Oct 23, 2019
.gitignore chore: simplify eslint scripts Oct 23, 2019
.prettierignore refactor: Global setup for compiling/linting/testing/typechecking of … Oct 23, 2019
.prettierrc.js Lift eslint/prettier configurations up to the root package, set up sc… Aug 20, 2018
.travis.yml chore(ci): Replace Snyk with yarn audit (#21) Mar 7, 2019
HACKING.md dx: Add note in HACKING.md about type checking Oct 23, 2019
LICENSE Add new copyright notice (on top of the original one) claiming copyri… Oct 9, 2018
README.md feat(transformations): Various changes, documentation (#24) Apr 15, 2019
babel.config.js refactor: Global setup for compiling/linting/testing/typechecking of … Oct 23, 2019
jest.config.js refactor: Global setup for compiling/linting/testing/typechecking of … Oct 23, 2019
lerna.json Create github releases by default (#39) Jun 7, 2019
lint-staged.config.js fix: correct error in lint-staged config Oct 23, 2019
package.json chore: move all eslint configuration into root configuration file Oct 23, 2019
yarn.lock fix: update to released scroll-behavior API (#70) Dec 20, 2019

README.md

Rudy

Think of your app in terms of states, not routes or components. Connect your components and just dispatch Flux Standard Routing Actions!

Rudy is the successor to redux-first-router. Compared to RFR, there are many new features, and some breaking changes. It is a work in progress. The basic features work, but there are still bugs and some features are incomplete.

Motivation

Rudy is a library for creating a controller (as in Model View Controller) for redux based apps. It provides an abstraction for handling all the side effects and cross cutting concerns that tend to pollute React components and make apps difficult to understand and work with. Some of the things it can do:

  • Maintain a bi-directional mapping between the URL and Redux actions of your choice. As far as your app is concerned, URL changes are Redux actions, and routing state is Redux state. This mapping works the same way for server side and client side rendering.
  • Trigger callbacks defined on redux actions (including but not limited to route changes), which can do things like making API calls or other side effects. They can also delay URL changes until the required data is ready, and/or redirect to other routes.
  • Set the page title on route changes based on the redux state
  • Block page navigation depending on the redux state
  • Load code split reducers, controller code, and components for each route when necessary.

This library can help you build an app where:

  • URLs are defined exclusively in one place
  • Code that receives input (and dispatches Redux actions) is decoupled from code that triggers side effects (in response to Redux actions).
  • The entire app's state can be kept in Redux, and components only need to receive state from Redux, not also from other places.
  • Components can be pure functions, because they don't have to handle state and side effects.
  • The majority of the code (reducers, selectors, components) are pure functions and can be easily unit tested with no mocking necessary. The little that remains (the controller) is neatly concentrated in one place.

Usage

Install

yarn add @respond-framework/rudy

Basic example for React

// index.js
// The entrypoint is mostly standard react-redux, just note the call to configureStore() and the last line.

import React from 'react'
import { connect, Provider } from 'react-redux'
import ReactDOM from 'react-dom'

import configureStore from './configureStore'
import * as components from './components'

// App component
const App = ({ page }) => {
  const Component = components[page]
  return <Component />
}
const ConnectedApp = connect(({ page }) => ({ page }))(App)

// Redux setup
const { store, firstRoute } = configureStore()

function render() {
  ReactDOM.render(
    <Provider store={store}>
      <ConnectedApp />
    </Provider>,
    document.getElementById('root'),
  )
}

store.dispatch(firstRoute()).then(() => render())
// routes.js
// Routes are centrally defined along with their paths, along with much more that is not shown here for simplicity.

export default {
  HOME: '/',
  USER: '/user/:id',
}
// pageReducer.js
// A simple reducer maps path actions to component names. This makes it easy to dynamically import pages!

const components = {
  HOME: 'Home',
  USER: 'User',
  NOT_FOUND: 'NotFound',
}

export default (state = 'Home', action = {}) => components[action.type] || state
// configureStore.js
// Configures the router and inserts it into the Redux store.
// Both arguments in the exported function are optional.

import { applyMiddleware, combineReducers, compose, createStore } from 'redux'
import { createRouter } from '@respond-framework/rudy'

import routes from './routes'
import page from './pageReducer'

export default (preloadedState, initialEntries) => {
  const options = { initialEntries }
  const { reducer, middleware, firstRoute } = createRouter(routes, options)

  const rootReducer = combineReducers({ page, location: reducer })
  const middlewares = applyMiddleware(middleware)
  const enhancers = compose(middlewares)

  const store = createStore(rootReducer, preloadedState, enhancers)

  return { store, firstRoute }
}
// components.js
// A few trivial components which can now access location and any params through Redux!

import React from 'react'
import { connect } from 'react-redux'

// Home component
const Home = ({ visitUser }) => {
  const rndUserId = Math.floor(20 * Math.random())
  return (
    <div>
      <p>Welcome home!</p>
      <button type="button" onClick={() => visitUser(rndUserId)}>
        {`Visit user ${rndUserId}`}
      </button>
    </div>
  )
}

const ConnectedHome = connect(
  null,
  (dispatch) => ({
    visitUser: (userId) => dispatch({ type: 'USER', params: { id: userId } }),
  }),
)(Home)

// User component
const User = ({ goHome, userId }) => (
  <div>
    <p>{`User component: user ${userId}`}</p>
    <button type="button" onClick={() => goHome()}>
      Back
    </button>
  </div>
)

const ConnectedUser = connect(
  ({ location: { params } }) => ({ userId: params.id }),
  (dispatch) => ({ goHome: () => dispatch({ type: 'HOME' }) }),
)(User)

// 404 component
const NotFound = ({ pathname }) => (
  <div>
    <h3>404</h3>
    Page not found: <code>{pathname}</code>
  </div>
)
const ConnectedNotFound = connect(({ location: { pathname } }) => ({
  pathname,
}))(NotFound)

export {
  ConnectedHome as Home,
  ConnectedUser as User,
  ConnectedNotFound as NotFound,
}

The source code for this example can be found here.

Also see:

The Flux Standard Routing Action (FSRA)

The redux actions that Rudy synchronises with URLs have a particular shape and meaning.

const routes = {
  BLOB: {
    path: '/:namespace/:repo/blob/:ref/:path+',
  },
}

const url = {
  url:
    '/respond-framework/rudy/blob/master/README.md?unused=test#the-flux-standard-routing-action-fsra',
  state: {
    invisible: '12345',
  },
}

const action = {
  type: 'BLOB',
  params: {
    namespace: 'respond-framework',
    repo: 'rudy',
    ref: 'master',
    path: 'README.md',
  },
  query: {
    unused: 'test',
  },
  hash: 'the-flux-standard-routing-action-fsra',
  state: {
    invisible: '12345',
  },
}

actionToUrl(action) == { url, state: { invisible: '12345' } }
urlToAction({ url, state: { invisible: '12345' }) == action

Development

Pull requests are welcome! See HACKING for some simple instructions for getting started if you want to make an improvement. More detailed documentation about development is available in the development docs directory.

License

MIT

You can’t perform that action at this time.