diff --git a/.changeset/fresh-shrimps-scream.md b/.changeset/fresh-shrimps-scream.md
new file mode 100644
index 0000000..63e9da4
--- /dev/null
+++ b/.changeset/fresh-shrimps-scream.md
@@ -0,0 +1,6 @@
+---
+"@rescript-relay-router-example/client-rendering": patch
+"rescript-relay-router": patch
+---
+
+fix parseRoute for routes with only query params or only path params
diff --git a/examples/client-rendering/src/routes/Root__Home_route_renderer.res b/examples/client-rendering/src/routes/Root__Home_route_renderer.res
new file mode 100644
index 0000000..9593e2c
--- /dev/null
+++ b/examples/client-rendering/src/routes/Root__Home_route_renderer.res
@@ -0,0 +1,8 @@
+let renderer = Route__Root__Home_route.makeRenderer(
+ ~prepare=({environment}) => {
+ LayoutQuery_graphql.load(~environment, ~variables=(), ~fetchPolicy=StoreOrNetwork)
+ },
+ ~render=props => {
+ {props.childRoutes}
+ },
+)
diff --git a/examples/client-rendering/src/routes/Root__PathParamsOnly_route_renderer.res b/examples/client-rendering/src/routes/Root__PathParamsOnly_route_renderer.res
new file mode 100644
index 0000000..d8e4e66
--- /dev/null
+++ b/examples/client-rendering/src/routes/Root__PathParamsOnly_route_renderer.res
@@ -0,0 +1,8 @@
+let renderer = Route__Root__PathParamsOnly_route.makeRenderer(
+ ~prepare=({environment}) => {
+ LayoutQuery_graphql.load(~environment, ~variables=(), ~fetchPolicy=StoreOrNetwork)
+ },
+ ~render=props => {
+ {props.childRoutes}
+ },
+)
diff --git a/examples/client-rendering/src/routes/__generated__/RouteDeclarations.res b/examples/client-rendering/src/routes/__generated__/RouteDeclarations.res
index 39e0cfb..8b373ae 100644
--- a/examples/client-rendering/src/routes/__generated__/RouteDeclarations.res
+++ b/examples/client-rendering/src/routes/__generated__/RouteDeclarations.res
@@ -520,6 +520,147 @@ let make = (~prepareDisposeTimeout=5 * 60 * 1000): array (() => import(Root__PathParamsOnly_route_renderer.renderer))->Obj.magic->doLoadRouteRenderer(~routeName, ~loadedRouteRenderers)
+ let makePrepareProps = (.
+ ~environment: RescriptRelay.Environment.t,
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t,
+ ~location: RelayRouter.History.location,
+ ): prepareProps => {
+ ignore(queryParams)
+ let prepareProps: Route__Root__PathParamsOnly_route.Internal.prepareProps = {
+ environment: environment,
+ location: location,
+ pageSlug: pathParams->Dict.getUnsafe("pageSlug"),
+ }
+ prepareProps->unsafe_toPrepareProps
+ }
+
+ {
+ path: "other/:pageSlug",
+ name: routeName,
+ chunk: "Root__PathParamsOnly_route_renderer",
+ loadRouteRenderer,
+ preloadCode: (
+ ~environment: RescriptRelay.Environment.t,
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t,
+ ~location: RelayRouter.History.location,
+ ) => preloadCode(
+ ~loadedRouteRenderers,
+ ~routeName,
+ ~loadRouteRenderer,
+ ~environment,
+ ~location,
+ ~makePrepareProps,
+ ~pathParams,
+ ~queryParams,
+ ),
+ prepare: (
+ ~environment: RescriptRelay.Environment.t,
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t,
+ ~location: RelayRouter.History.location,
+ ~intent: RelayRouter.Types.prepareIntent,
+ ) => prepareRoute(
+ ~environment,
+ ~pathParams,
+ ~queryParams,
+ ~location,
+ ~getPrepared,
+ ~loadRouteRenderer,
+ ~makePrepareProps,
+ ~makeRouteKey=(
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t
+ ): string => {
+ ignore(queryParams)
+
+ "Root__PathParamsOnly:"
+ ++ pathParams->Dict.get("pageSlug")->Option.getOr("")
+
+ }
+
+ ,
+ ~routeName,
+ ~intent
+ ),
+ children: [],
+ }
+ },
+ {
+ let routeName = "Root__Home"
+ let loadRouteRenderer = () => (() => import(Root__Home_route_renderer.renderer))->Obj.magic->doLoadRouteRenderer(~routeName, ~loadedRouteRenderers)
+ let makePrepareProps = (.
+ ~environment: RescriptRelay.Environment.t,
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t,
+ ~location: RelayRouter.History.location,
+ ): prepareProps => {
+ ignore(pathParams)
+ ignore(queryParams)
+ let prepareProps: Route__Root__Home_route.Internal.prepareProps = {
+ environment: environment,
+ location: location,
+ }
+ prepareProps->unsafe_toPrepareProps
+ }
+
+ {
+ path: "home",
+ name: routeName,
+ chunk: "Root__Home_route_renderer",
+ loadRouteRenderer,
+ preloadCode: (
+ ~environment: RescriptRelay.Environment.t,
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t,
+ ~location: RelayRouter.History.location,
+ ) => preloadCode(
+ ~loadedRouteRenderers,
+ ~routeName,
+ ~loadRouteRenderer,
+ ~environment,
+ ~location,
+ ~makePrepareProps,
+ ~pathParams,
+ ~queryParams,
+ ),
+ prepare: (
+ ~environment: RescriptRelay.Environment.t,
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t,
+ ~location: RelayRouter.History.location,
+ ~intent: RelayRouter.Types.prepareIntent,
+ ) => prepareRoute(
+ ~environment,
+ ~pathParams,
+ ~queryParams,
+ ~location,
+ ~getPrepared,
+ ~loadRouteRenderer,
+ ~makePrepareProps,
+ ~makeRouteKey=(
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t
+ ): string => {
+ ignore(pathParams)
+ ignore(queryParams)
+
+ "Root__Home:"
+
+
+ }
+
+ ,
+ ~routeName,
+ ~intent
+ ),
+ children: [],
+ }
}],
}
}
diff --git a/examples/client-rendering/src/routes/__generated__/Route__Root__Home_route.res b/examples/client-rendering/src/routes/__generated__/Route__Root__Home_route.res
new file mode 100644
index 0000000..a3de66a
--- /dev/null
+++ b/examples/client-rendering/src/routes/__generated__/Route__Root__Home_route.res
@@ -0,0 +1,70 @@
+// @generated
+// This file is autogenerated from `todoRoutes.json`, do not edit manually.
+module Internal = {
+ @live
+ type prepareProps = {
+ environment: RescriptRelay.Environment.t,
+ location: RelayRouter.History.location,
+ }
+
+ @live
+ type renderProps<'prepared> = {
+ childRoutes: React.element,
+ prepared: 'prepared,
+ environment: RescriptRelay.Environment.t,
+ location: RelayRouter.History.location,
+ }
+
+ @live
+ type renderers<'prepared> = {
+ prepare: prepareProps => 'prepared,
+ prepareCode: option<(. prepareProps) => array>,
+ render: renderProps<'prepared> => React.element,
+ }
+ @live
+ let makePrepareProps = (.
+ ~environment: RescriptRelay.Environment.t,
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t,
+ ~location: RelayRouter.History.location,
+ ): prepareProps => {
+ ignore(pathParams)
+ ignore(queryParams)
+ {
+ environment: environment,
+ location: location,
+ }
+ }
+
+}
+
+
+
+@inline
+let routePattern = "/home"
+
+@live
+let makeLink = () => {
+ RelayRouter.Bindings.generatePath(routePattern, Dict.fromArray([]))
+}
+
+@live
+let isRouteActive = (~exact: bool=false, {pathname}: RelayRouter.History.location): bool => {
+ RelayRouter.Internal.matchPathWithOptions({"path": routePattern, "end": exact}, pathname)->Option.isSome
+}
+
+@live
+let useIsRouteActive = (~exact=false) => {
+ let location = RelayRouter.Utils.useLocation()
+ React.useMemo(() => location->isRouteActive(~exact), (location, exact))
+}
+
+
+
+@obj
+external makeRenderer: (
+ ~prepare: Internal.prepareProps => 'prepared,
+ ~prepareCode: Internal.prepareProps => array=?,
+ ~render: Internal.renderProps<'prepared> => React.element,
+) => Internal.renderers<'prepared> = ""
+
diff --git a/examples/client-rendering/src/routes/__generated__/Route__Root__PathParamsOnly_route.res b/examples/client-rendering/src/routes/__generated__/Route__Root__PathParamsOnly_route.res
new file mode 100644
index 0000000..fb5f239
--- /dev/null
+++ b/examples/client-rendering/src/routes/__generated__/Route__Root__PathParamsOnly_route.res
@@ -0,0 +1,93 @@
+// @generated
+// This file is autogenerated from `todoRoutes.json`, do not edit manually.
+@live
+type pathParams = {
+ pageSlug: string,
+}
+
+module Internal = {
+ @live
+ type prepareProps = {
+ environment: RescriptRelay.Environment.t,
+ location: RelayRouter.History.location,
+ ...pathParams,
+ }
+
+ @live
+ type renderProps<'prepared> = {
+ childRoutes: React.element,
+ prepared: 'prepared,
+ environment: RescriptRelay.Environment.t,
+ location: RelayRouter.History.location,
+ ...pathParams,
+ }
+
+ @live
+ type renderers<'prepared> = {
+ prepare: prepareProps => 'prepared,
+ prepareCode: option<(. prepareProps) => array>,
+ render: renderProps<'prepared> => React.element,
+ }
+ @live
+ let makePrepareProps = (.
+ ~environment: RescriptRelay.Environment.t,
+ ~pathParams: dict,
+ ~queryParams: RelayRouter.Bindings.QueryParams.t,
+ ~location: RelayRouter.History.location,
+ ): prepareProps => {
+ ignore(queryParams)
+ {
+ environment: environment,
+ location: location,
+ pageSlug: pathParams->Dict.getUnsafe("pageSlug"),
+ }
+ }
+
+}
+
+
+
+@inline
+let routePattern = "/other/:pageSlug"
+
+@live
+let makeLink = (~pageSlug: string) => {
+ RelayRouter.Bindings.generatePath(routePattern, Dict.fromArray([("pageSlug", (pageSlug :> string)->encodeURIComponent)]))
+}
+
+@live
+let isRouteActive = (~exact: bool=false, {pathname}: RelayRouter.History.location): bool => {
+ RelayRouter.Internal.matchPathWithOptions({"path": routePattern, "end": exact}, pathname)->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> = ""
+
+
+@live
+let parseRoute: (
+ string,
+ ~exact: bool=?,
+) => option = RelayRouter.Internal.parseRoute(
+ PathParams({routePattern: routePattern}),
+)
+
diff --git a/examples/client-rendering/src/routes/__generated__/Route__Root_route.res b/examples/client-rendering/src/routes/__generated__/Route__Root_route.res
index af3ea05..00e977d 100644
--- a/examples/client-rendering/src/routes/__generated__/Route__Root_route.res
+++ b/examples/client-rendering/src/routes/__generated__/Route__Root_route.res
@@ -6,6 +6,7 @@ module Internal = {
byStatus: option<[#"completed" | #"not-completed"]>,
byStatusDecoded: option,
todoId: option,
+ pageSlug: option,
}
@live
@@ -68,20 +69,22 @@ let useIsRouteActive = (~exact=false) => {
React.useMemo(() => location->isRouteActive(~exact), (location, exact))
}
@live
-type subRoute = [#Todos]
+type subRoute = [#Todos | #Home]
@live
-let getActiveSubRoute = (location: RelayRouter.History.location): option<[#Todos]> => {
+let getActiveSubRoute = (location: RelayRouter.History.location): option<[#Todos | #Home]> => {
let {pathname} = location
if RelayRouter.Internal.matchPath("/todos", pathname)->Option.isSome {
Some(#Todos)
+ } else if RelayRouter.Internal.matchPath("/home", pathname)->Option.isSome {
+ Some(#Home)
} else {
None
}
}
@live
-let useActiveSubRoute = (): option<[#Todos]> => {
+let useActiveSubRoute = (): option<[#Todos | #Home]> => {
let location = RelayRouter.Utils.useLocation()
React.useMemo(() => {
getActiveSubRoute(location)
diff --git a/examples/client-rendering/src/routes/__generated__/Routes.res b/examples/client-rendering/src/routes/__generated__/Routes.res
index 2cee768..810951d 100644
--- a/examples/client-rendering/src/routes/__generated__/Routes.res
+++ b/examples/client-rendering/src/routes/__generated__/Routes.res
@@ -35,4 +35,14 @@ module Root = {
}
}
+ /** [See route renderer](./Root__PathParamsOnly_route_renderer.res)*/
+ module PathParamsOnly = {
+ module Route = Route__Root__PathParamsOnly_route
+
+ }
+ /** [See route renderer](./Root__Home_route_renderer.res)*/
+ module Home = {
+ module Route = Route__Root__Home_route
+
+ }
}
\ No newline at end of file
diff --git a/examples/client-rendering/src/routes/todoRoutes.json b/examples/client-rendering/src/routes/todoRoutes.json
index 431809f..22791d7 100644
--- a/examples/client-rendering/src/routes/todoRoutes.json
+++ b/examples/client-rendering/src/routes/todoRoutes.json
@@ -28,5 +28,13 @@
"children": []
}
]
+ },
+ {
+ "path": "other/:pageSlug",
+ "name": "PathParamsOnly"
+ },
+ {
+ "path": "home",
+ "name": "Home"
}
]
diff --git a/examples/client-rendering/test/UrlEncodingDecoding.test.res b/examples/client-rendering/test/UrlEncodingDecoding.test.res
index e8638ba..4a6bfab 100644
--- a/examples/client-rendering/test/UrlEncodingDecoding.test.res
+++ b/examples/client-rendering/test/UrlEncodingDecoding.test.res
@@ -27,6 +27,22 @@ describe("makeLink", () => {
})
describe("parsing", () => {
+ test("parseRoute correctly decode query params", _t => {
+ let queryParams =
+ Routes.Root.Todos.Route.parseRoute(
+ "/todos?byValue=%2Fincorrect%20value%2C%20for%20url",
+ )->Option.getExn
+
+ expect(queryParams.byValue->Option.getUnsafe)->Expect.toBe("/incorrect value, for url")
+ })
+ test("parseRoute correctly decode path params", _t => {
+ let pathParams =
+ Routes.Root.PathParamsOnly.Route.parseRoute(
+ "/other/%2Fincorrect%20value%2C%20for%20url",
+ )->Option.getExn
+
+ expect(pathParams.pageSlug)->Expect.toBe("/incorrect value, for url")
+ })
test("parseRoute correctly decode path and query params", _t => {
let (pathParams, queryParams) =
Routes.Root.Todos.Single.Route.parseRoute("/todos/123?showMore=false")->Option.getExn
diff --git a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Codegen.res b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Codegen.res
index 0f8b1eb..1a41758 100644
--- a/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Codegen.res
+++ b/packages/rescript-relay-router/cli/RescriptRelayRouterCli__Codegen.res
@@ -791,7 +791,7 @@ let parseRoute: (
string,
~exact: bool=?,
) => option = RelayRouter.Internal.parseRoute(
- PathParams({routePattern}),
+ PathParams({routePattern: routePattern}),
)
\n`
} else {
diff --git a/packages/rescript-relay-router/src/RelayRouter__Internal.res b/packages/rescript-relay-router/src/RelayRouter__Internal.res
index c498c86..15b63b3 100644
--- a/packages/rescript-relay-router/src/RelayRouter__Internal.res
+++ b/packages/rescript-relay-router/src/RelayRouter__Internal.res
@@ -220,37 +220,40 @@ type rec routeKind<_> =
let parseRoute:
type params. routeKind => (string, ~exact: bool=?) => option =
- routeKind => (route, ~exact=false) =>
+ routeKind =>
switch routeKind {
| PathAndQueryParams({routePattern, parseQueryParams}) =>
- switch route->String.split("?") {
- | [pathName, search] =>
- matchPathWithOptions({"path": routePattern, "end": exact}, pathName)->Option.map(({
- params,
- }) => {
- let params = Obj.magic(params)
- let queryParams =
+ (route, ~exact=false) =>
+ switch route->String.split("?") {
+ | [pathName, search] =>
+ matchPathWithOptions({"path": routePattern, "end": exact}, pathName)->Option.map(({
+ params,
+ }) => {
+ let pathParams = Obj.magic(params)
+ let queryParams =
+ search
+ ->RelayRouter__Bindings.QueryParams.parse
+ ->parseQueryParams
+ (pathParams, queryParams)
+ })
+ | _ => None
+ }
+ | QueryParams({routePattern, parseQueryParams}) =>
+ (route, ~exact=false) =>
+ switch route->String.split("?") {
+ | [pathName, search] =>
+ matchPathWithOptions({"path": routePattern, "end": exact}, pathName)->Option.map(_ => {
search
->RelayRouter__Bindings.QueryParams.parse
->parseQueryParams
- (params, queryParams)
- })
- | _ => None
- }
- | QueryParams({routePattern, parseQueryParams}) =>
- matchPathWithOptions({"path": routePattern, "end": exact}, route)->Option.map(_ => {
- route
- ->RelayRouter__Bindings.QueryParams.parse
- ->parseQueryParams
- })
+ })
+ | _ => None
+ }
| PathParams({routePattern}) =>
- switch route->String.split("?") {
- | [pathName, _search] =>
- matchPathWithOptions({"path": routePattern, "end": exact}, pathName)->Option.map(({
+ (route, ~exact=false) =>
+ matchPathWithOptions({"path": routePattern, "end": exact}, route)->Option.map(({
params,
}) => {
Obj.magic(params)
})
- | _ => None
- }
}