-
-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(router): Add useHistoryState hook for type-safe state management #3967
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This commit introduces a new `validateState` function to handle state validation within the router. It supports various validation methods, including standard validation and parsing, and integrates with route options to validate state during routing.
This commit introduces the TStateValidator type across various routing components, enhancing the validation capabilities for state management. The changes include updates to the createFileRoute and Route classes, as well as adjustments in related files to support the new state validation feature.
This commit introduces the `useHistoryState` hook and related types across various routing components, improving state management capabilities. Updates include modifications to the Route and createRoute functions to support the new state handling feature.
This commit introduces a new section in the BaseTanStackRouterDevtoolsPanel to display state parameters when available.
This commit introduces the `useHistoryState` hook along with its associated types, enhancing the state management capabilities within the router. The new implementation allows for more flexible state handling and selection options, improving overall functionality.
This commit introduces new TypeScript types related to the `useHistoryState` hook, improving type safety and flexibility in state management within the router. The new types facilitate better state selection and handling options, enhancing the overall functionality of the routing system.
…roved state selection This commit refactoring the state selection logic to utilize `useRouterState`.
…resolution This commit modifies the `ResolveUseHistoryState` and ` UseHistoryStateResult` types
…ryState This commit updates the `useHistoryState` hook to utilize `useLocation` for state management instead of `useRouterState`, improving the clarity and efficiency of state selection logic.
…tools panel This commit introduces a new function to filter out internal properties (keys starting with '__') from the router state before it is displayed in the BaseTanStackRouterDevtoolsPanel.
…nction in useHistoryState
…n useHistoryState
…router Router class
…TanStackRouterDevtoolsPanel
…toolsPanel by merging strict state
View your CI Pipeline Execution ↗ for commit b81f26d. ☁️ Nx Cloud last updated this comment at |
@@ -1201,6 +1201,46 @@ export class RouterCore< | |||
return [parentSearch, {}, searchParamError] | |||
} | |||
})() | |||
const [preMatchState, strictMatchState, stateError]: [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is almost the same as search validation. can we move the identical parts into a reusable function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I unified the shared logic between validateState and validateSearch, including the filter step. Splitting it further would require a helper with too many parameters and little added value, so I’d prefer to keep it as is.
packages/router-core/src/router.ts
Outdated
@@ -2962,6 +3033,33 @@ export function getInitialRouterState( | |||
statusCode: 200, | |||
} | |||
} | |||
function validateState(validateState: AnyValidator, input: unknown): unknown { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, can we move the validation of both state and search into a reusable function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in this commit: the validation logic for state/search has been extracted to a common function.
@@ -89,6 +89,21 @@ export type ResolveSearchValidatorInput<TValidator> = | |||
? ResolveSearchValidatorInputFn<TValidator['parse']> | |||
: ResolveSearchValidatorInputFn<TValidator> | |||
|
|||
export type ResolveStateValidatorInputFn<TValidator> = TValidator extends ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the same type as for search, right? if yes, we should reuse
maybe just rename the search type to something generic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 67f84d1
(#3967) &88c7126
(#3967) — both search and state now share one generic validator type.
select: (match: any) => { | ||
const matchState = match.state; | ||
const filteredState = Object.fromEntries( | ||
Object.entries(matchState).filter(([key]) => !(key.startsWith('__') || key === 'key')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the logic to filter internal state should be put into one single central location and be reused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 7847424
(#3967) and 799541f
(#3967)
I pulled the internal-state filter into a single helper omitInternalKeys inside @tanstack/history and switched every call-site to use it.
can you please add type tests, similar to what we do for search? also, what happens if I open up /posts directly and it has a state validator, but no state is present? |
…lterInternalState to omitInternalKeys
> can you please add type tests, similar to what we do for search?added tests > what happens if I open up /posts directly and it has a state validator, but no state is present?This behavior is same as how Current BehaviorWhen you navigate directly to a /posts with a state validator but no state present:
ExamplevalidateState: (input) =>
z.object({
example: z.string().default('default-example'),
count: z.number().default(0),
options: z.array(z.string()).default([]),
}).parse(input), Type System Handles This by
|
Hi @naoya7076, thanks for putting this up together. I have a question. How can one access to the route-specific history state type? For example, currently to access the type of history state, the |
Currently, This PR(useHistoryState) doesn't support accessing route-specific history state types like The only available approach currently is to manually define the type: type ExampleRouteState = {
color: string
// Same structure as defined in validateState
} You're correct that the current HistoryState is an aggregation of all route states, and there's no |
I guess we can access the route-specific history state type by |
Proposal
Overview
This PR introduces the new
useHistoryState
hook and related functionality to enable type-safe history state management in TanStack Router.Background
In the current implementation of TanStack Router, to pass type-safe state (History State) between routes that isn't part of the path or params, we need to define types globally. This leads to two main issues:
For example:
#284
When migrating from react-router to TanStack Router, developers want to be able to handle state in a type-safe manner, similar to how they could with react-router:
This PR addresses these issues by adding route-specific, type-safe history state management.
Changes
useHistoryState
hook export in the main package indexFileRoute
class to support state validation withTStateValidator
generic parameterexamples/react/basic-history-state
demonstrating usage patternsKey features of useHistoryState
Example usage
Users can define state validation on routes:
Then use it in components: