redux-history-sync

Keeping browser history and redux in sync

Usage no npm install needed!

<script type="module">
  import reduxHistorySync from 'https://cdn.skypack.dev/redux-history-sync';
</script>

README

redux-history-sync

Essentially, this module syncs browser history locations with a Redux store. If you are looking to read and write changes to the address bar via Redux this might be for you.

This module is intended to be the only module in your app that manages or touches window.history. If you want integration with react-router or history look elsewhere.

Install

npm install --save redux-history-sync

Overall Principals

This library saves each browser history entry to Redux. You can easily read every location a user has visited within the app from Redux. The app is therefor able to render a list of all the "pages" a user has visited from the Redux store. Clicking on one of those pages to navigate to that historical page is the same as selecting it from the browsers history. Doing so will emit the RESTORE_HISTORY action type.

If the app developer clicks "Reset" in Redux DevTools the browser history pointer is updated to the location when the app was loaded. If you disable the most current action that resulted in a page change it will move the browser history position back 1. If you navigate around to 6 pages and click "Reset" it is the same as clicking the back button 6 times. Clicking "Reset" and then clicking the browsers back button should exit the app to whatever page the browser visited previously. If browser window loaded the app first the back button will be disabled.

Navigating to a new "page" should act like it. UI state should reset/restore as you navigate to a new page or click back/forward. Filters enabled on one page should probably not carry over to another page. Clicking the browsers back/forward buttons while in the app is actually RESTORE_HISTORY not CREATE_HISTORY. If the app is going to change the url shown in the address bar it should probably be a new history entry both in the browsers history (via history.pushState) and in Redux. Therefore redux-history-sync provides no action for replaceState(). Please open an issue if you need it.

  • The browser forward/back/history dropdown should result in a Redux action just like a react button on the page would.
  • Changes made to the url by the app should be made via an action.
  • Location and related state is saved under a unique key in the store.
  • Each history event has location saved to its unique key.
  • Reducers can have state specific to a history location. When switching pages previous or new state will be sent to the reducer via the historySession reducer. Restore previous state on navigation changes.
  • Make browser back/forward navigation and the address bar as controlled as possible.
  • Store must have valid initialState set with current location/history information.
  • Typical changes to the pathname portion of window.location via Redux action is a push event.
  • Mirror DevTools changes by moving browser history forward or backward where possible.
  • Exchange many API parts for modularity and customization.
  • Absolutely no concern over routing/router/routes.
  • Avoid any direct usage of the window object from within the library. This might change in the future.

Usage

import {
  getInitState, historyMiddleware, historyReducer, syncHistoryWithStore,
} from 'redux-history-sync'
import { composeWithDevTools } from 'redux-devtools-extension'

/* global window */

const initState = {
  history: getInitState(window.location, window.document.title, window.history),
}
const reducer = {
  history,
}
const store = createStore(
  reducer,
  initState,
  composeWithDevTools( // Can use typical redux compose function instead.
    applyMiddleware(
      historyMiddleware(window.history),
      thunk,
    ),
  )
)
syncHistoryWithStore(store, window)
import { createHistory } from 'redux-history-sync'

dispatch(createHistory('/some/new/location'))

API

Actions

  • createHistory(location, title, key = null, pushState = true) HISTORY_CREATE This action should be dispatched when you want a new history entry or wish to change the location in the address bar. Usually the result of an interaction with a UI. Browser refresh and then forward can also create actions with this type.
  • restoreHistory(key, pushState = true) HISTORY_RESTORE This action should be dispatched when you want to exchange state with a previous history. Usually triggered by the browser back/forward buttons but can also be used inside the app to change browser history position.
  • createFromBrowser() HISTORY_LEARN If the user refreshes on a page the app thinks they do not have browser history. Clicking the browser back button will result in this action being triggered.

Possible hacks

  • getInitState(window.location, window.document.title) Dispatch initial action on reducer for sane DevTools resets.

Middleware

historyMiddleware(window.history, historyCache)

Reducer

historyReducer

Sync

Use this after the store is created to enable redux to control browser history. syncHistoryWithStore(store, window, historyCache)

Routes / Routing / Router

The address bar is a form input. It does not represent overall state. At its best the url can be parsed into an object and be used to populate a tiny portion of application state. See location-info

Discussions & Related Projects