7

I'm looking for best practices to integrate the following stack

  • React functional components
  • Redux state
  • Query parameters from URL

Basically I have got a search page with a complex filter component that is taking care of the filtering possibilities on the page. These filters are composed of dropdowns and checkboxes. If a filter is selected/deselected, I keep their states via action/event dispatcher/reducer etc.. Once the button submits is clicked there is an async HTTP call to the API, which will display the appropriate results on the page

Everything is working very well for now.

The tricky part is that I want to be able to share these filtered searches via an URL. I thought the best would to update the URL with query parameters. Such as /search?filter1=ok&filter2=true

Once the user entered the URL, the filter components would read the query parameters and update the state. But how can the state append/remove URL query parameters based on the user's actions?

I'm just looking for not overly complex solutions, using if possible the ability of my current dependency (shall I use hooks?)

I found various solutions but mostly based on Container components, where I'm trying to stick to functional components with hooks.

Thanks in advance for your tips, ideas.

Anselme

1
  • Are you using a history management library such as react-router? Commented Sep 23, 2020 at 2:28

3 Answers 3

3
+50

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.

Sign up to request clarification or add additional context in comments.

1 Comment

thanks for this interesting approach. I like it. Indeed I need to refactor a couple of elements before having it workable but it seems interesting for my issue. I will let you know If I have other eventual questions while implementing it
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.

2 Comments

thanks for the proposition. However, I don't get where is the best place to put this code snippet? in a middleware or within my filters components ?
@Anselme Since it's configuration method which should happen only once, you should call it after store initialization.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.