useLocationQuery.tsx 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. import {useMemo} from 'react';
  2. import {decodeInteger, decodeList, decodeScalar} from 'sentry/utils/queryString';
  3. import {useLocation} from 'sentry/utils/useLocation';
  4. type Scalar = string | boolean | number | undefined;
  5. type Decoder = typeof decodeList | typeof decodeScalar | typeof decodeInteger;
  6. /**
  7. * Select and memoize query params from location.
  8. * This returns a new object only when one of the specified query fields is
  9. * updated. The object will remain stable otherwise, avoiding re-renders.
  10. *
  11. * You shouldn't need to manually set the `InferredRequestShape` or `InferredResponseShape`
  12. * generics, instead type the left side of the statement.
  13. *
  14. * For example:
  15. * ```
  16. * type QueryFields = {statsPeriod: string};
  17. * const query: QueryFields = useLocationQuery({
  18. * fields: {statsPeriod: decodeScalar}
  19. * });
  20. * ```
  21. */
  22. export default function useLocationQuery<
  23. InferredRequestShape extends Record<string, Scalar | Scalar[] | Decoder>,
  24. InferredResponseShape extends {
  25. readonly [Property in keyof InferredRequestShape]: InferredRequestShape[Property] extends Decoder
  26. ? ReturnType<InferredRequestShape[Property]>
  27. : InferredRequestShape[Property];
  28. },
  29. >({fields}: {fields: InferredRequestShape}): InferredResponseShape {
  30. const location = useLocation();
  31. const locationFields = {};
  32. const forwardedFields = {};
  33. Object.entries(fields).forEach(([field, decoderOrValue]) => {
  34. if (typeof decoderOrValue === 'function') {
  35. if (decoderOrValue === decodeScalar) {
  36. locationFields[field] = decoderOrValue(location.query[field], '');
  37. } else if (decoderOrValue === decodeInteger) {
  38. locationFields[field] = decoderOrValue(location.query[field], 0);
  39. } else {
  40. locationFields[field] = decoderOrValue(location.query[field]);
  41. }
  42. } else {
  43. forwardedFields[field] = decoderOrValue;
  44. }
  45. }, {});
  46. const stringyForwardedFields = JSON.stringify(forwardedFields);
  47. const stringyLocationFields = JSON.stringify(locationFields);
  48. return useMemo(
  49. () => ({
  50. ...(forwardedFields as any),
  51. ...(locationFields as any),
  52. }),
  53. [stringyForwardedFields, stringyLocationFields] // eslint-disable-line
  54. );
  55. }