Encountering a Typescript issue while trying to access two distinct values dynamically from within a single object

Currently, I'm developing a component library and facing an issue with a TypeScript error:

An Element implicitly has an 'any' type due to the expression of type 'levelTypes | semanticTypes' being unable to index type '{ level1: { highEmphasis: string; mediumEmphasis: string; outlineHighEmphasis: string; outlineMediumEmphasis: string; semantic: { info: string; onInfo: string; negative: string; onNegative: string; ... 12 more ...; AIGradientEndpoint: string; }; interactive: { ...; }; }; ... 15 more ...; aiIntense: { ...; }; } | { ....'.

Property 'level1' is not found in type '{ level1: { highEmphasis: string; mediumEmphasis: string; outlineHighEmphasis: string; outlineMediumEmphasis: string;'ts(7053)

The part that's causing trouble is when trying to dynamically access two different keys in my color palette object:

theme.fdsTheme.palette[getForeground(level)][level].interactive.highEmphasisSurface

Including my component code for reference:

import styled from "@emotion/styled";

// Rest of the code...
:) 

I have been searching for solutions related to accessing two keys dynamically but have only come across examples dealing with single key accesses. Here is what I attempted:

// Define my theme as a separate type
import "@emotion/react";
import { Theme } from "../theme";

type DefaultTheme = typeof Theme;

declare module "@emotion/react" {
  export interface Theme extends DefaultTheme {}
}

import { Theme } from "@emotion/react";

// Previous Code...

const StyledButton = styled(BaseButton)<ButtonStyleProps>(({ theme, level }) => {

  return {
    [`&.${buttonClasses.root}`]: {
      // various styles...

      "&.fds-Button-primary": {
        backgroundColor:
          theme.fdsTheme.palette[getForeground(level) as keyof Theme][level as keyof Theme].interactive.highEmphasisSurface,
      },
    },
  };
});

UPDATE:

To simplify things, here's some dummy code:

interface TestProps {
  levelOne: "color" | "typo";
  levelTwo: "level1" | "level2" | "level3";
}

function TestComponent({ levelOne, levelTwo }: TestProps) {
  const theme = {
    color: {
      level1: {
        color: "",
      },
      level2: {
        color: "",
      },
    },
    typo: {
      level1: {
        color: "",
      },
      level2: {
        color: "",
      },
      level3: {
        color: "",
      },
    },
  };

  const checkLevel = (
    level: "level1" | "level2" | "level3"
  ): "color" | "typo" => {
    return level === "level1" || level === "level2" ? "color" : "typo";
  };

  const c = theme[checkLevel(levelTwo)][levelTwo].color;

  console.log(c);
}

The problematic line would be:

theme[checkLevel(levelTwo)][levelTwo].color;

Answer №1

I shifted this code block outside of the main component:

type Color = "color" | "typo";
type Level = "level1" | "level2" | "level3";

const theme = {
  color: {
    level1: {
      color: "",
    },
    level2: {
      color: "",
    },
  },
  typo: {
    level1: {
      color: "",
    },
    level2: {
      color: "",
    },
    level3: {
      color: "",
    },
  },
};

Next, I wrote the following piece of code:

type Theme = typeof theme;
type Themes = keyof Theme;

type KeyMap<T> = {
  [P in keyof T]: keyof T[P];
} 

type InvertResult<T extends Record<PropertyKey, PropertyKey>> = {
  [P in keyof T as T[P]]: P
}

type LevelToTheme = InvertResult<KeyMap<Theme>>;
type GetThemes<L extends Level> = LevelToTheme[L];

type SelectOne<
  Options extends Themes,
  PriorityTheme extends Themes
> = PriorityTheme extends Extract<Options, PriorityTheme> ?
  PriorityTheme : Exclude<Options, PriorityTheme>;

type PriorityTheme = "color";

const checkLevel = <L extends Level, TReturn = SelectOne<GetThemes<L>, PriorityTheme>>(
  level: L,
): TReturn => {
  const theme: any = level === "level1" || level === "level2" ? "color" : "typo";
  return theme;
};

The above logic can deduce most of the information, but there might be an issue that remains:

interface TestProps {
  currentLevelName: Level;
}

function TestComponent({ currentLevelName }: TestProps) {
  // myTheme is Theme['color'] because PriorityTheme = "color"
  const myTheme = theme[checkLevel(currentLevelName)]; 
  // ERROR Property 'level3' does not exist on type Theme['color']
  const c = myTheme[currentLevelName].color; 
  console.log(c);
}

This problem arises due to currentLevelName: Level; in the component props not being specific enough. If possible, you could handle it at compile-time by knowing the level passed to each component in advance:

interface Test2Props {
  currentLevelName: "level2";
}

function Test2Component({ currentLevelName }: Test2Props) {
  // myTheme is Theme['color'] because it is inferred
  const myTheme = theme[checkLevel(currentLevelName)]; 
  // OK Everything is inferred correctly
  const c = myTheme[currentLevelName].color; 
  console.log(c);
}

interface Test3Props {
  currentLevelName: "level3";
}

function Test3Component({ currentLevelName }: Test3Props) {
  // myTheme is Theme['typo'] because it is inferred
  const myTheme = theme[checkLevel(currentLevelName)];
  // OK Everything is inferred as expected
  const c = myTheme[currentLevelName].color; 
  console.log(c);
}

For a different approach, if predefining levels is not feasible, you may consider having a runtime check and manual casting like this:

theme[checkLevel(levelTwo)][levelTwo].color;

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

What is the best way to efficiently filter this list of Outcome data generated by neverthrow?

I am working with an array of Results coming from the neverthrow library. My goal is to check if there are any errors in the array and if so, terminate my function. However, the challenge arises when there are no errors present, as I then want to destructu ...

In Angular 8, a communication service facilitates interaction between parents and children

Using a Sharing service, I am able to pass data from my app component to the router-outlet render component. While I have been successful in passing strings and other data, I am now looking for a way to retrieve data from an API and share it with a compone ...

Step-by-step guide on how to stop CDK Drop depending on a certain condition

I'm trying to figure out how to disable dropping using CDK based on certain conditions. Specifically, I want the drop functionality to be disabled if the list I'm attempting to drop into is empty. I haven't been able to find a solution withi ...

What is the best way to troubleshoot substrings for accurately reading URLs from an object?

While a user inputs a URL, I am attempting to iterate through an object to avoid throwing an error message until a substring does not match the beginning of any of the URLs in my defined object. Object: export const urlStrings: { [key: string]: string } = ...

Whenever signing in with Next Auth, the response consistently exhibits the values of "ok" being false and "status" being 302, even

I am currently using Next Auth with credentials to handle sign-ins. Below is the React sign-in function, which can be found at this link. signIn('credentials', { redirect: false, email: email, password: password, ...

Error message displayed in console due to AJV JSON schema validation when the maximum character limit is exceeded

In this provided example: { "default": "adsds", "max": 1 } I attempted to reference the dynamically provided 'max' value and validate the number of characters entered in the 'default' field. To achieve ...

Struggling to make Cypress react unit testing run smoothly in a ReactBoilerplate repository

I have been struggling for the past 5 hours, trying to figure out how to make everything work. I even recreated a project's structure and dependencies and turned it into a public repository in hopes of receiving some assistance. It seems like there mi ...

What is the best way to display HTML in this particular situation?

This is the function I am working on: public static monthDay(month: number): string { let day = new Date().getDay(); let year = new Date().getFullYear(); return day + ", " + year; } I am trying to add <span></span> tags around ...

Modify the name of the document

I have a piece of code that retrieves a file from the clipboard and I need to change its name if it is named image.png. Below is the code snippet where I attempt to achieve this: @HostListener('paste', ['$event']) onPaste(e: ClipboardE ...

The most efficient and hygienic method for retrieving a value based on an observable

Looking at the structure of my code, I see that there are numerous Observables and ReplaySubjects. When trying to extract a value from one of these observables in the HTML template, what would be the most effective approach? In certain situations, parame ...

Instead of displaying the name, HTML reveals the ID

I have defined a status enum with different values such as Draft, Publish, OnHold, and Completed. export enum status { Draft = 1, Publish = 2, OnHold = 3, Completed = 4 } In my TypeScript file, I set the courseStatus variable to have a de ...

Acquire Superheroes in Journey of Champions from a REST endpoint using Angular 2

Upon completing the Angular 2 Tour of heroes tutorial, I found myself pondering how to "retrieve the heroes" using a REST API. If my API is hosted at http://localhost:7000/heroes and returns a JSON list of "mock-heroes", what steps must I take to ensure a ...

Switching the Require statement to an Import statement led to an error popping up

Currently, I am exploring the use of Ajv with typescript. import { Ajv } from "ajv"; let ajv = new Ajv({allErrors: true}); I have encountered an error and I'm unsure why it is occurring: [ts] 'Ajv' only refers to a type, but is being u ...

What is the correct way to express an object in an array?

I've encountered an issue: I'm working with an array called _daysConfig<DayConfig> When I manually populate it like this, everything functions correctly: _daysConfig: DayConfig[] = [ { date: new Date('Wed Jul 22 2020 21:06:00 GMT+02 ...

Issues with functionality of React/NextJS audio player buttons arise following implementation of a state

I am currently customizing an Audio Player component in a NextJs application using the ReactAudioPlayer package. However, the standard Import Next/Audio and using just <Audio> without props did not yield the expected results. The player functions as ...

Potential issue detected in TypeScript relating to the JQuery.html() method

For example: let $div = $("div"); let $p = $("p"); $div.html($p); This is producing the following error message: Supplied parameters do not match any signature of call target. UPDATE: In plain JavaScript/jQuery, the code is working. An equivalent (in f ...

Angular Error: Unable to access property 'users' on a null value

I am working on a component that takes in data through the @Input() decorator regarding a group. My objective is to generate a new array of objects during the initialization of the component based on the data from the group array. Below is the TypeScript c ...

Showcase pictures within an angular smart table

Is it possible to display images in a column within an ng smart table? We have several columns consisting mostly of data, with one column dedicated to displaying images. Following the ng smart table concept, I attempted to implement the code below which cu ...

What is the process of converting TypeScript to JavaScript in Angular 2?

Currently diving into the world of Angular 2 with TypeScript, finding it incredibly intriguing yet also a bit perplexing. The challenge lies in grasping how the code we write in TypeScript translates to ECMAScript when executed. I've come across ment ...

What is the best method for eliminating the .vue extension in TypeScript imports within Vue.JS?

I recently created a project using vue-cli3 and decided to incorporate TypeScript for added type safety. Here is a snippet from my src/app.vue file: <template> <div id="app"> <hello-world msg="test"/> </div> </template& ...