2

How do you define a TypeScript interface with a Key String index but known keys with specific types, so that you can reference the key in a map?

Example Interface (This doesn't work)

interface column {
  label?: string;
  width: string;
}

export interface IColumns {
  [key: string]: {
    title: column;
    objectID?: number;
    url?: column;
    author: column;
    comments: column;
    points: column;
    archive: column;
  };
}

This is being used like so,

const COLUMNS : IColumns = {
  title: {
    label: "Title",
    width: "40%",
  },
  author: {
    label: "Author",
    width: "30%",
  },
  comments: {
    label: "Comments",
    width: "10%",
  },
  points: {
    label: "Points",
    width: "10%",
  },
  archive: {
    width: "10%",
  },
};

This is where I map over the converted object and what to reference the key.

const Stories: React.FC<{ stories: IStory[] }> = ({ stories }) => (
  <div className="stories">
    <div className="stories-header">
      {Object.keys(COLUMNS).map((key) => (
        <span key={key} style={{ width: COLUMNS[key].width }}>
          {COLUMNS[key].label}
        </span>
      ))}
    </div>
6
  • Do you mean something like that stackoverflow.com/a/39281228/863110? Commented Jun 30, 2020 at 14:38
  • I saw that before posting my question, It addresses unknown keys with known key child properties but I have known keys and known child properties that I need to reference with a string key. Perhaps my issue is not in the defining of the type but in the mapping of the array. Commented Jun 30, 2020 at 17:35
  • I'm not sure I understand. What is the issue in your code? Bad autocompletion? A transpilation error? Commented Jun 30, 2020 at 18:23
  • No auto completion if I use { [key: string]: column } or Record<String, column>. The key is a generic string so .map works but I have no auto completion of that key only its properties. If I define it without indicating the key is a string I get typescript errors on the .map as i'm using columns[key] to create the column headers. Commented Jun 30, 2020 at 18:28
  • You can do something like this. Or you can use Object.entries instead of Object.keys so you wouldn't have to cast the value. Just like this: {Object.entries(COLUMNS).map(([key, value]) => ( <span key={key} style={{ width: value.width }}> {value.label} </span> ))} Commented Jun 30, 2020 at 20:38

1 Answer 1

5

You can declare a type for the possible keys

type Prop = 'title' | 'author' | 'comments' | 'points' | 'archive';

Then the interface will use that type as computed key using the in keyword

type IColumns = {[key in Prop]: column}

Now you can use that interface

const COLUMNS : IColumns = {
  title: {
    label: "Title",
    width: "40%",
  },
  author: {
    label: "Author",
    width: "30%",
  },
  comments: {
    label: "Comments",
    width: "10%",
  },
  points: {
    label: "Points",
    width: "10%",
  },
  archive: {
    width: "10%",
  },
};

About the map, you have 2 options:

  1. Use Object.keys but case the key into Prop
(Object.keys(COLUMNS) as Array<Prop>).forEach(key => ...)
  1. Use Object.entries so the value will be already Prop
Object.entries(COLUMNS).map(([key, value /* :Prop */])

playground

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.