How to sync Redux state and url query params

48,681

Solution 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.

Solution 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.

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
48,681
Anton
Author by

Anton

Updated on July 08, 2022

Comments

  • Anton
    Anton almost 2 years

    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)?

  • JoKer
    JoKer about 8 years
    Hi @Dan, we are facing a very similar problem like Anton. Your answer is quite promising but we have a hard time finding a good example where React Router or even React Router in combination with Redux changes the state of only one component for several different urls. In our case we change the hash tag and it should always be applied to just one component.
  • Dan Abramov
    Dan Abramov about 8 years
    Have a single route, and use the props router injects to control this component? This is really a separate question unrelated to Redux, and you might want to ask it separately.
  • JoKer
    JoKer about 8 years
    Ok, I will try to create a new question where I explain my problem in more detail. Thanks for answering so fast.
  • JoKer
    JoKer about 8 years
    Here is my new question. Would be really nice if you could have a look. stackoverflow.com/questions/36722584/…
  • Alex Parij
    Alex Parij over 7 years
    The problem with this approach as I see it is that once you have multiple components in container that change the url it becomes entangles mess of callbacks and url params update. May be do it in each component instead of using a callback to the container....
  • Benja
    Benja over 7 years
    what about using route params in your selectors? seems a bit silly to have to send this from a component into the selector, and potential cause of a lot of duplication
  • benzaita
    benzaita about 5 years
    This works fine if the search values are only in the URL. Consider what happens if you have another search filter which comes from a different source (e.g. redux store). Then when you want to reset all filters you need to update two places (URL and redux store), which triggers two updates to the props, which trigger two fetches. How can you avoid that?
  • jthetzel
    jthetzel about 4 years
    And a hooks version, use-query-params, from the same author: github.com/pbeshai/use-query-params
  • DannyMoshe
    DannyMoshe about 2 years
    Sounds great but how do you prevent render loops? You update the url from redux and listen to the url to update redux.
  • Hoan Phung
    Hoan Phung about 2 years
    The update from URL to Redux happens only one time when the component first mounted (in componentDidMount or in useEffect). The remaining of the time, the Redux store is always the lead and the URL will be updated according to the Redux store. This way, no loop is going to happen.