-
Notifications
You must be signed in to change notification settings - Fork 531
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
feat(ts): allow custom ui state and route state in routing #4816
Conversation
While I couldn't find a way to make InstantSearch itself generic (this gets passed to many places, which then loses generic), using the routing middleware directly is possible like this now ```ts import instantsearch from 'instantsearch.js/es' import { history } from 'instantsearch.js/es/lib/routers'; import { createRouterMiddleware } from 'instantsearch.js/es/middlewares'; import { StateMapping, UiState } from 'instantsearch.js/es/types'; type SwagIndexUiState = { swag: boolean }; type SwagUiState = { [indexName: string]: SwagIndexUiState }; const stateMapping: StateMapping<UiState & SwagUiState, SwagUiState> = { stateToRoute(uiState) { return Object.keys(uiState).reduce( (state, indexId) => ({ ...state, [indexId]: { swag: uiState[indexId].swag }, }), {} ); }, routeToState(routeState = {}) { return Object.keys(routeState).reduce( (state, indexId) => ({ ...state, [indexId]: routeState[indexId], }), {} ); }, }; const search = instantsearch(); search.use( createRouterMiddleware<UiState & SwagUiState, SwagUiState>({ router: history(), stateMapping, }) ); search.addWidgets([instantsearch.widgets.hits({ container })]); ```
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit cc02f67:
|
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.
Can we let users not have to provide UiState
, but we augment it on our side?
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.
is this what you expect @francoischalifour ?
router = historyRouter(), | ||
stateMapping = simpleStateMapping(), | ||
router = historyRouter<TRouteState>(), | ||
// technically this is wrong, as without stateMapping parameter given, the routeState *must* be UiState |
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.
Can you explain a bit more?
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.
simpleStateMapping is a StateMapping<UiState, UiState>
, and createRouterMiddleware
can be called with a different generic than UiState, so the types don't really match. The only thing I can think of otherwise is only conditionally allowing stateMapping to be conditional, but I couldn't find how
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 tried things with branding, but still could not get anything working as soon as simpleStatemapping is actually used (as it uses UiState (const stateMapping: StateMapping<UiState, TRouteState> | StateMapping<TRouteState, TRouteState>
becomes a union, and even though in that case both types are the same, typescript doesn't seem to be able to know that)
things I have tried
export type RouterProps<
TUiState extends UiState = UiState,
TRouteState = UiState & { ___default: true }
> = TUiState extends { ___default: true }
? {
router?: Router<TRouteState>;
stateMapping?: never;
}
: {
router?: Router<TRouteState>;
stateMapping: StateMapping<TUiState, TRouteState>;
};
export const createRouterMiddleware = <
TUiState extends UiState = UiState,
TRouteState = UiState & { ___default: true }
>(
props: RouterProps<TUiState, TRouteState> = {} as RouterProps<
TUiState,
TRouteState
>
): InternalMiddleware<TUiState> => {
const {
router = historyRouter<TRouteState>(),
stateMapping = simpleStateMapping<TRouteState>(),
} = props;
and variations of it
// We have to cast simpleStateMapping as a StateMapping<TUiState, TRouteState>. | ||
// this is needed because simpleStateMapping is StateMapping<TUiState, TUiState>. | ||
// While it's only used when UiState and RouteState are the same, unfortunately | ||
// TypeScript still considers them separate types. |
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.
new comment @francoischalifour
export type MiddlewareDefinition<TUiState extends UiState = UiState> = { | ||
onStateChange(options: { uiState: TUiState }): void; |
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.
Isn't it supposed to be this here too?
export type MiddlewareDefinition<TUiState extends UiState = UiState> = { | |
onStateChange(options: { uiState: TUiState }): void; | |
export type MiddlewareDefinition<TUiState = Record<string, unknown>> = { | |
onStateChange(options: { uiState: UiState & TUiState }): void; |
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 was thinking every place except the root only needs to deal with UiState and likely no extensions (not really user code), so all except the entry point expects the consumer to make the mix
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.
Right, that looks fair like that!
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.
Right, that looks fair like that!
export type MiddlewareDefinition<TUiState extends UiState = UiState> = { | ||
onStateChange(options: { uiState: TUiState }): void; |
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.
Right, that looks fair like that!
export type MiddlewareDefinition<TUiState extends UiState = UiState> = { | ||
onStateChange(options: { uiState: TUiState }): void; |
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.
Right, that looks fair like that!
Right, that looks fair like that! |
1 similar comment
Right, that looks fair like that! |
InstantSearch generic:
generic middleware
references
fixes DX-2298