score:0

You can get the URL by window.location.pathname and do the necessary split, slice and join to get the base URL. After that you can use history.push() method (if you are using React routing and BrowserHistory) to change the URL in the useEffect(). It will change the URL in the browser.

score:1

You can make your own implementation of synchronization but I'd suggest using redux-query-sync simple and lightweight library which does exactly that.

You can simply define mapping between your state and url for easy synchronization:

import ReduxQuerySync from 'redux-query-sync'

ReduxQuerySync({
    store, // your Redux store
    params: {
        dest: {
            // The selector you use to get the destination string from the state object.
            selector: state => state.route.destination,
            // The action creator you use for setting a new destination.
            action: value => ({type: 'setDestination', payload: value}),
        },
    },
    // Initially set the store's state to the current location.
    initialTruth: 'location',
})

If you don't want to install the library, you can look at the source code and make even simpler implementation of yours, although I don't see a need for that because library worked perfectly when I used it and it's not large package.

score:2

This solution might be more complicated than you'd like since you already have code in place but here is how i've solved this problem without adding 2 way binding and extra libraries that everyone seems to love for these kinds of issues.

Shifting your philosophy and treating your history/url as your filter state will allow you to use all the uni directional patterns you enjoy. With the url as your new filter state, attaching an effect to it will let you trigger effects such as syncing your app state to the url, fetching, ect. This lets your standard navigation features such as links, back and forth, ect work for free since they will simply be filtered through the effect. Assuming youre using the standard react-router/redux stack the pattern might look something like this, but can be adapted to use whatever you have on hand.

const dispatch = useDispatch();
const location = useLocation();
const parse = (search) => {
  // parse search parameters into your filter object applying defaults ect.
};

useEffect(async () => {
  const filters = parse(location.search);

  dispatch({ type: 'SEARCH_START', payload: filters }); // set spinner, filters, ect.

  const response = await fetch(/* your url with your filters */);
  const results = await response.json();

  dispatch({ type: 'SEARCH_END', payload: results });

  // return a disposer function with a fetch abort if you want.
}, [location.search]);

This effect will parse and dispatch your search actions. Notice how its reading values directly from location.search, parsing them, and then passing those values off to redux or whatever state management you use as well as fetching.

To handle your filter update logic, you search actions would just need to push history. This will give you unidirectional flow, keeps the results in sync with the url, and keeps the url in sync with the users filters. You are no longer updating filters directly, state must flow in one direction.

const useFilters = () => {
  const serialize = (filters) => {
    // exact opposite of parse. Remove default filter values or whatever you want here.
    // return your new url.
  };
  const history = useHistory();
  const filters = useSelector(selectFilters); // some way to find your already parsed filters so you can add to them.
  
  return {
    sortBy: (column) => history.push(serialize({ ...filters, sortBy: column })),
    search: (query) => history.push(serialize({ ...filters, query })),
    filterByShipping: (priority) => {} // ect,
    filterByVendor: (vendor) => {} // blah blah
  };
}

Above is an example of a filter api in hook form. With useFilters() you can use the returning functions to change the url. The effect will then be triggered, parse the url, trigger a new search, and save the parsed filter values that you can use in your other components.

The parse and serialize functions simply convert a value from query string to filters and back. This can be as complex or as simple as you need it to be. If you are already using a query string library, it could be used here. In my projects they typically parse short keys such as 'q' for query and return a mono typed filter value with defaults for things like sort order, if they are not defined. The stringify/serialize would do the opposite. It'll take the filters, convert them to short keys, remove nulls and defaults and spit out a search url string I can use for any urls/hrefs/ect.


Related Query

More Query from same tag