1

I am now learning Redux and React, and I am writing a hobby project that works like this.

The project is a search service that searches with tags. User can add or remove tags in the UI. In the code, the tags are represented in the Redux state, and adding and removing the tags happens via separate actions. So far so good, and I have a toy code that works.

Now I want to somehow "bind" the tags with the part of URL that comes after hash; for example, serialize the tags, separated by dash; so that user can copy/paste the URL and have the same tags.

I cannot find how to do that easily.

I am always hitting something called "react router", but all I see in the examples is always having more enter points to the app and showing user different parts of app according to the hash. I don't want that. So I don't know if I actually need this router; I don't want to route anything.

And even if I want this, I can't find how to bind the Redux state with the URL. I don't want to inject the tags directly to the props of the components; I want the tags from the url in the Redux state.

It feels like I am doing something wrong and I need to rethink my overall design, but I don't know where to start.

(I did not add any actual code, since I think it's more general question about app design, but I can simplify my toy code later and paste it here)

Karel Bílek
  • 32,538
  • 28
  • 83
  • 137
  • Possible duplicate of [How to sync Redux state and url hash tag params](https://stackoverflow.com/questions/36722584/how-to-sync-redux-state-and-url-hash-tag-params) – jtbandes Sep 08 '20 at 17:50

2 Answers2

1

You should listen to the hashchange event and update the state once it changed. To perform two-way binding you also should listen to the state changes and update window hash once the state changed. This is the idea of how it can work (fiddle):

function changeHash(state = '', action) {
  if(action.type == 'CHANGE_HASH')
    return action.hash;

  return state;
}

const store = Redux.createStore(changeHash);

store.subscribe(() => {
  const hash = store.getState();
  if (window.location.hash !== hash) {
    window.location.hash = hash;
    console.log('State changed: ' + hash);
  }
});

window.addEventListener("hashchange", () => {
  const hash = window.location.hash;
  if (store.getState() !== hash) {
    store.dispatch({
      type: 'CHANGE_HASH',
      hash
    });
    console.log('Hash changed: ' + hash);
  }
}, false);

setTimeout(() => {
    store.dispatch({
    type: 'CHANGE_HASH',
    hash: '#changed-state'
  });
}, 3000);

setTimeout(() => {
    window.location.hash = '#changed-hash';
}, 6000);
Alexander Bykov
  • 204
  • 3
  • 5
0

I was inspired by Alexander Bykov's answer + redux-persist and made this - an enhancer, that makes two-way binding of hash<->store.

import { applyMiddleware } from 'redux';
import createActionBuffer from 'redux-action-buffer';

const CHANGE_HASH = '@@hashSynch/CHANGE_HASH';
const hashEnhancer = (hashFromState, stateFromStateAndHash) => createStore => (reducer, initialState) => {
  const store = createStore(liftReducer(reducer), initialState, applyMiddleware(createActionBuffer(CHANGE_HASH)));
  store.subscribe(() => {
    const hash = hashFromState(store.getState());
    if (window.location.hash !== hash) {
        window.location.hash = hash;
    }
  });

  window.addEventListener('hashchange', () => {
    const hash = window.location.hash;
    const savedHash = hashFromState(store.getState());
    if (savedHash !== hash) {
      store.dispatch({
        type: CHANGE_HASH,
        hash
      });
    }
  }, false);

  store.dispatch({
    type: CHANGE_HASH,
    hash: window.location.hash
  });

  function liftReducer(reducer) {
    return (state, action) => {
      if (action.type !== CHANGE_HASH) {
        return reducer(state, action);
      } else {
        return stateFromStateAndHash(state, action.hash);
      }
    }
  }

  return {
    ...store,
    replaceReducer: (reducer) => {
      return store.replaceReducer(liftReducer(reducer))
    }
  }
};

Use like this:

export const store = createStore(
    reducer,
    initialState,
    hashEnhancer(hashFromState, stateFromStateAndHash)
);

where hashFromState is function of type hash=>state, and stateFromStateAndHash is function (state, hash) => state.

It might be overengineered and Router would be simplier, I just don't understand react-router or react-router-redux at all.

Karel Bílek
  • 32,538
  • 28
  • 83
  • 137