-
-
Notifications
You must be signed in to change notification settings - Fork 87
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
Manual types with null cause issues with computed #369
Comments
Unfortunately that seems to be a limitation of TypeScript. There's two ways I'm aware of to solve it.
fullName: (): string => {
return `${state2$.fname.get()} ${state2$.lname.get()}`
},
import type { ObservableValue } from '@legendapp/state';
export type State2Type= ObservableValue<typeof state2$>; Or does anyone know of a better way to handle this? |
For React contexts, I let the observable to infer the type. Context types still do the job: type UserContextValue = {
fname: string
lname: string
fullName: () => string
}
const UserContext = createContext<Observable<UserContextValue> | null>(null)
function UserContextProvider({
userFromBackend: { fname, lname },
children,
}: { userFromBackend: { fname: string; lname: string }; children: ReactNode }) {
const value$ = useObservable({
fname,
lname,
fullName: () => `${value$.fname.get()} ${value$.lname.get()}`,
})
// The inferred type should match the context type
return <UserContext.Provider value={value$}>{children}</UserContext.Provider>
} The only downside of this approach is that removing props from the Context type won't raise any error: type UserContextValue = {
fname: string
// We don't need `lname` no more
// lname: string
fullName: () => string
}
const UserContext = createContext<Observable<UserContextValue> | null>(null)
function UserContextProvider({
userFromBackend: { fname, lname },
children,
}: { userFromBackend: { fname: string; lname: string }; children: ReactNode }) {
const value$ = useObservable({
fname,
// But we forgot to remove it from the observable
lname,
fullName: () => `${value$.fname.get()} ${value$.lname.get()}`,
})
// No error has been risen
return <UserContext.Provider value={value$}>{children}</UserContext.Provider>
} |
I didn't catch that. Could you please clarify this approach with a more complete example? |
It wouldn't be useful for this case, but it's a way to extract the data type of an observable. So for example: type UserContextValue = {
fname: string
lname: string
fullName: () => string
}
const state$ = observable<UserContextValue>({...})
type StateType = ObservableValue<typeof state$>;
// StateType == UserContextValue But in this case I think you're probably best off with |
Thank you! To summarize, here's the full example with React context: type UserContextValue = {
fname: string
lname: string
fullName: () => string
}
const UserContext = createContext<Observable<UserContextValue> | null>(null)
function UserContextProvider({
userFromBackend: { fname, lname },
children,
}: { userFromBackend: { fname: string; lname: string }; children: ReactNode }) {
const value$ = useObservable<UserContextValue>({
fname,
lname,
fullName: (): string => `${value$.fname.get()} ${value$.lname.get()}`,
})
return <UserContext.Provider value={value$}>{children}</UserContext.Provider>
} |
Also FYI you actually can type
So then it would be typed to allow But if you make it just a string like this: type UserContextValue = {
fname: string
lname: string
fullName: string
} It's just an observable string:
So |
This makes sense! I would go with |
I also found out that if the computed values may return null, it must be declared as const state$ = observable<{
fname: string
lname: string
// `string | null` would cause TS2769: No overload matches this call.
fullName: () => string | null
}>({
fname: 'Annyong',
lname: 'Bluth',
// A child is computed
fullName: (): string | null => {
return Math.random() ? `${state$.fname.get()} ${state$.lname.get()}` : null
},
})
So now it is not obvious what to choose 😅. I think I will stick with the But I had a type like this one: export type UserState = {
fname: string
lname: string
fullName: string | null
} And it was convenient to: fullName: (): UserState['fullName'] => {
return Math.random() ? `${state$.fname.get()} ${state$.lname.get()}` : null
}, And now it's not possible (with |
One more finding: There's no need to manually provide a return type if the return value is not a primitive: const state$ = observable<{
fname: string
lname: string
// Will not work with the `() => T` style!
fullName: {
value: string
}
}>({
fname: 'Annyong',
lname: 'Bluth',
// No return type provided, no errors
fullName: () => {
return {
value: `${state$.fname.get()} ${state$.lname.get()}`,
}
},
}) |
Oh that's a very interesting finding! And that null issue is probably something fixable. I'll reopen this to make sure I remember to take a look at that: #369 (comment) |
Taking an example from this page, manually providing type annotations raises a TypeScript error:
It's essential for me to provide typings manually, because I use React context so I have to export state's type for the hook.
The text was updated successfully, but these errors were encountered: