[SOLVED] Typescript empty object for react state

Issue

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?

Solution

  1. Well, first of all get rid of any ases – they’re you pinky-swearing that "I know perfectly well what I’m doing with my types".
  2. Make the type for the inventory state field something like inventory: Inventory | null, and initialize it with null.
  3. In your render function, destructure inventory into a local: const {inventory} = this.state;. (This helps TS with inference.)
  4. if(inventory === null) return <Loading />;
  5. The if/return will help TypeScript narrow the type of inventory for the rest of the render function; after all, if it’s not null, 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.

Answered By – AKX

Answer Checked By – Gilberto Lyons (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *