diff --git a/.changeset/quiet-cows-fly.md b/.changeset/quiet-cows-fly.md new file mode 100644 index 0000000..32d8e87 --- /dev/null +++ b/.changeset/quiet-cows-fly.md @@ -0,0 +1,5 @@ +--- +"rescript-relay-router": patch +--- + +Fix clean needed to remove path param type annotations in route patterns emitted. diff --git a/examples/client-rendering/src/routes/Root__Todos__ByStatusDecodedExtra_route_renderer.res b/examples/client-rendering/src/routes/Root__Todos__ByStatusDecodedExtra_route_renderer.res new file mode 100644 index 0000000..aed6a01 --- /dev/null +++ b/examples/client-rendering/src/routes/Root__Todos__ByStatusDecodedExtra_route_renderer.res @@ -0,0 +1,8 @@ +let renderer = Routes.Root.Todos.ByStatusDecodedExtra.Route.makeRenderer( + ~prepare=_props => { + () + }, + ~render=_props => { + React.null + }, +) diff --git a/examples/client-rendering/src/routes/__generated__/RouteDeclarations.res b/examples/client-rendering/src/routes/__generated__/RouteDeclarations.res index e3eca13..6680d74 100644 --- a/examples/client-rendering/src/routes/__generated__/RouteDeclarations.res +++ b/examples/client-rendering/src/routes/__generated__/RouteDeclarations.res @@ -4,7 +4,7 @@ open RelayRouter__Internal__DeclarationsSupport external unsafe_toPrepareProps: 'any => prepareProps = "%identity" let loadedRouteRenderers: Belt.HashMap.String.t = Belt.HashMap.String.make( - ~hintSize=6, + ~hintSize=7, ) let make = (~prepareDisposeTimeout=5 * 60 * 1000): array => { @@ -239,7 +239,7 @@ let make = (~prepareDisposeTimeout=5 * 60 * 1000): array (() => Js.import(Root__Todos__ByStatusDecodedExtra_route_renderer.renderer))->Obj.magic->doLoadRouteRenderer(~routeName, ~loadedRouteRenderers) + let makePrepareProps = (. + ~environment: RescriptRelay.Environment.t, + ~pathParams: Js.Dict.t, + ~queryParams: RelayRouter.Bindings.QueryParams.t, + ~location: RelayRouter.History.location, + ): prepareProps => { + let prepareProps: Route__Root__Todos__ByStatusDecodedExtra_route.Internal.prepareProps = { + environment: environment, + + location: location, + byStatusDecoded: pathParams->Js.Dict.unsafeGet("byStatusDecoded")->((byStatusDecodedRawAsString: string) => (byStatusDecodedRawAsString :> TodoStatusPathParam.t)), + statuses: queryParams->RelayRouter.Bindings.QueryParams.getArrayParamByKey("statuses")->Belt.Option.map(value => value->Belt.Array.keepMap(value => value->Js.Global.decodeURIComponent->TodoStatus.parse)), + } + prepareProps->unsafe_toPrepareProps + } + + { + path: "extra/:byStatusDecoded", + name: routeName, + chunk: "Root__Todos__ByStatusDecodedExtra_route_renderer", + loadRouteRenderer, + preloadCode: ( + ~environment: RescriptRelay.Environment.t, + ~pathParams: Js.Dict.t, + ~queryParams: RelayRouter.Bindings.QueryParams.t, + ~location: RelayRouter.History.location, + ) => preloadCode( + ~loadedRouteRenderers, + ~routeName, + ~loadRouteRenderer, + ~environment, + ~location, + ~makePrepareProps, + ~pathParams, + ~queryParams, + ), + prepare: ( + ~environment: RescriptRelay.Environment.t, + ~pathParams: Js.Dict.t, + ~queryParams: RelayRouter.Bindings.QueryParams.t, + ~location: RelayRouter.History.location, + ~intent: RelayRouter.Types.prepareIntent, + ) => prepareRoute( + ~environment, + ~pathParams, + ~queryParams, + ~location, + ~getPrepared, + ~loadRouteRenderer, + ~makePrepareProps, + ~makeRouteKey=( + ~pathParams: Js.Dict.t, + ~queryParams: RelayRouter.Bindings.QueryParams.t + ): string => { + + "Root__Todos__ByStatusDecodedExtra:" + ++ pathParams->Js.Dict.get("byStatusDecoded")->Option.getOr("") + ++ queryParams->RelayRouter.Bindings.QueryParams.getParamByKey("statuses")->Option.getOr("") + } + + , + ~routeName, + ~intent + ), + children: [], + } + }, { let routeName = "Root__Todos__Single" let loadRouteRenderer = () => (() => Js.import(Root__Todos__Single_route_renderer.renderer))->Obj.magic->doLoadRouteRenderer(~routeName, ~loadedRouteRenderers) diff --git a/examples/client-rendering/src/routes/__generated__/Route__Root__Todos__ByStatusDecodedExtra_route.res b/examples/client-rendering/src/routes/__generated__/Route__Root__Todos__ByStatusDecodedExtra_route.res new file mode 100644 index 0000000..7cc5f0e --- /dev/null +++ b/examples/client-rendering/src/routes/__generated__/Route__Root__Todos__ByStatusDecodedExtra_route.res @@ -0,0 +1,186 @@ +// @generated +// This file is autogenerated from `todoRoutes.json`, do not edit manually. +@live +type pathParams = { + byStatusDecoded: TodoStatusPathParam.t, +} + +type queryParams = { + statuses: option>, +} + +module Internal = { + @live + type prepareProps = { + environment: RescriptRelay.Environment.t, + location: RelayRouter.History.location, + ...pathParams, + ...queryParams, + } + + @live + type renderProps<'prepared> = { + childRoutes: React.element, + prepared: 'prepared, + environment: RescriptRelay.Environment.t, + location: RelayRouter.History.location, + ...pathParams, + ...queryParams, + } + + @live + type renderers<'prepared> = { + prepare: prepareProps => 'prepared, + prepareCode: option<(. prepareProps) => array>, + render: renderProps<'prepared> => React.element, + } + @live + let makePrepareProps = (. + ~environment: RescriptRelay.Environment.t, + ~pathParams: Js.Dict.t, + ~queryParams: RelayRouter.Bindings.QueryParams.t, + ~location: RelayRouter.History.location, + ): prepareProps => { + { + environment: environment, + + location: location, + byStatusDecoded: pathParams->Js.Dict.unsafeGet("byStatusDecoded")->((byStatusDecodedRawAsString: string) => (byStatusDecodedRawAsString :> TodoStatusPathParam.t)), + statuses: queryParams->RelayRouter.Bindings.QueryParams.getArrayParamByKey("statuses")->Belt.Option.map(value => value->Belt.Array.keepMap(value => value->Js.Global.decodeURIComponent->TodoStatus.parse)), + } + } + +} + +@live +let parseQueryParams = (search: string): queryParams => { + open RelayRouter.Bindings + let queryParams = QueryParams.parse(search) + { + statuses: queryParams->QueryParams.getArrayParamByKey("statuses")->Belt.Option.map(value => value->Belt.Array.keepMap(value => value->Js.Global.decodeURIComponent->TodoStatus.parse)), + + } +} + +@live +let applyQueryParams = ( + queryParams: RelayRouter__Bindings.QueryParams.t, + ~newParams: queryParams, +) => { + open RelayRouter__Bindings + + + queryParams->QueryParams.setParamArrayOpt(~key="statuses", ~value=newParams.statuses->Belt.Option.map(statuses => statuses->Belt.Array.map(statuses => statuses->TodoStatus.serialize->Js.Global.encodeURIComponent))) +} + +@live +type useQueryParamsReturn = { + queryParams: queryParams, + setParams: ( + ~setter: queryParams => queryParams, + ~onAfterParamsSet: queryParams => unit=?, + ~navigationMode_: RelayRouter.Types.setQueryParamsMode=?, + ~removeNotControlledParams: bool=?, + ~shallow: bool=?, + ) => unit +} + +@live +let useQueryParams = (): useQueryParamsReturn => { + let internalSetQueryParams = RelayRouter__Internal.useSetQueryParams() + let {search} = RelayRouter.Utils.useLocation() + let currentQueryParams = React.useMemo(() => { + search->parseQueryParams + }, [search]) + + let setParams = ( + ~setter, + ~onAfterParamsSet=?, + ~navigationMode_=RelayRouter.Types.Push, + ~removeNotControlledParams=true, + ~shallow=true, + ) => { + let newParams = setter(currentQueryParams) + + switch onAfterParamsSet { + | None => () + | Some(onAfterParamsSet) => onAfterParamsSet(newParams) + } + + internalSetQueryParams({ + applyQueryParams: applyQueryParams(~newParams, ...), + currentSearch: search, + navigationMode_: navigationMode_, + removeNotControlledParams: removeNotControlledParams, + shallow: shallow, + }) + } + + { + queryParams: currentQueryParams, + setParams: React.useMemo( + () => setParams, + (search, currentQueryParams), + ), + } +} + +@inline +let routePattern = "/todos/extra/:byStatusDecoded" + +@live +let makeLink = (~byStatusDecoded: TodoStatusPathParam.t, ~statuses: option>=?) => { + open RelayRouter.Bindings + let queryParams = QueryParams.make() + switch statuses { + | None => () + | Some(statuses) => queryParams->QueryParams.setParamArray(~key="statuses", ~value=statuses->Belt.Array.map(value => value->TodoStatus.serialize->Js.Global.encodeURIComponent)) + } + RelayRouter.Bindings.generatePath(routePattern, Js.Dict.fromArray([("byStatusDecoded", (byStatusDecoded :> string)->Js.Global.encodeURIComponent)])) ++ queryParams->QueryParams.toString +} +@live +let makeLinkFromQueryParams = (~byStatusDecoded: TodoStatusPathParam.t, queryParams: queryParams) => { + makeLink(~byStatusDecoded, ~statuses=?queryParams.statuses, ) +} + +@live +let useMakeLinkWithPreservedPath = () => { + let location = RelayRouter.Utils.useLocation() + React.useMemo(() => { + (makeNewQueryParams: queryParams => queryParams) => { + let newQueryParams = location.search->parseQueryParams->makeNewQueryParams + open RelayRouter.Bindings + let queryParams = location.search->QueryParams.parse + queryParams->applyQueryParams(~newParams=newQueryParams) + location.pathname ++ queryParams->QueryParams.toString + } + }, [location.search]) +} + + +@live +let isRouteActive = (~exact: bool=false, {pathname}: RelayRouter.History.location): bool => { + RelayRouter.Internal.matchPathWithOptions({"path": routePattern, "end": exact}, pathname)->Belt.Option.isSome +} + +@live +let useIsRouteActive = (~exact=false) => { + let location = RelayRouter.Utils.useLocation() + React.useMemo(() => location->isRouteActive(~exact), (location, exact)) +} + +@live +let usePathParams = (): option => { + let {pathname} = RelayRouter.Utils.useLocation() + switch RelayRouter.Internal.matchPath(routePattern, pathname) { + | Some({params}) => Some(Obj.magic(params)) + | None => None + } +} + +@obj +external makeRenderer: ( + ~prepare: Internal.prepareProps => 'prepared, + ~prepareCode: Internal.prepareProps => array=?, + ~render: Internal.renderProps<'prepared> => React.element, +) => Internal.renderers<'prepared> = "" \ No newline at end of file diff --git a/examples/client-rendering/src/routes/__generated__/Routes.res b/examples/client-rendering/src/routes/__generated__/Routes.res index 64d0c84..2cee768 100644 --- a/examples/client-rendering/src/routes/__generated__/Routes.res +++ b/examples/client-rendering/src/routes/__generated__/Routes.res @@ -24,6 +24,11 @@ module Root = { } } + /** [See route renderer](./Root__Todos__ByStatusDecodedExtra_route_renderer.res)*/ + module ByStatusDecodedExtra = { + module Route = Route__Root__Todos__ByStatusDecodedExtra_route + + } /** [See route renderer](./Root__Todos__Single_route_renderer.res)*/ module Single = { module Route = Route__Root__Todos__Single_route diff --git a/examples/client-rendering/src/routes/todoRoutes.json b/examples/client-rendering/src/routes/todoRoutes.json index 991651f..b5be960 100644 --- a/examples/client-rendering/src/routes/todoRoutes.json +++ b/examples/client-rendering/src/routes/todoRoutes.json @@ -18,6 +18,10 @@ } ] }, + { + "path": "extra/:byStatusDecoded:TodoStatusPathParam.t", + "name": "ByStatusDecodedExtra", + }, { "path": ":todoId?showMore=bool", "name": "Single", diff --git a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res index a4aa715..44fe89b 100644 --- a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res +++ b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Types.res @@ -86,6 +86,18 @@ module RoutePath: { currentRoutePath: list, } + let cleanPathParamTypeAnnotations = p => + if p->String.includesFrom(":", 1) { + ":" ++ + p + ->String.sliceToEnd(~start=1) + ->String.split(":") + ->Array.get(0) + ->Option.getOr("") + } else { + p + } + let make = (path, ~currentRoutePath) => { let cleanPath = path->String.split("?")->Array.get(0)->Option.getOr("") { @@ -96,23 +108,16 @@ module RoutePath: { ->List.reverse ->List.concat(currentRoutePath.currentRoutePath) ->List.filter(urlPart => urlPart != "") - ->List.map(p => { - // Handle path params with explicit type annotations - if p->String.includesFrom(":", 1) { - ":" ++ - p - ->String.sliceToEnd(~start=1) - ->String.split(":") - ->Array.get(0) - ->Option.getOr("") - } else { - p - } - }), + ->List.map(p => cleanPathParamTypeAnnotations(p)), } } - let getPathSegment = t => t.pathSegment + let getPathSegment = t => + t.pathSegment + ->String.split("/") + ->Array.map(p => cleanPathParamTypeAnnotations(p)) + ->Array.join("/") + let getFullRoutePath = t => "/" ++ t.currentRoutePath->List.reverse->List.toArray->Array.join("/") let toPattern = t =>