acdlite / redux-router

Redux bindings for React Router – keep your router state inside your Redux store
MIT License
2.3k stars 216 forks source link

Update routing state in response to my own app actions? #250

Closed mallison closed 8 years ago

mallison commented 8 years ago

I'm trying to find the least intrusive way to add routing to my application. redux-router suits my thinking better as the routing is in the state so I can inject information from the URL where needed with connect instead of the react-router-redux approach where only components wrapped with Route can access location params.

However, I now need to change a lot of action creators to thunks instead of pure actions. For example, say I have some navigation for going through slides. I have a very simple action creator,

function showNext() {
  return { type: SHOW_NEXT };
}

and a very simple reducer

function slideReducer(state={slides: [], currentSlide: null}, action) {
  if (action.type === SHOW_NEXT) {
    return { ...state, currentSlide: wrapAroundIncrement(state.currentSlide) };
  }
  return state;
}

As I want the slide index in the URL I need to change this to:

function showNext() {
  return (dispatch, getState) => {
    const state = getState();
    const url = getNextSlideURL(state);
    dispatch(push(url));
    dispatch({ type: SHOW_NEXT });
  }
}

I feel what I'd really like to do is extend routerStateReducer and update the router state in response to my apps' own actions. That way the rest of the code, like action creators, don't need to change. But that doesn't really work because it requires the routerStateReducer to have access to global state and for my slideReducer to have already updated it's part of the state. I'm thinking something like:

function routerStateReducer(state = null, action) {
  // ROUTER_DID_CHANGE etc. here ....
  if (action.type === slideActionTypes.SHOW_NEXT) {
    const state = // somehow get *global* state *already updated* by another reducer
    const { currentSlide } = state.app;
    const pathname =  buildURL(currentSlide);
    return { ...state, pathname: pathname };
  }
}

I know this won't work but is it entirely the wrong approach? Am I wrong to feel that routing can be 'layered' on to an existing application? Otherwise it feels I need to make a lot of changes to add routing. Whereas an approach like the above means the routing is loosely coupled to the rest of the application.

mallison commented 8 years ago

I think react-router-redux without react-router may be the answer. The location state is in the store. The warning about using it directly I think only applies if using react-router. Looking at the implementation it looks like you could update the location in response to other actions and receive an action when the location is otherwise changed (back button etc.).

mindjuice commented 8 years ago

@mallison Interesting idea. You would lose a lot of functionality that react-router provides though, like declarative routes, route bundle loading, redirects, etc.

In my case, I'm not using any advanced features of react-router, so I would only lose the declarative routes and that is easily replaceable with JS code.

You might find this discussion interesting too: https://github.com/reactjs/react-router-redux/issues/95

The main point I tried to make there was that the app's main reducers should just work on their state as if there was no such thing as a URL/location, and that components should only pull from the app state to perform their rendering. No component should ever have to reach into something like router.location.query.someParamName to render either. Then you can write some independent functions like mapStateToLocation() and mapLocationToState() that handle all the details of going back and forth between state and location.

From your comments above, it seems you may have a similar opinion.

I'm playing around with some ideas right now, so we'll see if that goes anywhere.

Curious to find out if/how the react-router-redux without react-router works for you.

mallison commented 8 years ago

@mindjuice Yes, sounds like we're of the same opinion. I've made it work for my purposes (and written it up at http://blog.marvelapp.com/managing-the-url-in-a-redux-app/). The next improvement would be to use callbacks, as you suggested, to map between state and location -- this would remove the duplication in the solution I'm using.

The changes to react-router-redux would be small I think to support our usage. Maybe we should get PR into react-router-redux and see what people think?