salsita / prism

React / Redux action composition made simple http://salsita.github.io/prism/
495 stars 24 forks source link

usage with react-router-redux #27

Closed jmarceli closed 8 years ago

jmarceli commented 8 years ago

For anyone looking for react-router integration here is the example:

https://github.com/jmarceli/redux-elm-router

---- ORIGINAL question

Hi, I really like this redux implementation it is really clean and well documented. Anyway I have a problem with routing. How should I refactor the code to get react-router-redux support?

I come up with the following solution: src/boilerplate.js

import React from 'react';
import { render } from 'react-dom';
import { createStore, compose } from 'redux';
import { Provider, connect } from 'react-redux';
import reduxElm from 'redux-elm';
import { Router, Route, browserHistory } from 'react-router'; // added
import { syncHistoryWithStore } from 'react-router-redux'; // added
import Hello from './hello-world/view'; // added

export default (containerDomId, View, updater) => {
  const storeFactory = compose(
    reduxElm,
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )(createStore);

  const store = storeFactory(updater);

  const history = syncHistoryWithStore(browserHistory, store); // added

  const ConnectedView = connect(appState => ({
    model: appState
  }))(View);

  render((
    <Provider store={store}>
      { /* I wrap ConnectedView inside Router but I'm not sure if it is a good idea */ }
      <Router history={history}>
        <Route path="/" component={ConnectedView}>
          <Route path="/hello" component={Hello} />
        </Route>
      </Router>
    </Provider>
  ), document.getElementById(containerDomId));
}

src/home/updater.js (it is my main application parte executed from main.js)

import { Updater, Matchers } from 'redux-elm';
import gifViewerUpdater, { init as gifViewerInit } from '../gif-viewer/updater';
import { routerReducer } from 'react-router-redux';

export const initialModel = {
  globalCounter: 0,
  routing: routerReducer()
};

export default new Updater(initialModel)
  .case('Increment', model => model + 1)
  .toReducer();

But it doesn't work. Well it works (actions are dispatched), but my app is always on the first page home. I would appreciate any ideas on how to make routing work.

tomkis commented 8 years ago

Hello, I am really glad that you like it!

So here's the idea:

Your root view would look like this:

import { view } from 'redux-elm';

import buildRouting from './buildRouting';

export default view(({ history }) => buildRouting(history));

And the implementation of buildRouting is pretty straightforward:

import React from 'react';
import { connect } from 'react-redux';
import { forwardTo } from 'redux-elm';
import { Router, Route, IndexRoute } from 'react-router';

import Template from '../../template/template';
import CounterView from '../counter/counterView';
import AsyncView from '../async/asyncView';

const connectView = (View, modelKey, ...nesting) =>
  connect(appState => ({ model: appState.root[modelKey] }))(
    props => <View {...props} dispatch={forwardTo(props.dispatch, ...nesting)} />);

const ConnectedCounterView = connectView(CounterView, 'counter', 'Counter');
const ConnectedAsyncView = connectView(AsyncView, 'async', 'Async');

export default history => (
  <Router history={history}>
    <Route path="/" component={Template}>
      <IndexRoute component={ConnectedCounterView} />
      <Route path="counter" component={ConnectedCounterView} />
      <Route path="async" component={ConnectedAsyncView} />
    </Route>
  </Router>
);

don't forget to modify boilerplate.js:

import React from 'react';
import { render } from 'react-dom';
import { createStore, compose } from 'redux';
import { Provider, connect } from 'react-redux';
import reduxElm from 'redux-elm';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';

export default (containerDomId, View, updater) => {
  const storeFactory = compose(
    reduxElm,
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )(createStore);

  const store = storeFactory(updater);
  const history = syncHistoryWithStore(browserHistory, store);

  const ConnectedView = connect(appState => ({
    model: appState
  }))(View);

  render((
    <Provider store={store}>
      <ConnectedView history={history} />
    </Provider>
  ), document.getElementById(containerDomId));
}
jmarceli commented 8 years ago

Thanks for the solution. I'll give it a try and report back.

jmarceli commented 8 years ago

Well, I didn't manage to make it work. Here is the result: https://github.com/jmarceli/redux-elm-router.

It doesn't change current route and every counter update ends with the warning:

browser.js:49Warning: [react-router] You cannot change <Router routes>; it will be ignored

Probably I misunderstood some of your ideas. If you'll be so kind and take a look I would be grateful.

tomkis commented 8 years ago

@jmarceli fixed in https://github.com/jmarceli/redux-elm-router/pull/1 ... I will keep this issue opened as we need to document react-router integration and some best practices.

jmarceli commented 8 years ago

Thanks for your help. Now everything works as expected. I've added a couple of commits to my repo to make the code more readable (I hope) and some comments which explains what's going on.

jmatsushita commented 8 years ago

+1 for an example about react-router integration with redux-elm I'm wondering about the comment here: https://github.com/jmarceli/redux-elm-router/blob/master/src/template.js#L7-L8 and how elmish this is (I'm fairly new to this though so I might be wrong :) )

jmarceli commented 8 years ago

Thanks for the feedback. I've updated template.js according to the Elm Architecture (see: https://github.com/jmarceli/redux-elm-router/blob/master/src/template/view.js). Please take a look and tell me how you feel about it.

jmatsushita commented 8 years ago

You know how I feel thanks to this? Great! 😀 Very aesthetically pleasing!

jmarceli commented 8 years ago

I've updated my example code by adding custom routerMiddleware which allows for push('/someroute') usage from any elm component in the app. It is especially useful if you are using saga for handling form submissions and you want to redirect user after that.

tomkis commented 8 years ago

If you are looking for some plug&play solution, please feel free to use our official boilerplate

tomkis commented 8 years ago

Closing, nothing actionable here.