I'm creating a generic class named Loadable<T>
, with two fields. One is named state
, and contains the current state of the loadable resource. The second is named data
, and contains the value of the loadable resource.
state
is defined as "loading" | "loaded" | "error"
, and I'd like to type of data
to change based on the current value of state
.
For example, if state
is "loading"
, then data
should be null
. I cannot figure out the syntax to do this.
I've created a type
named LoadableState<T, U>
which is defined as:
type LoadableState<T, U> =
T extends "loaded" ? U :
T extends "loading" ? null :
T extends "error" ? string :
never;
Where T
passed must be the type "loaded" | "loading" | "error"
.
This part works, but trying to define data
from this does not.
export class Loadable<T> {
public state: "loaded" | "loading" | "error" = "loading";
public data: LoadableState<state, T>;
// ^^^^^ Cannot find name 'state`
}
TypeScript throws the following error: Cannot find name 'state'
Other things I've tried:
public data: LoadableState<this.state, T>;
// Duplicate identified 'state'
public data: LoadableState<typeof this.state, T>;
// Cannot find name 'this'
public data: LoadableState<typeof state, T>;
// Cannot find name 'state'
public data: state extends "loaded" ? T :
state extends "loading" ? null :
state extends "error" ? string :
never;
// Cannot find name 'state'
I'm not sure if this is actual possible. If this isn't, is there another solution to this problem?
Syntax: We can create the conditional types with the used of ternary operator and extends in TypeScript. Type1 extends Type2 ? for One value : for different value; Here extend works as compare function which checks is Type1 have properties of Type2 if yes then it jumps to true branch else in the false branch.
Using ?: with undefined as type definition While there are no errors with this interface definition, it is inferred the property value could undefined without explicitly defining the property type as undefined . In case the middleName property doesn't get a value, by default, its value will be undefined .
TypeScript comes with some built-in type guards: typeof and instanceof . They're very useful, but have limited scope. For example, typeof can only be used to check string , number , bigint , function , boolean , symbol , object , and undefined types.
I don't think there's a way (or at least not a clean way) to have the type of one class property to depend on the current value of another property.
You're better off combining both these properties into a single object:
type LoadState<T> = {
state: "loading",
} | {
state: "loaded",
data: T
} | {
state: "error",
data: string; // or maybe 'message'
}
export class Loadable<T> {
state: LoadState<T> = { state: "loading" }
}
Though the class wrapper isn't actually very useful here, you might consider not using the class at all.
When using this type, Typescript will enforce that you check the state
before trying to access data:
function handleLoadState<T>(loadState: LoadState<T>) {
loadState.data // ERROR, can't access data, since it might not exist
if(loadState.state === "loaded")
loadState.data // okay
}
}
This pattern is called a Discriminated Union and is quite commonly used in Typescript.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With