I am trying to create a type for a specific object that can be empty, let's start at the bottom and work our way up to the React class.
Note: I am not using hooks, but even f your answer was in hooks, it can't be that hard to translate.
We first need to define the state for our class:
export default interface CharacterInventoryTabsState {
table: string;
dark_tables: boolean;
loading: boolean;
inventory: Inventory | {};
}
The issue is the inventory
property. So lets look at the Inventory
type:
export default interface Inventory {
equipped: InventoryDetails[] | [];
inventory: InventoryDetails[] | [];
quest_items: InventoryDetails[] | [];
usable_items: InventoryDetails[] | [];
savable_sets: SetDetails[] | [];
usable_sets: SetDetails[] | [];
sets: {[key: string]: InventoryDetails[] | []}
set_equipped: boolean;
}
The inventory object is made up of other objects and their associated types. Fantastic. So lets look at the react class:
The below is just a snippit of the class.
export default class CharacterInventoryTabs extends React.Component<any, CharacterInventoryTabsState> {
constructor(props: any) {
super(props);
// ...
this.state = {
table: 'Inventory',
dark_tables: false,
loading: true,
inventory: {} as Inventory, // I saw this trick to initialize "empty objects"
}
}
componentDidMount() {
watchForDarkModeInventoryChange(this);
(new Ajax()).setRoute('character/' this.props.character_id '/inventory').doAjaxCall('get', (result: AxiosResponse) => {
this.setState({
loading: false,
inventory: result.data, // => Here we initialize inventory but ...
});
}, (error: AxiosError) => {
console.log(error);
})
}
// ... some where in the render method we do:
render() {
// ... Loader logic so we would never access the state until loading was set to false.
return(
<InventoryTable dark_table={this.state.dark_tables} character_id=. {this.props.character_id} inventory={this.state.inventory.inventory} />
);
}
}
The issue is simple: Property 'inventory' does not exist on type '{} | Inventory'. Property 'inventory' does not exist on type '{}'.
Well duh, its not initialized till after the ajax call and I have a "if loading show the loader jazz" so it would "technically be initialized after the ajax call" but how do I make typescript be quiet about this, aside from the dreaded: any
.
I saw the trick of const var = {} as Type
in another stack question related to this, but alas it does not seem to work for me.
What is the correct way to handle a situation like this?
CodePudding user response:
- Well, first of all get rid of any
as
es - they're you pinky-swearing that "I know perfectly well what I'm doing with my types". - Make the type for the inventory state field something like
inventory: Inventory | null
, and initialize it withnull
. - In your render function, destructure
inventory
into a local:const {inventory} = this.state;
. (This helps TS with inference.) if(inventory === null) return <Loading />;
- The if/return will help TypeScript narrow the type of
inventory
for the rest of the render function; after all, if it's notnull
, it can only be a real Inventory.
Unless you need loading
for anything else, you could also drop it from state, since you can tell whether you're loading based on just whether inventory !== null
.