How to sync Redux state and url hash tag params

43,512

Solution 1

I think you don’t need to.
(Sorry for a dismissive answer but it’s the best solution in my experience.)

Store is the source of truth for your data. This is fine.
If you use React Router, let it be the source of truth for your URL state.
You don’t have to keep everything in the store.

For example, considering your use case:

Because the url parameters only contain the slugs of the lectures and the chapters which are selected. In the store I have a list of lectures and chapters with a name, slug and a selected Boolean value.

The problem is you’re duplicating the data. The data in the store (chapter.selected) is duplicated in the React Router state. One solution would be syncing them, but this quickly gets complex. Why not just let React Router be the source of truth for selected chapters?

Your store state would then look like (simplified):

{
  // Might be paginated, kept inside a "book", etc:
  visibleChapterSlugs: ['intro', 'wow', 'ending'],

  // A simple ID dictionary:
  chaptersBySlug: {
    'intro': {
      slug: 'intro',
      title: 'Introduction'
    },
    'wow': {
      slug: 'wow',
      title: 'All the things'
    },
    'ending': {
      slug: 'ending',
      title: 'The End!'
    }
  }
}

That’s it! Don’t store selected there. Instead let React Router handle it. In your route handler, write something like

function ChapterList({ chapters }) {
  return (
    <div>
      {chapters.map(chapter => <Chapter chapter={chapter} key={chapter.slug} />)}
    </div>
  )
}

const mapStateToProps = (state, ownProps) => {
  // Use props injected by React Router:
  const selectedSlugs = ownProps.params.selectedSlugs.split(';')

  // Use both state and this information to generate final props:
  const chapters = state.visibleChapterSlugs.map(slug => {
    return Object.assign({
      isSelected: selectedSlugs.indexOf(slug) > -1,
    }, state.chaptersBySlug[slug])
  })

  return { chapters }
}

export default connect(mapStateToProps)(ChapterList)

Solution 2

react-router-redux can help you inject the url stuff to store, so every time hash tag changed, store also.

Share:
43,512

Related videos on Youtube

JoKer
Author by

JoKer

Co-founder of kenHub GmbH.

Updated on July 05, 2022

Comments

  • JoKer
    JoKer almost 2 years

    We have a list of lectures and chapters where the user can select and deselect them. The two lists are stored in a redux store. Now we want to keep a representation of selected lecture slugs and chapter slugs in the hash tag of the url and any changes to the url should change the store too (two-way-syncing).

    What would be the best solution using react-router or even react-router-redux?

    We couldn't really find some good examples where the react router is only used to maintain the hash tag of an url and also only updates one component.

    • Dan Abramov
      Dan Abramov about 8 years
      Why do you want to keep this state in the store, as opposed to just using React Router as the source of truth for URL parameters, and using the props it injects in your mapStateToProps(state, ownProps)?
    • JoKer
      JoKer about 8 years
      Because the url parameters only contain the slugs of the lectures and the chapters which are selected. In the store I have a list of lectures and chapters with a name, slug and a selected Boolean value.
    • mb21
      mb21 about 6 years
  • Dan Abramov
    Dan Abramov about 8 years
    It just gives you the location, not the parsed params, so it’s of limited usefulness unless you’re fine with reimplementing what React Router gives you manually. It’s a nice solution for keeping store in sync with actions so that recording and replaying user sessions works but it’s not really that useful for apps that don’t need this.
  • JoKer
    JoKer about 8 years
    Thanks @dan for your detailed answer. I will try to apply it and report how it worked.
  • JoKer
    JoKer about 8 years
    sorry for coming back so late but I was on holidays the last week. I have one question. You described one way which is getting the data from the url and rendering it. How would you update the url with the new selected chapters?
  • Dan Abramov
    Dan Abramov about 8 years
    I would call browserHistory.push() (where browserHistory is imported from react-router) in the action creator. I would probably use Redux Thunk to make it clear there’s a side effect going on. I’d call dispatch({ ... }); browserHistory.push(...) inside it.
  • Alexander Reshytko
    Alexander Reshytko about 8 years
    Hey Dan. Thanks for Redux! My I also put my 2 cents in the topic and ask a question. What if an application state-transitioning logic is dependent on the data in a query? That means the reducers need it and you can't just get rid of it in the store.
  • Dan Abramov
    Dan Abramov about 8 years
    @AlexanderReshytko Since this data comes from “external world” I’d emit actions with it when necessary. This doesn’t mean you need a generic “route changed” action though (even though you can dispatch it if you find it useful). Your reducers could then use that information, just like they usually do.
  • Jacob Rask
    Jacob Rask almost 8 years
    It's a bit awkward to send parameters to reselect selectors, so being able to get the current url params from the store is very useful.
  • lud
    lud over 7 years
    @DanAbramov thanks for you answer but I don't get it ! How can you do stuff (like dispatching actions) if the URL data is only available in the Router ? Imagine you build a simple chat, and in the URL hash you put the chatroom name. You load the app with #/some-fun-room in the URL. How can you dispatch action to connect to the room ? I see people dispatch stuff from the components ... it seems completely wrong to me.
  • Ioan
    Ioan about 7 years
    @DanAbramov Regarding your first comment, may I ask what would you pass in the dispatch ? Why it is needed ? Thanks!
  • krzychek
    krzychek almost 7 years
    Your mapStateToProps creates there new object on chapters property of returned props. This will cause the component to rerender every time anything in store changes (because one of props changes on every redux action).