getState - greydragon888/real-router GitHub Wiki

router.getState

1. Overview

  • What it does: Returns the current router state. The state contains route name, parameters, and path.
  • When to use:
    • To get information about the current route
    • To check current route parameters
    • For conditional logic based on current state
    • For integration with UI frameworks

2. Signature

router.getState<P extends Params = Params>(): State<P> | undefined

Types

type State<P extends Params = Params> = {
  name: string;
  params: P;
  path: string;
};

type Params = Record<string, unknown>;

Usage Examples

// Basic usage
const state = router.getState();
if (state) {
  console.log(state.name);   // 'users.view'
  console.log(state.params); // { id: '123' }
  console.log(state.path);   // '/users/123'
}

// With typed parameters
interface UserParams {
  id: string;
  tab?: string;
}

const state = router.getState<UserParams>();
if (state) {
  console.log(state.params.id);  // Typed as string
}

// Check state before navigation
const currentState = router.getState();
if (currentState?.name !== 'login') {
  router.navigate('login');
}

// React integration
function CurrentRoute() {
  const [state, setState] = useState(router.getState());

  useEffect(() => {
    return router.subscribe((routeState) => {
      setState(routeState.route);
    });
  }, []);

  return state ? <div>{state.name}</div> : null;
}

3. Parameters

The method takes no parameters.

Generic Types

Parameter Default Description
P Params Route parameters type

4. Return Value

  • Type: State<P> | undefined
  • undefined: If router is not started or was stopped
  • State: If router is started and has current state

State Structure

Field Type Description
name string Route name (dot-separated)
params P Route parameters
path string URL path

Characteristics

  • Returned state is frozen (deeply frozen) — modification attempts will throw error in strict mode
  • This is a cached value — repeated calls return the same object without overhead
  • State is updated only as a result of successful navigation or navigateToNotFound()

5. Side Effects

The method has no side effects — it's a pure getter function.

6. Possible Errors

The method does not throw errors.

7. Related Methods

Method When to use
getPreviousState() Getting previous state
isActive() Checking router state
start() Starting the router
stop() Stopping the router

8. Behavior

Main Scenarios

  • Non-started router: Returns undefined
  • Started router: Returns current state
  • After stopping: Returns undefined

Test Examples

import { getPluginApi } from "@real-router/core/api";
const pluginApi = getPluginApi(router);
// Returns undefined when router is not started
router.stop();
expect(router.getState()).toBe(undefined);

// Returns set state
const state = pluginApi.makeState("home", { id: 1 }, "/home");
router.setState(state);
expect(router.getState()).toStrictEqual(state);

State Lifecycle

// 1. Before start
const router = createRouter(routes);
expect(router.getState()).toBe(undefined);

// 2. After start
await router.start("/home");
expect(router.getState()).toBeDefined();
expect(router.getState()?.name).toBe("home");

// 3. After navigation
await router.navigate("users");
expect(router.getState()?.name).toBe("users");

// 4. After stop
router.stop();
expect(router.getState()).toBe(undefined);

Immutability (Freezing)

// State is frozen
const state = router.getState();
expect(Object.isFrozen(state)).toBe(true);

// Modification attempt throws error (strict mode)
expect(() => {
  state!.name = "modified";
}).toThrowError();

Caching

// Repeated calls return same object
const state1 = router.getState();
const state2 = router.getState();
expect(state1).toBe(state2); // Referential equality

Edge Cases

  • Non-started router: undefined
  • After stop(): undefined
  • After setState(undefined): undefined
  • With generic types: Returns typed state

Guarantees

  • Returned state is immutable (deeply frozen)
  • Repeated calls return same object (caching)
  • undefined when router is not active
  • Typing through generic P
  • Has no side effects

Migration from router5

Version Comparison

router5 (master) Real Router (current)
Signature () => State | null <P>() => State<P> | undefined
Return when absent null undefined
Immutability No Deeply frozen
Caching No (direct access) Cached frozen state
TypeScript generics No P parameter type

Breaking Changes

Return Type Change from null to undefined

router5 (old):

router.getState = () => routerState; // routerState: State | null

// Check for null
const state = router.getState();
if (state === null) {
  console.log("Router not started");
}

Real Router (new):

router.getState = (): State<P> | undefined => frozenState;

// Check for undefined
const state = router.getState();
if (state === undefined) {
  console.log("Router not started");
}

Impact: Code explicitly checking === null will no longer correctly detect missing state. However checks like if (!state) or if (state) will continue to work.

State Immutability

router5 (old):

const state = router.getState();
state.params.newParam = "value"; // OK — can modify
state.name = "modified"; // OK — can modify

Real Router (new):

const state = router.getState();
state.params.newParam = "value"; // TypeError in strict mode
state.name = "modified"; // TypeError in strict mode

Impact: Code that modifies returned state directly will get an error. This is an anti-pattern violating encapsulation. Create new object via spread or makeState().

Migration Checklist

  • Replace === null checks with === undefined or use !state
  • Ensure code doesn't modify returned state directly
  • Use spread { ...state, ... } to create modified copy

Migration Examples

null → undefined Check

// ❌ Old code
if (router.getState() === null) {
  console.log("Not started");
}

// ✅ New code — explicit check
if (router.getState() === undefined) {
  console.log("Not started");
}

// ✅ Or — universal check (works in both cases)
if (!router.getState()) {
  console.log("Not started");
}

State Modification → Creating Copy

import { getPluginApi } from "@real-router/core/api";
const pluginApi = getPluginApi(router);
// ❌ Old code — direct modification
const state = router.getState();
state.params.newParam = "value";

// ✅ New code — create copy via spread
const state = router.getState();
const modified = {
  ...state,
  params: { ...state.params, newParam: "value" },
};

// ✅ Or use makeState
const modified = pluginApi.makeState(
  state.name,
  { ...state.params, newParam: "value" },
  state.path,
);

TypeScript — Using Generic Types

// ❌ Old code — without typing
const state = router.getState();
const id = state?.params.id as string; // Type assertion

// ✅ New code — with typing
interface MyParams {
  id: string;
  filter?: string;
}
const state = router.getState<MyParams>();
const id = state?.params.id; // Typed as string (P = MyParams)
⚠️ **GitHub.com Fallback** ⚠️