How to sync Redux state and url query params

2019-03-08 23:08发布

问题:

I have a web page with a search panel. Search panel has several input fields: id, size, ...

What I want is when user set search values (for example: id=123 and size=45) and press a search button:

  1. searchState in Redux reducer should be updated with new search values (id=123 and size=45)
  2. URL should be changed to "http://mydomain/home?id=123&size=45"

And on the other hand if the user changes URL to http://mydomain/home?id=111&size=22 then:

  1. searchState in reducer should be changed with new search values (id=111 and size=22)
  2. UI search panel inputs should be updated with new values (id=111 and size=22)

How to reach with goal? What router should I use (react-router, redux-router, react-router-redux ot other)?

回答1:

I would suggest just using React Router directly and not keeping searchState in Redux. React Router will inject URL parameters into your components, and you can use them in mapStateToProps(state, ownProps) to calculate the final props.

If you really want to see route changes as actions, you can use react-router-redux for two-way syncing, but it won’t give you the params in the state—just the current location. The only use case for it if you want to record and replay actions, and have the URL bar update as you replay them.

It is by no means required—in most cases, just using React Router directly is enough.



回答2:

In short: You could use redux-query-sync to keep the URL parameters and fields in the store in sync. It works without any Router.


Longer story: Struggling with the same question, I first appreciated Dan Abramov's answer, suggesting to (in my words) consider the URL as a 'second store', a part of the application state outside the Redux store. But then I found that having two kinds of 'stores' makes it difficult to modify code, to e.g. move things from one to the other, because each 'store' has a different interface. As a developer, I would rather like to select any of the fields in my Redux state, and expose them in the URL, as if the location were a small window to (a part of) my state.

Hence I just published redux-query-sync, to let you provide a selector function for selecting a value from the state, which is then exposed in the window location at a parameter name you specify. To also let it sync in the other direction, thus from the URL to Redux state (e.g. when the application initially loads), you can provide an action creator that will be passed the parameter value, so your reducer can update the state accordingly.



回答3:

Here's the way I've been handling the first scenario:

  • When the input value changes, its component triggers a callback that was passed as a prop from its container.

  • In the callback, the container dispatches the action responsible for updating Redux state when the event occurs.

  • In the line immediately following the action call, I use this.context.router.push() and pass it the url with the correct query string.

I'm not certain that this is the correct approach. I found it preferable to updating the URL first because, in my opinion, the query string should be a reflection of state rather than the master of it.

Regarding the reverse scenario, I'm really not sure. It seems like manually setting the URL would trigger a full reload, but I might be mistaken.

As for which router to use, I am using React Router by itself. I wasn't really finding value in using the others and this discussion was the clincher for me.



回答4:

I have recently finished working on a similar issue to this. I used MobX as my store and redux-router as my router. (I did fine with react-router, but I needed to dispatch a push action outside of the component - redux as a global store works great here)

My solution has been similar to what cantera has described - my router state is merely a reflection of the form state. This has the added bonus of not having to ditch my form state and depend entirely on the router state.


In the first scenario,

1) I update my form input as usual, that triggers a re-render in the results component.

2) In componentDidUpdate() of the results component, I use the form props to construct the updated query string.

3) I push the updated query string to the router state. This is purely for the effect of updating the URL.


For the second scenario

1) In the onEnter hook on the root Route component, I get the available query string and parse it into the initial form values.

2) I update my store with the values. This is only for the first load of the page, and the only time the router state dictates the form state.


Edit: This solution does not account for the case when you go back in your browser history, because the url will not update your state.



回答5:

I would recommend the newly tool react-url-query which will do that syncing quite straight-forward.