Mismatched non-intersecting categories with TypeScript

I have an object that I need to conditionally render some JSX based on certain properties. I want to restrict access to specific parts of the object until certain conditions are met. Here is my scenario:

const { alpha, bravo } = myObject;

if (alpha.loading === true) {
    // At this point, no properties on bravo should be accessible
  // this means
  // bravo.active must throw an undefined error here
    return "Loading"
}

if (alpha.error === true) {
    return "Error"
}

// Bravo should only be accessible here
// this means:
// type = {
//   signedIn: boolean  
//

if(bravo.signedIn === false {
   return "Guest"
}

return "Signed In"

Currently, I am using the following type definition for myObject:

type MyObjectType = {
    alpha: {
      loading: true;
    }
} | {
    alpha: {
      loading: false;
      error: true;
    }
} | {
    alpha: {
      loading: false;
      error: false;
    }
    bravo: {
      signedIn: false
    }
} | {
    alpha: {
      loading: false;
      error: false;
    }
    bravo: {
      signedIn: true;
      name: string;
    }
}

TypeScript throws an error saying

Property 'signedIn' does not exist on type MyObjectType
. How can I destructure this property along with auth but ensure that none of its properties are accessible before auth.loading === false and auth.error == false? The same applies to bravo.name, which should only be available when bravo.signedIn === true. Any assistance on this matter would be greatly appreciated.

Thank you in advance for your help!

Answer №1

To handle related properties effectively, I devised a method involving keeping the object myObject as a flat object with flags. The various states are defined as follows:

type State1 = {
    loading: true;
}

type State2 = {
    loading: false;
    error: true;
}

type State3 = {
    loading: false;
    error: false;
    signedIn: false;
}

type State4 = {
    loading: false;
    error: false;
    signedIn: true;
    name: string;
}

type MyObjectType = State1 | State2 | State3 | State4;

Although the types have generic names for illustrative purposes, choosing more descriptive types is recommended. An improved naming suggestion could be

type PageState = Loading | Error | Unsigned | LoggedIn
, but for now, we will stick with the generic names.

By utilizing these types, it becomes feasible to establish relationships between values and determine the state of the object using type narrowing. Let's walk through a clear example:

if (myObject.loading === true) {
    /* Only State1 inside the if */

    console.log(myObject.name);     //error - no 'name' property in State1
    console.log(myObject.signedIn); //error - no 'signedIn' property in State1
    console.log(myObject.error);    //error - no 'error' property in State1

    return "Loading";
}
/* We've filtered State1. Below `myObject` can only be State2 | State3 | State4 */

if (myObject.error === true) {
    /* Only State2 inside the if */

    if (myObject.loading === true) { //error - 'loading' cannot be true in State2
        console.log("hello")
    }
    console.log(myObject.name);      //error - no 'name' property in State2
    console.log(myObject.signedIn);  //error - no 'signedIn' property in State2

    return "Error";
}
// More logical checks...

return "Signed In";

This methodology works well when accessing properties directly from myObject. Destructuring, unfortunately, does not work due to the absence of properties in all states. For instance:

const { loading, error, signedIn, name } = myObject;

The TypeScript compiler struggles to narrow down the type even after conditional checks. This limitation is evident in scenarios where deconstructing an object leads to ambiguous typings.


In another attempt, I tried simplifying the structure by defining tuples for each state:

type StateA = [
    { error: "not logged in"; },
    never
]

type StateB = [
    { error: "not premium"; },
    never
]

type StateC = [
    { error: false; },
    { data: string }
]

Surprisingly, similar issues persisted regarding independent typing of the tuple elements. Although narrowing was effective for one item in the tuple, cohesion between both components remained elusive.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Learn how to automatically access keys in local storage using Angular

I need to implement a way to store data in local storage so that it persists even when the page is refreshed. In my HTML file, there is a button that triggers a function in my TypeScript file. This function takes a parameter 'game', which is an i ...

Struggling to track down the issue in my ts-node express project (Breakpoint being ignored due to generated code not being located)

For my current project, I decided to use the express-typescript-starter. However, when I attempted to debug using breakpoints in VS Code, I encountered an issue where it displayed a message saying "Breakpoint ignored because generated code not found (sourc ...

What could be causing the issue with the conditional validation in my zod and react-hook-form integration?

Hello there! I recently refactored my code and encountered a problem with a conditional field. When the user selects "Yes," an additional field should appear below for them to fill out. However, the issue I'm facing is that when "Yes" is selected, the ...

Disabling an anchor using the 'disabled' property is proving to be a challenge for me

I'm attempting to dynamically enable or disable an anchor element based on the user's role. So far, I've tried a few different methods: document.getElementById('myBtn').disabled = true; However, this returns an error: The propert ...

Encountering issues with MediaSession.setPositionState() and seekto functionalities not functioning properly

Having trouble with MediaSession.setPositionState() not displaying the audio time and seekbar not behaving as expected. const sound= document.querySelector('sound'); function updatePositionState() { if ('setPositionState' in navigato ...

How to integrate a chips feature in Angular 4 using Typescript

Struggling to incorporate a chips component into my Angular web application, which comprises Typescript, HTML, and CSS files. After grappling with this for weeks without success, I have yet to find the right solution. To review my current code, you can a ...

Using `useState` within a `while` loop can result in

I'm working on creating a Blackjack game using React. In the game, a bot starts with 2 cards. When the user stands and the bot's card value is less than 17, it should draw an additional card. However, this leads to an infinite loop in my code: ...

The Typescript counterpart to PropTypes.oneOf, utilizing a pre-existing variable

While I know this question has been addressed on Stack Overflow here, I still find myself struggling with a similar issue in my TypeScript project. Currently, I am in the process of converting a JavaScript project to Typescript. Within one of my React com ...

The function res.status is not defined

Currently, I am in the process of integrating my upcoming app with Google Sheets. I have relocated the function that manages the post request to "app/api/sheets" as per the recommended documentation. import type { NextApiRequest, NextApiResponse } from &ap ...

What is the best way to eliminate the content of an element using javascript/typescript?

The progress bar I'm working with looks like this: <progress class="progress is-small" value="20" max="100">20%</progress> My goal is to use javascript to remove value="20", resulting in: <progre ...

Sending data to a parent component from a popup window in Angular Material using a button click while the window is still open

How can I retrieve data from an Angular Material Dialog Box and send it to the Parent component? I am able to access data after the dialog box is closed. However, I am wondering if there is a way to retrieve data while the dialog box is still open, especi ...

What is the process for configuring PhpStorm to sync with TypeScript tsconfig.json in .vue files?

Vue.js (webpack + vueify) with TypeScript is my current setup. The ts configuration appears to be functioning, but only in .ts files. For instance, in tsconfig.json: "compilerOptions": { "strictNullChecks": false, So strictNullChecks works as expect ...

Steps for incorporating ProxyConfig in Angular7 Application1. First, create a new

Having trouble building the application with proxy configuration. It works fine with ng serve or npm run start, but I need it to work with npm run build or ng build. After that, I want to deploy the dist folder to Tomcat webapps and make everything functio ...

How can we ensure in ReactJS that one API call has been completed before making another one?

How can we ensure one API call is completed before making the next call in reactJS? In my componentDidMount function, I need to check the length of data. When the length is more than 4, I first want to save the data and then retrieve it. componentDidM ...

Incorporating Common Types for Multiple Uses

Is there a way to efficiently store and reuse typings for multiple React components that share the same props? Consider the following: before: import * as React from 'react'; interface AnotherButtonProps { disabled?: boolean; onClick: (ev ...

Implement a default dropdown menu that displays the current month using Angular and TypeScript

I am looking to implement a dropdown that initially displays the current month. Here is the code snippet I have used: <p-dropdown [options]="months" [filter]="false" filterBy="nombre" [showClear] ...

Setting up Webhook for Clerk in a Next.js and Prisma (T3 stack) environment

I am facing a challenge in my personal project using Next.js (T3 stack) where I need to synchronize Clerk users with a user table in my Supabase DB. My goal is to have a Users table, defined in my schema.prisma, that includes the user_id from Clerk along ...

Trouble encountered when attempting to call a function within another function in StencilJS

I am currently following a tutorial on building a drag and drop file uploader using StencilJS for some practice and fun. However, I have encountered an error in the code. Below is a snippet of the code, but I can provide more if necessary. @Component({ ...

What is the reason behind the lack of exported interfaces in the redux-form typings?

I've been exploring how to create custom input fields for redux-form. My journey began by examining the Material UI example found in the documentation here. const renderTextField = ({input, label, meta: { touched, error }, ...custom }) => < ...

What is the best way to separate a string using a comma as a delimiter and transform it into a string that resembles an array with individual string elements

I am in search of a way to transform a string, such as: "one, two, three, four" into a string like: "["one", "two", "three", "four"]" I have been attempting to devise a solution that addresses most scenarios, but so far, I have not been successful. The ap ...