如何同步 Redux 状态和 URL 查询参数

我有一个带有搜索面板的网页。搜索面板有几个输入字段: id,size,..。

我想要的是当用户设置搜索值(例如: id = 123和 size = 45)并按下搜索按钮时:

  1. 应该使用新的搜索值(id = 123和 size = 45)更新 Redux reduce 中的 searchState
  2. 网址应更改为“ http://mydomain/home?id=123&size=45

另一方面,如果用户将 URL 更改为 http://mydomain/home?id=111&size=22,那么:

  1. 用新的搜索值(id = 111和 size = 22)更改 reduce 中的 searchState
  2. UI 搜索面板输入应该使用新值(id = 111和 size = 22)进行更新

如何达到目标? 我应该使用什么路由器(反应路由器,还原路由器,反应路由器还原其他) ?

52483 次浏览

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.

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.

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.

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.

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

I also recently finished working on this feature for my company's app. We wanted to add different search queries to the URL.

A few things I would like to share here:

1) We used Redux store and react-router. Since we also used Typescript, we ended up using RouteComponentProps from react-router to use this.props.history.push() to update the URL.

2) We kept all the search queries in the Redux store. The work flow for updating the Redux store and then the URL is as follows:

Select some filtering options in the app => Dispatch actions to update filtering state in the Redux store => update the URL

3) At the end, we also want users to be able to enter an URL with query params and it will update all the filters in the app. For this to work, the work flow is even simpler:

User enters the URL => dispatch actions to update Redux state with query params from the URL. The update of Redux state will automatically cause a re-rendering in the app and all filters will be updated.

So the most important thing here is always keep the Redux state and URL in sync with each other.