Skip to content

Commit

Permalink
feat(routing): useRouteArgsIfActive
Browse files Browse the repository at this point in the history
This hook provides access to the routes params/query/meta if the route is active and returns undefined if it's not active
  • Loading branch information
UberMouse committed Dec 6, 2022
1 parent 8f9a4d4 commit f28dc6f
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export {
type RouteArgumentFunctions,
buildCreateRoute,
matchRoute,
useIsRouteActive,
useRouteArgsIfActive,
} from "./routing";
export { loggingMetaOptions } from "./useService";
export { lazy } from "./lazy";
1 change: 1 addition & 0 deletions src/routing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ export {
type Routing404Event,
} from "./handleLocationChange";
export { useIsRouteActive } from "./useIsRouteActive";
export { useRouteArgsIfActive } from "./useRouteArgsIfActive";

export { RoutingContext } from "./providers";
3 changes: 2 additions & 1 deletion src/routing/useIsRouteActive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { AnyRoute } from "./createRoute";
import { useActiveRouteEvents } from "./providers";

/**
* @public
* Accepts Routes and returns true if any route is currently active. False if not.
*
* If used outside of a RoutingContext, an error will be thrown.
* @param routes - the routes to check
* @returns true if any route is active, false if not
* @throws if used outside of a RoutingContext
* @throws if used outside of an xstate-tree root
*/
export function useIsRouteActive(...routes: AnyRoute[]): boolean {
const activeRouteEvents = useActiveRouteEvents();
Expand Down
59 changes: 59 additions & 0 deletions src/routing/useRouteArgsIfActive.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { renderHook } from "@testing-library/react";
import { createMemoryHistory } from "history";
import React from "react";
import { z } from "zod";

import { buildCreateRoute } from "./createRoute";
import { RoutingContext } from "./providers";
import { useRouteArgsIfActive } from "./useRouteArgsIfActive";

const createRoute = buildCreateRoute(() => createMemoryHistory<any>(), "/");
const fooRoute = createRoute.simpleRoute()({
event: "foo",
url: "/:foo",
paramsSchema: z.object({ foo: z.string() }),
querySchema: z.object({ bar: z.string() }),
});
describe("useRouteArgsIfActive", () => {
it("returns undefined if the route is not active", () => {
const { result } = renderHook(() => useRouteArgsIfActive(fooRoute), {
wrapper: ({ children }) => (
<RoutingContext.Provider value={{ activeRouteEvents: { current: [] } }}>
{children}
</RoutingContext.Provider>
),
});

expect(result.current).toBe(undefined);
});

it("returns the routes arguments if the route is active", () => {
const { result } = renderHook(() => useRouteArgsIfActive(fooRoute), {
wrapper: ({ children }) => (
<RoutingContext.Provider
value={{
activeRouteEvents: {
current: [
{
type: "foo",
meta: {},
originalUrl: "",
params: { foo: "bar" },
query: { bar: "baz" },
},
],
},
}}
>
{children}
</RoutingContext.Provider>
),
});

expect(result.current).toEqual({
params: { foo: "bar" },
query: { bar: "baz" },
meta: {},
});
});
});
39 changes: 39 additions & 0 deletions src/routing/useRouteArgsIfActive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { assertIsDefined } from "../utils";

import { AnyRoute, ArgumentsForRoute } from "./createRoute";
import { useActiveRouteEvents } from "./providers";
import { useIsRouteActive } from "./useIsRouteActive";

/**
* @public
* Returns the arguments for the given route if the route is active.
* Returns undefined if the route is not active.
*
* @param route - the route to get the arguments for
* @returns the arguments for the given route if the route is active, undefined otherwise
* @throws if used outside of an xstate-tree root
*/
export function useRouteArgsIfActive<TRoute extends AnyRoute>(
route: TRoute
): ArgumentsForRoute<TRoute> | undefined {
const isActive = useIsRouteActive(route);
const activeRoutes = useActiveRouteEvents();

if (!isActive) {
return undefined;
}

const activeRoute = activeRoutes?.find(
(activeRoute) => activeRoute.type === route.event
);
assertIsDefined(
activeRoute,
"active route is not defined, but the route is active??"
);

return {
params: activeRoute.params,
query: activeRoute.query,
meta: activeRoute.meta,
} as ArgumentsForRoute<TRoute>;
}
6 changes: 6 additions & 0 deletions xstate-tree.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ export enum SlotType {
// @public (undocumented)
export type StyledLink<TStyleProps = {}> = <TRoute extends AnyRoute>(props: LinkProps<TRoute> & TStyleProps) => JSX.Element;

// @public
export function useIsRouteActive(...routes: AnyRoute[]): boolean;

// @public
export function useRouteArgsIfActive<TRoute extends AnyRoute>(route: TRoute): ArgumentsForRoute<TRoute> | undefined;

// @public (undocumented)
export type V1Selectors<TContext, TEvent, TSelectors, TMatches> = (ctx: TContext, canHandleEvent: (e: TEvent) => boolean, inState: TMatches, __currentState: never) => TSelectors;

Expand Down

0 comments on commit f28dc6f

Please sign in to comment.