Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sync queryParameters with Redux state and react router for function components

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

like image 742
Anselme Avatar asked Oct 15 '22 00:10

Anselme


1 Answers

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.

like image 117
Brenden Avatar answered Nov 04 '22 02:11

Brenden