pbeshai / use-query-params

React Hook for managing state in URL query parameters with easy serialization.

Home Page:https://pbeshai.github.io/use-query-params

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use with a store like Zustand

camerondrysdale opened this issue · comments

Is it possible to use this with a store like Zustand? https://github.com/pmndrs/zustand

This is because we have components like:

<ParentContainer>
    <Toolbar/>
    <Sidebar/>
    <Content/>
</ParentContainer>

And then each of those has its own state and having to try and pass this up and down would be painful... so instead we use a store to have the state stored 'globally' but we want to use the query string to store this state as well.

For example:

const [latitude, setLatitude] = useQueryParam('latitude', withDefault(StringParam, ''));
const [longitude, setLongitude] = useQueryParam('longitude', withDefault(StringParam, ''));
const [zoom, setZoom] = useQueryParam('zoom', withDefault(StringParam, ''));

But what we want to do is use something like the following (from our store):

const latitude = useStore(state => state.latitude);
const setLatitude = useStore(state => state.setLatitude);

And inside useStore we something like:

import create from 'zustand'

const useStore = create(set => ({
  latitude: '',
  setLatitude: () => set(state => ({ latitude: state.latitude })),
}));

export { useStore }

So it'd be great if we could get the store to hook into useQueryParams or vice-versa. Does that seem possible?

Sure. I use it with a store being in react-hook-form. Here's an example of watching an id query param. I have a provider that updates useQueryParams via setId when the form value changes. This particular component isn't utilizing it but I'll use default params to instantiate the form's store. I then hand over control to the form's store for the single source of truth, updating the query param as it changes.

export const IdFilterProvider = ({ children }: { children: ReactNode }) => {
  const [id, setId] = useQueryParam(`id`, StringParam);

  const methods = useForm({
    defaultValues: {
      id: id,
    },
  });
  const { watch, setValue } = methods;
  const idValue = watch(`id`);

  const setIdValue = useCallback(
    (v) => {
      setValue(`id`, v);
    },
    [setValue]
  );

  useEffect(() => {
    setId(idValue);
  }, [idValue, setId]);

  const context = useMemo(
    () => ({
      idValue,
      setIdValue,
      methods,
    }),
    [idValue, setIdValue, methods]
  );
  return <IdContext.Provider value={context}>{children}</IdContext.Provider>;
};

Since the query params in the url is the same, call the same useQueryParam hook in each component is fine. You don't need to pass it all around.

Just call useQueryParam('latitude', withDefault(StringParam, '')); in Sidebar.js, Toolbar.js and Content.js. Or you can make a customized hook for it:

// hooks.js
export const useLatitudeParam = () => useQueryParam('latitude', withDefault(StringParam, ''));
export const useLongitudeParam = () => useQueryParam('longitude', withDefault(StringParam, ''));
export const useZoomParam = () => useQueryParam('zoom', withDefault(StringParam, ''));

// Content.js
import { useLatitudeParam, useLongitudeParam, useZoomParam } from './hooks';
const Content = props => {
  // ...
  const [latitude, setLatitude] = useLatitudeParam();
  const [longitude, setLongitude] = useLongitudeParam();
  const [zoom, setZoom] = useZoomParam();
  // ...
  return ...
}

also, you can use useQueryParams instead:

// hooks.js
export const useMapParams = () => useQueryParams({
  latitude: withDefault(StringParam, ''),
  longitude: withDefault(StringParam, ''),
  zoom: withDefault(StringParam, ''),
});

// Content.js
import { useMapParams } from './hooks';
const Content = props => {
  // ...
  // typeof mapParams = {  }
  const [mapParams, setMapParams] = useMapParams();
  // ...
  return ...
}