Giter Site home page Giter Site logo

redux-little-router's Introduction

Project Deprecated

As of December 10, 2018, this project is no longer under active maintenance. There are a few reasons we at Formidable made this decision:

  • React Router and its ecosystem have solved many of the problems this library was originally created for.
  • There are other alternatives that store React Router state in Redux, so you get the benefits of their view layer while keeping router state in your store. We recommend connected-react-router.
  • React Router v4's component APIs are more suited to presentational / connected components than prior versions. With that, it becomes more ergonomic to use those components even when routing with Redux.
  • We are hard at work at other new and exciting open source!

We appreciate all of the effort our maintainers and collaborators in the community have put into this project. We're proud to have made the world of Redux routing a little bit better with all of you. ๐ŸŽ‰

redux-little-router

Build Status codecov npm

redux-little-router is a tiny router for Redux applications that lets the URL do the talking.

The router follows three basic principles:

  • The URL is just another member of the state tree.
  • URL changes are just plain actions.
  • Route matching should be simple and extendable.

While the core router does not depend on any view library, it provides flexible React bindings and components.

Why another router?

To understand why redux-little-router exists, check out our blog series, "Let the URL do the Talking":

Part 1 Part 2 Part 3

While React Router is a great, well-supported library, it hoards URL state within the view layer and makes certain Redux patterns difficult, if not impossible. This chart outlines a major issue in accessing URL state from outside of React Router.

react-router-redux is meant only to enable time-travel debugging in React Router and doesn't allow you to safely access URL state. redux-router, while allowing you to access URL state, is experimental, lags behind React Router releases, and recommends react-router-redux in its README.

redux-little-router makes URL state a first-class citizen of your Redux store and abstracts cross-browser navigation and routing into a pure Redux API.

Redux usage

To hook into Redux applications, redux-little-router uses a store enhancer that wraps the history module and adds current and previous router state to your store. The enhancer listens for location changes and dispatches rich actions containing the URL, parameters, and any custom data assigned to the route. redux-little-router also adds a middleware that intercepts navigation actions and calls their equivalent method in history.

Wiring up the boilerplate

The following is an example of a redux-little-router setup that works for browser-rendered applications. For a server rendering example, check out our advanced docs.

import { combineReducers, compose, createStore, applyMiddleware } from 'redux';
import { routerForBrowser } from 'redux-little-router';

import yourReducer from './your-app';

// Define your routes in a route-to-anything hash like below.
// The value of the route key can be any serializable data,
// including an empty object.

// This data gets attached to the `router.result` key of the state
// tree when its corresponding route is matched and dispatched.
// Useful for page titles and other route-specific data.

// Uses https://github.com/snd/url-pattern for URL matching
// and parameter extraction.
const routes = {
  '/messages': {
    title: 'Message'
  },
  '/messages/:user': {
    title: 'Message History'
  },
  // You can also define nested route objects!
  // Just make sure each route key starts with a slash.
  '/': {
    title: 'Home',
    '/bio': {
      title: 'Biographies',
      '/:name': {
        title: 'Biography for:'
      }
    }
  }
};

// Install the router into the store for a browser-only environment.
// routerForBrowser is a factory method that returns a store
// enhancer and a middleware.
const { reducer, middleware, enhancer } = routerForBrowser({
  // The configured routes. Required.
  routes,
  // The basename for all routes. Optional.
  basename: '/example'
});

const clientOnlyStore = createStore(
  combineReducers({ router: reducer, yourReducer }),
  initialState,
  compose(enhancer, applyMiddleware(middleware))
);

Often, you'll want to update state or trigger side effects after loading the initial URL. To maintain compatibility with other store enhancers (particularly ones that handle side effects, like redux-loop or redux-saga), we require this optional initial dispatch to happen in client code by doing the following:

import { initializeCurrentLocation } from 'redux-little-router';

// ...after creating your store
const initialLocation = store.getState().router;
if (initialLocation) {
  store.dispatch(initializeCurrentLocation(initialLocation));
}

Provided actions and state

redux-little-router provides the following action creators for navigation:

import { push, replace, go, goBack, goForward } from 'redux-little-router';

// `push` and `replace`
//
// Equivalent to pushState and replaceState in the History API.
// If you installed the router with a basename, `push` and `replace`
// know to automatically prepend paths with it. Both action creators
// accept string and object arguments.
push('/messages');

// Parsed query string stored in the `query` field of router state
push('/messages?filter=business');

// Provided query object stringified into the `search` field of router state
replace({
  pathname: '/messages',
  query: {
    filter: 'business'
  }
});

// Optional second argument accepts a `persistQuery` field. When true,
// reuse the query object from the previous location instead of replacing
// or emptying it.
push(
  {
    pathname: '/messages',
    query: {
      filter: 'business'
    }
  },
  {
    persistQuery: true
  }
);

// Navigates forward or backward a specified number of locations
go(3);
go(-6);

// Equivalent to the browser back button
goBack();

// Equivalent to the browser forward button
goForward();

// Creates a function that blocks navigation with window.confirm when returning a string.
// You can customize how the prompt works by passing a `historyOptions` option with a
// `getUserConfirmation` function to `routerForBrowser`, `routerForExpress`, etc.
// See https://www.npmjs.com/package/history#blocking-transitions
block((location, action) => {
  if (location.pathname === '/messages') {
    return 'Are you sure you want to leave the messages view?';
  }
});

// Removes the previous `block()`.
unblock();

Note: if you used the vanilla action types prior to v13, you'll need to migrate to using the public action creators.

These actions will execute once dispatched. For example, here's how to redirect using a thunk:

import { push } from 'redux-little-router';

export const redirect = href => dispatch => {
  dispatch(push(href));
};

On location changes, the store enhancer dispatches a LOCATION_CHANGED action that contains at least the following properties:

// For a URL matching /messages/:user
{
  pathname: '/messages/a-user-has-no-name',
  route: '/messages/:user',
  params: {
    user: 'a-user-has-no-name'
  },
  query: { // if your `history` instance uses `useQueries`
    some: 'thing'
  },
  search: '?some=thing',
  result: {
    arbitrary: 'data that you defined in your routes object!'
    parent: { // for nested routes only
      // contains the result of the parent route,
      // which contains each other parent route's
      // result recursively
    }
  }
}

Your custom middleware can intercept this action to dispatch new actions in response to URL changes.

The reducer consumes this action and adds the following to the root of the state tree on the router property:

{
  pathname: '/messages/a-user-has-no-name',
  route: '/messages/:user',
  params: {
    user: 'a-user-has-no-name'
  },
  query: {
    some: 'thing'
  },
  search: '?some=thing',
  result: {
    arbitrary: 'data that you defined in your routes object!',
    parent: { /* the parent route's result */ },
  },
  previous: {
    pathname: '/messages',
    route: '/messages',
    params: {},
    query: {},
    result: {
      more: 'arbitrary data that you defined in your routes object!'
      parent: { /* the parent route's result */ }
    }
  }
}

Your custom reducers or selectors can derive a large portion of your app's state from the URLs in the router property.

React bindings and usage

redux-little-router provides the following to make React integration easier:

  • A <Fragment> component that conditionally renders children based on current route and/or location conditions.
  • A <Link> component that sends navigation actions to the middleware when tapped or clicked. <Link> respects default modifier key and right-click behavior. A sibling component, <PersistentQueryLink>, persists the existing query string on navigation.

Instances of each component automatically connect() to the router state with react-redux.

You can inspect the router state in any child component by using connect():

const mapStateToProps = state => ({ router: state.router });
export default connect(mapStateToProps)(YourComponent);

<Fragment>

Think of <Fragment> as the midpoint of a "flexibility continuum" that starts with raw switch statements and ends with React Router v3's <Route> component. Fragments can live anywhere within the React tree, making split-pane or nested UIs easy to work with.

The simplest fragment is one that displays when a route is active:

<Fragment forRoute="/home/messages/:team">
  <p>This is the team messages page!</p>
</Fragment>

You can also match a fragment against anything in the current location object:

<Fragment withConditions={location => location.query.superuser}>
  <p>Superusers see this on all routes!</p>
</Fragment>

You can use withConditions in conjunction with forRoute to set strict conditions for when a <Fragment> should display.

To show a Fragment when no other Fragments match a route, use <Fragment forNoMatch />.

<Fragment> lets you nest fragments to match your UI hierarchy to your route hierarchy, much like the <Route> component does in react-router@v3. Given a URL of /about/bio/dat-boi, and the following elements:

<Fragment forRoute="/about">
  <div>
    <h1>About</h1>
    <Fragment forRoute="/bio">
      <div>
        <h2>Bios</h2>
        <Fragment forRoute="/dat-boi">
          <div>
            <h3>Dat Boi</h3>
            <p>Something something whaddup</p>
          </div>
        </Fragment>
      </div>
    </Fragment>
  </div>
</Fragment>

...React will render:

<div>
  <h1>About</h1>
    <div>
      <h2>Bios</h2>
        <div>
          <h3>Dat Boi</h3>
          <p>Something something whaddup<p>
        </div>
    </div>
</div>

<Fragment> makes basic component-per-page navigation easy:

<Fragment forRoute="/">
  <div>
    <Fragment forRoute="/">
      <Home />
    </Fragment>
    <Fragment forRoute="/about">
      <About />
    </Fragment>
    <Fragment forRoute="/messages">
      <Messages />
    </Fragment>
    <Fragment forRoute="/feed">
      <Feed />
    </Fragment>
  </div>
</Fragment>

<Link>

Using the <Link> component is simple:

<Link className="anything" href="/yo">
  Share Order
</Link>

Alternatively, you can pass in a location object to href. This is useful for passing query objects:

<Link
  className="anything"
  href={{
    pathname: '/home/messages/a-team?test=ing',
    query: {
      test: 'ing'
    }
  }}
>
  Share Order
</Link>

To change how <Link> renders when its href matches the current location (i.e. the link is "active"), use activeProps. For example, you can add className to activeProps to use a different CSS class when the link is active:

<Link
  href="/wat"
  className="normal-link"
  activeProps={{ className: 'active-link' }}
>
  Wat
</Link>

<Link> takes an optional valueless prop, replaceState, that changes the link navigation behavior from pushState to replaceState in the History API.

Use with immutable

redux-little-router supports the use of immutable.js in tandem with an immutable-aware combineReducers function like provided by redux-immutable. To use it, you will need to import the immutable version of the router or component you want to use. For instance,

import { immutableRouterForBrowser, ImmutableLink } from 'redux-little-router/es/immutable';
import { combineReducers } from 'redux-immutable';

const { reducer, enhancer, middleware } = immutableRouterForBrowser({ routes });

const store = createStore(
  combineReducers({ router: reducer, ... }),
  ...
);

Depending on your environment, you might need to modify the import statement further. In that case, here are some tips:

// works: ESM (preferred for webpack2+)
import { immutableRouterForBrowser } from 'redux-little-router/es/immutable';

// works: CJS (preferred for webpack1 or Node.js)
import { immutableRouterForBrowser } from 'redux-little-router/lib/immutable';

// DOESN'T WORK
import { immutableRouterForBrowser } from 'redux-little-router/immutable';

Environment

redux-little-router requires an ES5 compatible environment (no IE8).

Stability

We consider redux-little-router to be stable. Any API changes will be incremental.

Versioning

redux-little-router follows strict semver. Don't be alarmed by the high version number! Major version bumps represent any breaking change, no matter how small, and do not represent a major shift in direction. We strive to make breaking changes small and compartmentalized.

Contributing

We welcome community contributions! We have standardized our dev experience on yarn so make sure to have that installed.

$ git clone [email protected]:FormidableLabs/redux-little-router.git
$ cd redux-little-router
$ yarn install

After any changes and before a PR, make sure to pass our build and quality checks:

$ yarn run build
$ yarn run check

When ready for release, we use an npm version workflow:

$ npm version <major|minor|patch|ACTUAL_VERSION_NUMBER>
$ npm publish
$ git push && git push --tags

After publishing, consider drafting some release notes to let the world know about all the great new features!

Community

redux-little-router's People

Contributors

adregan avatar aweary avatar baer avatar bion avatar chrisbolin avatar divmain avatar gertsonderby avatar kevanstannard avatar kinware avatar koleok avatar lucretiel avatar michaelhogg avatar michaeljonathanblack avatar mmcgahan avatar neolegends avatar opavader avatar parkerziegler avatar ryan-roemer avatar scottnonnenberg avatar stefvhuynh avatar stuk avatar tptee avatar viniter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-little-router's Issues

Routes need to be defined without leading `/`

The routes as defined on the home-page doesn't work:

const routes = {
  '/': {
    title: 'Home'
  },
  '/messages': {
    title: 'Message'
  },
  '/messages/:user': {
    title: 'Message History'
  }
};

You need to define them without the leading slash:

const routes = {
  '/': {
    title: 'Home'
  },
  'messages': {
    title: 'Message'
  },
  'messages/:user': {
    title: 'Message History'
  }
};

This issue was observed after upgrading to the newest version (7.0.0).

Calm down the robot

In preparation for the bot: label this a "bug"

This is an absolutely awesome project, and I can tell it's going to gain traction quickly... but the gitmagic-bot (at least with its current settings) is really going to discourage community involvement.

I'd like to kindly suggest turning it down a notch or two.

Enhance replaceReducer or don't enhance reducer

Hi,

When we call replaceReducer on store, the routerReducer is not injected.
I think you should enhance replaceReducer to re-inject routerReducer or simply don't enhance reducer and ask to users to manually add routerReducer in they reducer.

Note that this is not blocking because the package expose the routerReducer so if someone else have trouble with replaceReducer, you just have to inject the routerReducer yourself :

import { routerReducer } from 'redux-little-router';

store.replaceReducer(combineReducer({
  router: routerReducer
}));

Core | Nested relatives fragments with root involved

Hi,

I think this is a bug.
I have those routes :

export default {
  '/': {
    title: 'HOME',
    '/toto': {
      title: 'TOTO',
    },
    '/tvshow/:id': {
      title: 'TVSHOW',
    },
  },
}

When I use this root Component it works :

  <div>
    <Search />
    <RelativeFragment forRoute="/toto">
      <Results />
    </RelativeFragment>
  </div>

When i use this root Component it doesn't work :

  <RelativeFragment forRoute="/">
    <Search />
    <RelativeFragment forRoute="/toto">
      <Results />
    </RelativeFragment>
  </RelativeFragment>

(I assume It works when the Results Component is printed when i go to /toto)

Fragments don't update upon Link navigation

Updating the Redux store router state by clicking a Link component does not force Fragment components to update. That is, clicking a link causes the page's location to change without causing changes to render.

My quick and dirty fix for my application is to call forceUpdate() via a subscriber whenever the Redux store changes. Initializing a subscriber either in the provider or in each fragment seems a likely permanent fix, but where?

Add `replaceRoutes` on store

Hi,

Have you ever though about a replaceRoutes method on the store ?
The case study I have in mind is code splitting. In fact this case study is not only in my mind :
I am working on a web app (Typescript / React / Redux) and I'm currently switching from react-router-redux to redux-little-router (because it will simplify a lot of stuff). With react-router the routes object wasn't split because react-router need all routes to boot.
So with redux-little-router, I can do the same thing but I feel that maybe a replaceRoutes is possible.
What we could get is something like this :

  • You first pass a initial route object to redux-little-router with one (or more) route ending with * (like myapp/private/*).
  • Then in the render that match this route, we fetch a some new routes, compute a new routes object by replacing the * by some sub routes and then call store.replaceRoutes(newRoutes) that replace the route object internally and dispatch a route event.

This would allow to:

  • expose only public routes of the app
  • isolate routes in each split part.
  • make hot reloading possible

Expose onClick handler separate from Link's <a>

We may want access to the same onClick handler that is passed to the <a> tag here. This should include all location normalization, query-state persistence, etc. and will allow non-a elements to interact with the router in a first-class way.

Fragments do not get updated

Hi,

I'm using the latest version 8.0.0.

I create my store like this:

let store = createStore(
    rootReducer,
    initialState,
    createStoreWithRouter({
        routes,
        pathname: '/',
        forServerRender: typeof window == 'undefined'
    })
);

And my root element is:

<RouterProvider store={store}>
    <Provider store={store}>
        <App />
    </Provider>
</RouterProvider>

I have some testing elements like this:

<div>
    <Link href='/messages'>Go messaging!</Link>
    <Fragment forRoute='/messages'>
        <div>
            We're messaging!
            <Link href='/'>Go home!</Link>
        </div>
    </Fragment>
</div>

When I click the link the URL changes but the fragment stays the same untill I manually force a rerender.

PS: Also it looks like Fragment can only have one child at max, why is that?

Thanks,
Marc

Documentation / Demo | Redux provider

Hello there,

When I read the doc, I assume that redux-little-router/RouterProvider needs the child component to be wrapped in the redux/Provider :

redux-little-router assumes and requires that your root component is wrapped in <Provider> from react-redux. 

When I read the demo, this is the other way around :

export default store => Root =>
  <Provider store={store}>
    <RouterProvider store={store}>
      <Root />
    </RouterProvider>
  </Provider>;

So my question is : What is the best way ?

And if you can update either the documentation either the demo code it'll be nice :)
I can do this part in a PR if you want to.

providerRouter/RouteProvider component must connected to router

It isn't really documented that the component made with provideRouter or RouterProvider must be connected to redux with a mapStateToProps function that returns an object containing router. The docs mention that it can be used to get at router state, not that it's mandatory.

This should probably make the example something like:

const store = createYourStore();

const App = () => {
  return (
    <RouterProvider store={ store }>
      <YourComponent />
    </RouterProvider>
  );
};

const ConnectedApp = connect(
  state => ({
    router: state.router
  })
)(App);

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

Split out from #35

404 Routes

Im wondering if there is a simplified method for checking all routes and 404 routes, I have found the following to work, but it would be ideal to have 404 capability available via a fragment:

All routes (Not sure why this works actually):

<Fragment forRoute=''>
  <p>Matches all routes</p>
</Fragment>

No route matches:

const App = (
  context: {
    router: RouterContext
  }
) => {
  const { matchRoute } = context.router.store;
  const matchResult = matchRoute(location.pathname);
  if(!matchResult) {
    return( <div> 404 error </div>)
  } else {
   return( <div> Application </div>)
 }
}

Nested routing API

Thought dump (more to come):

  • backwards compatible
  • nested route definitions should be a plain nested object
  • introduce <RelativeFragment> (<NestedFragment>?)

Reducer is called twice when a link is clicked

Hi

I have a dummy reducer that print out the state:

export default (state, action) => {
  console.log('%s', JSON.stringify(state));
  return state;
}

and the routes and store configure like the following,

const routes = {
  '/home': {
    title: 'Message'
  },
  '/cheese': {
    title: 'cheese'
  },
  '/dog': {
    title: 'dog'
  },
  '/cat': {
    title: 'cat'
  }
};

const store = createStore(reducer, {}, createStoreWithRouter({
  routes,
  pathname: '/home'
}));

and a React component that display some links,

const Demo = ({ router }) => {
  return <div>
    <Link href='/cheese'>Cheese</Link>
    <Link href='/dog'>Dog</Link>
    <Link href='/cat'>Cat</Link>
    <Link href='/hipster'>Hipster</Link>
  </div>
};

and the reducer will be called twice (duplicated log messages) every time I clicked on a Link. Did I miss anything here?

Documentation on exposed actions.

The readme only mentions LOCATION_CHANGED. It'd be helpful if there was documentation on the other actions exposed and what kind of payload they expect.

Fragment navigation bug

Migrating from react-router, I expect to be able to do something like:

<Fragment forRoute="/">
  <App />
</Fragment>
<Fragment forRoute="/help">
  <Help />
</Fragment>

And then have a link elsewhere:

<Link href="/help">Help</Link>

Clicking on that link would update the URL and change the view. Right now, it only updates the URL, so I'm clearly missing something. Unfortunately, the tests are mostly focused on the unit level, so it's hard for me to get a big picture view of how things connect.

Something I've really appreciated from other projects is the inclusion of an examples directory.

Documentation that outlines differences from other routing solutions

This includes:

  • react-router-redux
  • redux-router

Can be taken from:

http://formidable.com/blog/2016/07/11/let-the-url-do-the-talking-part-1-the-pain-of-react-router-in-redux/
http://formidable.com/blog/2016/07/19/let-the-url-do-the-talking-part-2-bargaining-and-acceptance-with-redux-and-react-router/
http://formidable.com/blog/2016/07/25/let-the-url-do-the-talking-part-3-empower-the-url-with-redux-little-router/

There should be a simple explanation of the use-case on when redux-little-router should be used vs above two.

I am sure this is planned, but creating this issue so people can subscribe.

createStoreWithRouter pathname not optional

In createStoreWithRouter the docs say:

pathname: '/home', // optional, the beginning URL

I don't think that is accurate, as if you comment out pathname:

create-matcher.js:24 Uncaught TypeError: Cannot read property 'split' of undefined

Split out from #35

Question: Handling basic page-style routing

How would we go about handling fairly basic page style route handling when a user initially visits the application. Routes such as (home, about, feed, messages, etc.). I feel like in React Router, I can specify those fairly easily by doing something such as:

<Route path="/" component={App}>
  <Route path="home" component={Home}/>
  <Route path="about" component={About}/>
  <Route path="about" component={Messages}/>
  <Route path="about" component={Feed}/>
</Route>

In redux-little-router would I simply have a singular parent component that conditionally renders children (Home, About, Messages, Feed) based on URL state? If so, are there any examples of this. If not, how would I go about doing something like this.

Thank you.

Support for createHashHistory

I'm interested in wanting to use this library for some projects I'm involved in moving forward. It looks like it currently only uses history's createBrowserHistory with HTML5 history. There are some situations beyond my control where I need to use hash routes, which is holding me back from jumping head first into this library. Also I'm not sure about the implementation details but I'm assuming with createMemoryHistory this could also be used with React Native?

Suggest offering a `Provider`-esque React element

For top-level navigation between pages, I liked this pattern from react-router:

<Router>
  <Route />
  <Route />
</Router>

With redux-little-router it's a bit more annoying. Since you can't easily have multiple children floating about, I have to use a div, something like:

const Foo = (
  <div>
    <Fragment />
    <Fragment />
  </div>
);

const Bar = provideRouter({ store, history })(Foo);

const Root = () => (
  <Provider store={store}>
    <Bar />
  </Provider>
);

Sadly, Redux' Provider doesn't allow for multiple children, which would make this a bit cleaner.

It would be cool to have something like:

<Provider store={store}>
  <RouterProvider store={store} history={history}>
    <Fragment />
    <Fragment />
  </RouterProvider>
</Provider>

I think having the Higher-Order Function is a good idea, but think that the element might provide a common usecase.

Suggest clarifying if initialStateForSSR is required or not

In my intended usecase, I don't have server-side rendering (I don't even have a JS-backed server!), so reading about initialStateForSSR is a bit disconcerting. I can't quite tell if I need to use it or if I can leave it out or what. I'm still going to investigate, but clarifying the docs might help others out that have the same question.

Thanks for a new library that puts Redux in the forefront!

having a url as a query string parameter can confuse the query object.

In situations where you have a return Url, such as ?ReturnUrl=/other?foo=foo&bar=bar, the query object from the little-router is:

{
  ReturnUrl:"/other?foo=foo",
  bar: "bar"
}

and it should be:

{
  ReturnUrl:"/other?foo=foo&bar=bar"
}

... or should it?, it can become a complicated problem, and maybe this could be the beginning of a discussion.

route list must be complete

It was not clear to me that the list of routes passed to createStoreWithRouter must be complete. I think this led me astray:

// Arbitrary data to add to the state tree when a route is
// matched and dispatched. Useful for page titles and other
// route-specific data.

I figured I didn't need that to get started. Perhaps it ultimately wont be required? Right now, trying to use a link that isn't in there leads to:

fragment.js:27 Uncaught TypeError: Cannot read property 'route' of null

It made sense to me that there could be fragments which didn't correspond to preconfigured routes.

Split out from #35

TypeScript TSD available?

Been waiting for this solution for a long time, well done to Formidable Labs!

Has anyone authored a TSD I could use?

Thanks
Tom

<Link /> href ignores query params

I would expect the following:

<Link href='/path?key=value'>Text</Link>

to render:

<a href='/path?key=value'>Text</a>

but instead, we get:

<a href='/path'>Text</a>

Clicking on the rendered anchor only directs the browser to /path.

Nested RelativeFragments not working as expected

I'm trying to migrate some code from react-router to redux-little-router, but I'm struggling a bit with how Fragments are supposed to work. I think I've narrowed it down to this (which can be added to fragment.spec.js in the test directory):

  it('does support traditional nesting', () => {
    const wrapper = mount(
      <RelativeFragment forRoute='/play'>
        <RelativeFragment forRoute='/c/:code'>
          <p>code</p>
        </RelativeFragment>
        <RelativeFragment forRoute='/'>
          <p>none</p>
        </RelativeFragment>
      </RelativeFragment>,
      fakeContext({
        pathname: '/play/c/123',
        route: '/play/c/:code'
      })
    );

    expect(wrapper.containsMatchingElement(<p>code</p>)).to.be.true;
    expect(wrapper.containsMatchingElement(<p>none</p>)).to.be.false;
});

So could anyone clue me in on how this is supposed to work, why isn't the Fragment containing "code" being matched?

RelativeFragment using _only_ `withConditions` never renders

It looks like you cannot use withConditions logic with <RelativeFragment> without also including forRoute - the store.matchWildcardRoute will always return null, which means this line always results in a null render.

The 'fix' is to use AbsoluteFragment instead of RelativeFragment, but it would be nice if I could just use one kind of fragment across my entire app without having to worry about different behavior outside of how forRoute is interpreted.

It would be worth adding a parallel test that uses RelativeFragment for both of these test cases, unless RelativeFragment should never be used in this way.

Location Descriptor

The link that should explain what a Location Descriptor is, that's broken and I couldn't find anything like it in the main page of the history project:

https://github.com/FormidableLabs/redux-little-router/blame/master/README.md#L228

In particular, I didn't understand if I could specify an URL as an object with dynamic segments not substituted:

// In a standalone file, such as /routes.js
const MESSAGES_URL = '/home/messages/:user'

// In a normal component's file
<Link className='anything' href={{
  pathname: MESSAGES_URL,
  params: {
    user: 'caesarsol'
  },
}}>

Add a way to style the div that wrap children or use React.Children.only

Hi,

It would be a good thing to be able to add style to the div that wrap props.children in fragment.

Or maybe a better approach would be to replace return <div>{children}</div>; by return React.Children.only(children); to throw an error if children contain more than one item.
This would also avoid adding an useless div in the case of a single children and we can still do this if we want to render more than one component in a fragment:

<Fragment>
  <div>
    <MyFirstComponent />
    <MySecondComponent />
  </div>
</Fragment>

How to navigate without Link?

How can I navigate to another route without using a Link component.
For example as a response of another action or a websocket call or something linke that.
Or can I somehow dispatch a REPLACE or PUSH action which the router picks up?
And is there a method I can use to create the action object (action-creator)?

Thanks

Using url-pattern route configuration

I had a look at the https://github.com/snd/url-pattern docs and that looks great, but I could not figure out how to apply this to my routes. The example I was trying to get working was a nested route. e.g. get the Fragment to be displayed should the url be /home, /home/subhome or whatever. I figured a regex would be the answer, but putting that into the routes seemed impossible as the routes are the keys. On the offchance I also tried the regex in the forRoute of the Fragment to no avail.

Split out from #35

unserializable route properties

The approach that I took to solve this problem was to encourage the use of matchRoute. I memoized the function returned by createMatcher so it can be called many times without penalty. I think it's the data in the matched route that is pertinent, not all the data in routes, plus then it's a lot easier to get at. Especially given that the user may be using a custom matcher and/or routes definition.

const _X = (props, context) => {
  const { router } = props;
  const { router: { store: { matchRoute }} } = context;
  const r = matchRoute(router.pathname);
  return <h1>r.somedata</h1>;
};

_X.contextTypes = {
  router: React.PropTypes.object
};

const X = connect(state => ({ router: state.router }))(_X);

This could be wrapped up into something like connect perhaps so all that code doesn't need to be repeated whenever the matchedRoute is needed.

This works out quite well for custom middleware taking advantage of the router as well. I do this by creating my middleware with a closure of matchRoute.

const codeSplitFactory = matchRoute => store => next => action => { ... }

Happy to PR this: https://github.com/dpwrussell/redux-little-router/tree/routeMatch

Also, I didn't completely follow the purpose of this:

    // Ensure that the router props from connect()
    // actually get to the child component(s)
    return cloneElement(this.props.children, {
      router: {
        ...routerState,

        // This is a hack to allow routes to define
        // unserializable things like components
        result: store.routes[routerState.route]
      }
    });

I don't understand why you would pass the router (and result) as properties to the children. Surely part of the point in storing router state in redux is to allow the user to connect to redux if they want to get at that data. I.e. they do a connectStateToProps.

Supporting url-pattern custom pattern syntax

url-patterns supports custom pattern syntax (doc). redux-little-router does not use url-pattern directly, but does use its syntax. If I use the default url-pattern syntax, I am able to access state.router.params, which has the parameters parsed from the URL. However, if I use a UrlPattern with custom syntax, state.router.params is undefined. I can accomplish the same effect by using urlPattern.match(state.router.pathname), but that requires importing my UrlPattern wherever I need to do that. It would be great if redux-little-router supported custom syntax in UrlPatterns, maybe as an additional parameter in routerForBrowser?

Demo | Refreshing nested example is KO

Hi,

There is a bug in the client demo, adding --history-api-fallback doesn't fix the problem.

To reproduce :

  • npm run demo
  • Click on nester
  • The URL is equals to http://localhost:3000/this/is/nested
  • Refresh or open a new tab with this URL : http://localhost:3000/this/is/nested
  • There is no result

A few documentation issues

First let me just say that having used React + Reach Router + React Router Redux for some time I am extremely happy that there is work in progress on an alternative as I basically agree with nearly everything in the trilogy of blog posts. When I learnt React I was convinced that it was the future, and again with Redux... then as soon as I started adding the Router I felt like I'd lost a lot of the things I'd liked in the first place.

I think there is a mistake when saying:

Like React Router's <Provider>, you'll want to wrap provideRouter around your app's top-level component like so. (fixed in #37)

I think this should probably say Router not Provider as that is the part of React Router that this seems to resemble. Provider is from react-redux and is definitely still needed, which I think also needs reinforcing.

Keep up the good work, I will be testing this as it develops, happy to help in any way I can, and look forward to reaching the point when it can be used in production.

Dispatch in middleware does not use store-enhancer dispatch

Hi,

I am experimenting with this:

Your custom middleware can intercept this action to dispatch new actions in response to URL changes.

I have written a barebones middleware for routes with auth in their result. Like so:

export const auth = store => next => action => {

  if (
    action.type === LOCATION_CHANGED &&
    action.payload &&
    action.payload.result &&
    action.payload.result.auth
  ) {

    const loggedIn = selectors.isLoggedIn(store.getState());

    if (!loggedIn) {
      setTimeout(() => {
        store.dispatch({
          type: PUSH,
          payload: '/login'
        });
        // Other dispatch which works perfectly
        // store.dispatch(fetchVocabsIfNeeded());
      }, 3000);
    }
  }
  return next(action);
};

The setTimeout is just to simulate an async fetch that I would usually dispatch to the server to see if I had an active session. If not, then I want to go to the login page.

This seems to generate a ROUTER_PUSH action with a payload of '/login'. The route does not change and a fragment for '/login' is not displayed. However, if I dispatch that same action from an onClick on my App component, the result is a ROUTER_LOCATION_CHANGE with the payload of pathname, key, route, params, etc, and it works fine.

I am not experienced with redux middleware and suspect that this has something to do with the middleware chain and where redux-little-router sits in that chain, but I'm unsure how to address this. Should this be possible?

Cheers

react-router example rewrites

I know it's a lot to ask, but implementing some of the react-router examples would be very helpful for learning this library, and inspire confidence that it can be used in place of react-router in non-trivial situations.

RelativeFragment with Nested Index

These are my defined routes:

const routes = {
  '/': {},
  '/foo': {
    '/bar': {},
  },
}

And here is my JSX:

<RelativeFragment forRoute='/foo'>
  Matches /foo and everything deeper

  <RelativeFragment forRoute='/'>
    Matches /foo and only /foo
  </RelativeFragment>

  <RelativeFragment forRoute='/bar'>
    Matches /foo/bar
  </RelativeFragment>
</RelativeFragment>

My question is about this part:

<RelativeFragment forRoute='/'>
  Matches /foo and only /foo
</RelativeFragment>

When I navigate to /foo, I'd like my app to render:

Matches /foo and everything deeper
Matches /foo and only /foo

Instead I only see:

Matches /foo and everything deeper

Why didn't it match / route? Is this a bug or is this intended?

Navigating to /foo/bar should render:

Matches /foo and everything deeper
Matches /foo/bar

That part works.

Official support for `hashHistory`

I initially asked

Any plans to have a hashHistory?

but I just tried importing

import { hashHistory } from 'react-router'
...

    createStoreWithRouter({
      routes,
      pathname: location.pathname, // optional, the beginning URL
      // basename: '',
      history: hashHistory
    }),

...

That actually does seem to work! It would be good to get some documentation on managing histories and if there is a hashHistory alternative to importing it from react-router which feels (although probably isn't!) very wrong!

Split out from #35

Query parameters are not rendered to the href of Link

If you create a <Link /> element with either a string or object href, it renders the <a /> tag without any of the query parameters. The onClick handler DOES redirect the browser to the correct place (with the query parameters), but omits it from the actual markup, breaking command-clicks, etc, and any automated traversing like search engines.

Input

<div>
	<Link href='/pathname?foo=bar'>String</Link>
	<Link href={{pathname: '/pathname', query: {foo: 'bar'}}}>Query</Link>
	<Link href={{pathname: '/pathname', search: '?foo=bar'}}>Search</Link>
</div>

Expected

<div>
	<a href='/pathname?foo=bar'>String</a>
	<a href='/pathname?foo=bar'>Query</a>
	<a href='/pathname?foo=bar'>Search</a>
</div>

Actual

<div>
	<a href='/pathname'>String</a>
	<a href='/pathname'>Query</a>
	<a href='/pathname'>Search</a>
</div>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.