remix-run / history

Manage session history with JavaScript
MIT License
8.29k stars 960 forks source link

react router breaks if you use decodedUrl since it double encodes % percent chars #874

Open nojvek opened 3 years ago

nojvek commented 3 years ago

I know this is related to #505 , but wanted to put the solution out on the internet if someone else hit it too. This one took us many hours of painful debugging.

After much hair pulling and head banging, we came up to a hacky solution where we encode the % character to a utf-8 (U+FE6A) variant of it % so react router doesn't decode or encode it.

function encodePercentChars(str: string): string {
  return str.replace(/%/g, '﹪');
}

function decodePercentChars(str: string): string {
  return str.replace(/﹪/g, '%');
}

function encodePathParams(path: string, pathParams: Obj): string {
  let pathStr = path;
  for (const key of Object.keys(pathParams)) {
    pathStr = pathStr.replace(`:${key}`, encodePercentChars(encodeURIComponent(pathParams[key])));
  }
  return pathStr;
}

/**
 * @returns path params from location e.g /item/:id -> {id: 123}
 */
export function usePathParams<PathParamsT>(): PathParamsT {
  const routerParams = useReactRouterParams<any>();
  const pathParams: Obj = {};
  for (const key of Object.keys(routerParams)) {
    pathParams[key] = decodeURIComponent(decodePercentChars(routerParams[key]));
  }
  return (pathParams as unknown) as PathParamsT;
}

It breaks standard url formatting schema. AFAIK not a great architectural decision on react-router's side to auto-encode and decode urls because is messes with what the user passed into history.push(...) and <Link to={...}>. It's a gotcha that bites you. We've had multiple customers complain that they can't load a certain page in our app.

Hopefully this helps someone else.