-
Notifications
You must be signed in to change notification settings - Fork 669
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
Add TypedCreateSelector interface to support creating a pre-typed version of createSelector #500
Conversation
Would this still be compatible with old types when not giving the state? |
From the view of library consumer this change only adds a new utility type and does not change the old types. Before, people do this // someSelector.ts
import { createSelector } from 'reselect';
import { RootState } from 'somewhere';
const someSelector = createSelector((state: AppState) => someState, someCombiner); After, people can still do as before, but they have one more option // utils.ts
import { TypedCreateSelector, createSelector } from 'reselector';
import { RootState } from 'somewhere';
export const createAppSelector: TypedCreateSelector<RootState> = createSelector;
// someSelector.ts
import { createAppSelector } from 'utils.ts';
const someSelector = createAppSelector(state => someState, someCombiner); // `state` here will be inferred to `RootState`, no need for being explicit
|
I like this idea, but the PR is stale against |
So I think this whole PR can be replaced by a simple wrapper type that we provide in either documentation or we add it and export somewhere (might need to workshop the name). It looks like this (95% sure this works)... type State = { users: Users, roles: Roles }
const buildCreateStoreSelector =
<Store>() =>
<FirstResult, FinalResult, Selectors extends SelectorArray>(
first: Selector<Store, FirstResult, never>,
...selectors: [
...Selectors,
(
...args: SelectorResultArray<[typeof first, ...Selectors]>
) => FinalResult
]
) =>
createSelector(first, ...selectors)
const createStoreSelector = buildCreateStoreSelector<State>()
const selector1 = createStoreSelector(state => state.users, ...)
const selector2 = createStoreSelector(state => state.roles, ...) @markerikson I wouldn't mind adding this in as a utility function for this pattern. I've used something similar in a lot of the projects I've been on. On the other hand, this could be a simple tip in the docs somewhere, too. Personal aside...I've started using this pattern instead: // utils.ts
export type SimpleSelector<R> = (store: RootStore) => R // RootStore is my application's store type
export const simpleSelector = <R>(selector: SimpleSelector<R>): SimpleSelector<R> => selector
export const useSelector = createSelectorHook<RootStore>() // auth.selectors.ts
import { createSelector } from '@reduxjs/toolkit'
import { simpleSelector } from './utils'
const selectAuthenticatedSlice = simpleSelector(({ auth: { user } }) =>
user.state === 'authenticated' ? user : undefined
)
export const authSelectors = {
user: {
name: createSelector(selectAuthenticatedSlice, (slice) => slice?.name),
id: createSelector(selectAuthenticatedSlice, (slice) => slice?.id),
},
users: {
value: simpleSelector(({ auth: { users } }) => (users.state === 'loaded' ? users.value : undefined)),
state: simpleSelector((store) => store.auth.users.state),
},
} then I use it like this... // UserId.tsx
import { useSelector } from './utils'
import { authSelectors } from './auth.selectors'
const UserId = () => {
const user = useSelector(authSelectors.user.id)
return <p>User ID is {user}</p>
} |
I'd say start as a copy-paste pattern, and only add if we need to. New APIs are forever :) |
Yeah, my preference is just making it a copy-paste kinda thing. It's a good bit of overhead to add to the library (tests, making sure it works moving forward, etc). |
Please make sure that the return type is preserved even after applying some equality checks like shallow equal or deep equal. I observed that using useSelector hook with one of the equality checks loses the type of the returned value, being transformed to any |
@gheorghesava-mck that sounds like an entirely different problem. Can you post a separate issue for that, and please include a CodeSandbox or repo that shows the problem happening? |
@markerikson just tested with the latest version and it worked fine. Seems I was behind with some patch versions for react-redux. My apologies! |
Any news? |
Similar to
TypedUseSelectorHook
, this interface allows us to easily create a version ofcreateSelector
that is properly typed for our store's root state.Code example:
This frees us from having to type selectors all the time in some codebase:
Instead of
Now we can do this and it's still type-safe:
There is also a question on this feature on StackOverflow: How to define a version of createSelector typed for my app?
In this PR I converted type of
createSelector
into an interface so thatCreateSelector
andTypedCreateSelector
have very similar interface structures. I think this will make it easier to update these types.I also added typing tests for the new interface.