Creating a custom string subtype in TypeScript

I am currently working on developing a game called Risk using TypeScript and React hooks. This game is played on a map, so my first step was to design a MapEditor. The state of the Map Editor is as follows:

export interface IMapEditorState {
   mousePos: IPoint;
   countries: {[k: string]: ICountry};
   continents: { [k: string]: IContinent };
}

The objects countries and continents are essential components. Here is the interface for the country object:

//The "name" property in each key will be the same in `{[k: string]: ICountry}`;
export interface ICountry {
   name: string;
   border: IDot[];
   neighbours: string[];
   completed: boolean;
}

Next, I created a reducer function. For all types of actions, I utilized two props: name and data. The name prop will always be a string, while the data type will vary based on the name:

type ActionTypes = {name: "removeCountry", data: string} | {name: "addCountry", data: ICountry};
const reducer = (state: IMapEditorState, action: ActionTypes) => {
   ...
}

In the ActionTypes, the first type is

{name: "removeCountry", data: string}
. When using {name: "removeCountry"} in the dispatch method, the compiler enforces that the data passed must be a string. However, this string should be specific to the keys in {[k: string]: ICountry} within the IMapEditorState or the name attribute in ICountry.

I explored creating a subtype of a string named CountryName, which could be used like this:

export interface IMapEditorState {
   mousePos: IPoint;
   countries: {[k: CountryName]: ICountry};
   continents: { [k: string]: IContinent };
}
export interface ICountry {
   name: CountryName;
   border: IDot[];
   neighbours: string[];
   completed: boolean;
}
type ActionTypes = {name: "removeCountry", data: CountryName} | {name: "addCountry", data: ICountry};

Any insights you have on my data structure for the game would be greatly appreciated!

Answer №1

In order to conduct these checks during compile time, it's necessary to create a comprehensive list of all potential country names:

type CountryName = 'cName1' | 'cName2' | 'cName3';

Alternatively, if an initial object containing all possible countries can be defined, it should be declared as const (to prevent TS from generalizing its strings), and then the keys can be extracted using keyof:

const initialCountries = {
    cName1: {
        name: 'cName1',
        completed: false
        // ...
    },
    cName2: {
        name: 'cName2',
        completed: false
    },
    cName3: {
        name: 'cName3',
        completed: false
    },
} as const;
type CountryName = keyof typeof initialCountries;

The resulting CountryName will be "cName1" | "cName2" | "cName3".

Subsequently, the IMapEditorState can be defined utilizing the aforementioned CountryName:

export interface ICountry {
    name: CountryName;
    border: IDot[];
    neighbours: string[];
    completed: boolean;
}
export interface IMapEditorState {
    mousePos: IPoint;
    countries: { [k: CountryName]: ICountry };
    continents: { [k: string]: IContinent };
}

This setup allows for successful compilation:

const initalIMapEditorState: IMapEditorState = {
    countries: initialCountries,
    // ...
};

With this framework in place, CountryName can be used across various contexts:

type ActionTypes = {name: "removeCountry", data: CountryName} | {name: "addCountry", data: ICountry};

Answer №2

Dealing with stringified dates in Typescript 4.1.5:

In my scenario, I encountered the need to distinguish a stringified date in the format "YYYY-MM-DD" as an ISODate rather than simply a string. Instead of opting for this particular solution, I decided on a different approach that I believe is more efficient, especially when dealing with a larger number of valid strings:

interface ISODateDifferentiator extends String {
  [key: string]: unknown;
}
export type ISODate = ISODateDifferentiator & string; 

export const ValidateIsoDate = (date: string): date is ISODate => {
  return date.match(/^\d{4}-\d{2}-\d{2}$/) !== null;
} 

You will encounter the expected error message

Type 'string' is not assignable to type 'ISODate'
during unsafe assignments, but you can easily convert using the typeguard function. Additionally, you will still benefit from string method auto-completion.

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

conceal the bootstrap navigation bar while the page is being

Struggling to toggle a Bootstrap navbar on scroll? Need some guidance from the pros out there. I admit, my Bootstrap and jQuery skills are pretty basic. The browser console doesn't throw any errors, and I've experimented with fadeIn, fadeOut, add ...

I am looking to transfer information to a different screen using FlatList in React Native

Hey everyone, I'm new to React Native and I'm trying to create a news app with posts. I'm having trouble figuring out how to pass data in a flatlist to another screen and open the screen with the post data. I'm using V6 navigation. Here ...

The reason behind the successful execution of the final .then in promise chaining

I am new to JavaScript and have encountered an interesting problem with the following code. Even though I haven't returned anything from the .then method, the last .then (blue color) works instead of the first one (red color). Can someone explain why ...

Using NextJS: Customizing page names for cleaner URLs

Currently working on a project in NextJS, I've come across an interesting observation. The file name within my pages folder seems to directly correspond to the path displayed in the URL bar. My question is, can I possibly create a structure like this ...

React: Why aren't class methods always running as expected?

I created a class component that retrieves a list of applications from an external API. It then sends a separate request for each application to check its status. The fetching of the applications works well, but there is an issue with the pinging process ...

Creating React elements dynamically with material-ui can be done by utilizing state expressions in properties. This allows for the generation

Within a functional component, I aim to dynamically generate material-ui context menus by utilizing a state object: let legendContextMenuStatesObject = {}; for (let key of keys) { legendContextMenuStatesObject[key] = initialState; } const [lege ...

Using Typescript and React to render `<span>Text</span>` will only display the text content and not the actual HTML element

My function is a simple one that splits a string and places it inside a styled span in the middle. Here's how it works: splitAndApplyStyledContent(content: string, textType: string, separator: string) { const splittedContent = content.split(separat ...

Is there a way to determine which radio button has been chosen using jQuery?

I'm trying to retrieve the value of the selected radio button using jQuery. Can anyone help with this? Currently, I am able to target all radio buttons like so: $("form :radio") But how can I determine which one is actually selected? ...

Transforming dates from JSON and showcasing them on a DataTable

I'm facing an issue with my local JSON file where the dates are being rendered in a DataTable as /Date(1350028800000)/ (10/12/2012). I came across this thread which talks about converting the date format, but I need help figuring out how to implement ...

A JavaScript plugin that offers functionality similar to the repeating options in Google Calendar for forms

Does anyone know of a JavaScript plugin that visually replicates the repeat options in Google Calendar? ...

Executing a <SCRIPT> within an Ajax-loaded webpage

Utilizing Framework7 for my web application has been great since it allows me to load pages using Ajax, giving it an app-like feel. However, I am facing a challenge with making the "ad" code display properly on Ajax-loaded pages. If you inspect the ad co ...

Error: Google Chrome encountered an unexpected token } that caused a syntax error

I encountered an error that reads: Uncaught SyntaxError: Unexpected token } This error only appears in Chrome, while other browsers like Mozilla and IE do not show it. Here is my script causing the issue: <script type="text/javascript" language="jav ...

Unable to set up npm for node-resemble on macOS

Issue Encountered: Error installing node-resemble package due to missing README data and configure errors. npm install failed with exit status 1. ...

What does the main attribute in npm stand for?

The command npm init generates a file called package.json. The contents typically look like this: { "name": "webpack-tut", "version": "1.0.0", "description": "", "main": "index.js", .... } I came across this information in the official documen ...

Learn how to easily copy all the text from a grid in React JS by utilizing the "Copy All" icon from MUI

Hey everyone, I've included "Copy All" material UI icons in my grid. The grid consists of 2 columns and 2 rows. I want to be able to copy all the values in the columns and rows when the copy all icon is clicked. Code: const items=[{ id:1, name:' ...

Rendering a Subarray using Map in ReactJS

I have an object containing data structured like this: [{"groupid":"15","items":[ {"id":"42","something":"blah blah blah"}, {"id":"38","something":"blah blah blah"}]}, {"groupid":"7","items": [{"id":"86","something":"blah blah blah"}, {"id":"49","somethin ...

What is the best way to store article IDs using Javascript or HTML?

On my web page, I have a collection of links that users can interact with by clicking and leaving comments. These links are loaded through JSON, each with its unique identifier. But here's my dilemma - how can I determine which link has been clicked? ...

I continue to encounter an error every time I attempt to place an HTML nested div on a separate line

When I structure the HTML like this, I don't encounter any errors: <div class="game-card"><div class="flipped"></div></div> However, if I format it differently, I receive an error message saying - Cannot set property 'vi ...

The submit button remains disabled even after verifying all the required fields

I have a registration form with basic customer details and a Submit button. I have successfully validated all the fields using Ajax, except for the Confirm password field which is being validated using JavaScript. The issue I am facing is that when I val ...

JavaScript - Error encountered when accessing information from elements in the DOM

Just started learning javascript and encountering a problem that I can't seem to solve. I'm attempting to assign the className of a <div> element to a variable, but I keep receiving this error message: scripts.js:30 Uncaught TypeError: Can ...