Question

Fix "Element implicitly has an 'any' type" in TypeScript Object Indexing

Question

I'm using TypeScript in a React project and getting this error when filtering an array:

.filter(({ name }) => plotOptions[name]);

TypeScript reports:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ train_1: boolean; train_2: boolean; train_3: boolean; train_4: boolean; }'.
No index signature with a parameter of type 'string' was found on type '{ train_1: boolean; train_2: boolean; train_3: boolean; train_4: boolean; }'.

I tried adding an index signature based on an article about indexing objects in TypeScript, but the error still remains.

Here is the component:

import React, { Component } from "react";
import createPlotlyComponent from "react-plotly.js/factory";
import Plotly from "plotly.js-basic-dist";
const Plot = createPlotlyComponent(Plotly);

interface IProps {
  data: any;
}

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

interface IState {
  plotOptions: PlotOptions;
}

type TrainName = keyof PlotOptions;

interface TrainInfo {
  name: TrainName;
  x: number[];
  y: number[];
  type: string;
  mode: string;
}

class FiltrationPlots extends Component<IProps, IState> {
  readonly state: IState = {
    plotOptions: {
      train_1: true,
      train_2: true,
      train_3: true,
      train_4: true
    }
  };

  render() {
    const { data } = this.props;
    const { plotOptions } = this.state;

    if (data.filtrationData) {
      const plotData: TrainInfo[] = [
        {
          name: "train_1",
          x: data.filtrationData.map((i: any) => i["1-CumVol"]),
          y: data.filtrationData.map((i: any) => i["1-PressureA"]),
          type: "scatter",
          mode: "lines"
        },
        {
          name: "train_2",
          x: data.filtrationData.map((i: any) => i["2-CumVol"]),
          y: data.filtrationData.map((i: any) => i["2-PressureA"]),
          type: "scatter",
          mode: "lines"
        },
        {
          name: "train_3",
          x: data.filtrationData.map((i: any) => i["3-CumVol"]),
          y: data.filtrationData.map((i: any) => i["3-PressureA"]),
          type: "scatter",
          mode: "lines"
        },
        {
          name: "train_4",
          x: data.filtrationData.map((i: any) => i["4-CumVol"]),
          y: data.filtrationData.map((i: any) => i["4-PressureA"]),
          type: "scatter",
          mode: "lines"
        }
      ].filter(({ name }) => plotOptions[name]);

      return (
        <Plot
          data={plotData}
          layout={{ width: 1000, height: 1000, title: "A Fancy Plot" }}
        />
      );
    }

    return <h1>No Data Loaded</h1>;
  }
}

export default FiltrationPlots;

Why does TypeScript reject plotOptions[name], and how should this be typed correctly?

Short Answer

By the end of this page, you will understand why TypeScript does not allow indexing a specifically typed object with a plain string, how keyof solves this, and how to correctly type related React data structures so object access like plotOptions[name] is safe and accepted by the compiler.

Concept

TypeScript distinguishes between:

  • any string: could be "train_1", "hello", or "does_not_exist"
  • specific known keys: such as "train_1" | "train_2" | "train_3" | "train_4"

Your plotOptions object has a fixed shape:

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

That means TypeScript knows exactly which property names are valid.

The problem is that in your original code, name was typed as string:

interface trainInfo {
  name: string;
}

When TypeScript sees this:

plotOptions[name]

it asks: "If name can be any string, how can I guarantee that property exists on plotOptions?"

It cannot guarantee that, so it raises an error.

Why this matters

This rule prevents bugs like:

const plotOptions = { train_1: true };
const name = "train_999";
console.log(plotOptions[name]);

In plain JavaScript, this returns undefined. In TypeScript, the compiler tries to stop mistakes earlier by forcing you to prove that the key is valid.

The correct fix

Instead of typing name as string, type it as one of the valid keys of plotOptions:

type TrainName = keyof PlotOptions;

Now TrainName becomes:

"train_1" | "train_2" | "train_3" | "train_4"

Then use it in your data type:

interface TrainInfo {
  name: TrainName;
  x: number[];
  y: number[];
  type: string;
  mode: string;
}

Now plotOptions[name] is safe, because name is guaranteed to be one of the allowed keys.

About index signatures

You tried adding an index signature like:

[key: string]: boolean;

That changes the meaning of the type. It tells TypeScript:

  • any string is allowed as a key
  • every such key returns a boolean

That is broader than your actual object shape. It may silence some errors, but it also weakens type safety. If you know the valid keys in advance, keyof and literal unions are usually the better choice.

Mental Model

Think of plotOptions like a locker cabinet with exactly four labeled doors:

  • train_1
  • train_2
  • train_3
  • train_4

If someone gives you a key labeled just string, that is like saying:

"Here is a key for some door name."

TypeScript responds:

"I only know these four doors. If you cannot guarantee the label matches one of them, I will not let you open it."

But if the key is typed as:

"train_1" | "train_2" | "train_3" | "train_4"

then TypeScript knows the key fits one of the actual doors, so access is allowed.

A plain string is too vague. A keyof type is a verified label.

Take Quiz

Syntax and Examples

Core syntax

Use keyof when a variable must match one of an object's keys.

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

type TrainName = keyof PlotOptions;

Now use that key type where needed:

interface TrainInfo {
  name: TrainName;
}

const plotOptions: PlotOptions = {
  train_1: true,
  train_2: false,
  train_3: true,
  train_4: true
};

const item: TrainInfo = { name: "train_1" };

console.log(plotOptions[item.name]); // boolean

Beginner-friendly example

Incorrect

type Settings = {
  darkMode: boolean;
  compact: boolean;
};

const settings: Settings = {
  darkMode: true,
  compact: false
};

function readSetting(key: string) {
  return settings[key]; // Error
}

key could be any string, not just darkMode or compact.

Correct

type Settings = {
  darkMode: boolean;
  compact: boolean;
};

type SettingKey = keyof Settings;

const settings: Settings = {
  darkMode: true,
  compact: false
};

function readSetting(key: SettingKey) {
  return settings[key];
}

Now the function only accepts valid keys.

Applied to your React example

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

type TrainName = keyof PlotOptions;

interface TrainInfo {
  name: TrainName;
  x: number[];
  y: number[];
  type: string;
  mode: string;
}

const plotData: TrainInfo[] = [
  { name: "train_1", x: [1], y: [10], type: "scatter", mode: "lines" },
  { name: "train_2", x: [2], y: [20], type: "scatter", mode: "lines" }
].filter(({ name }) => plotOptions[name]);

Because name is no longer just string, indexing works.

Step by Step Execution

Example to trace

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

type TrainName = keyof PlotOptions;

interface TrainInfo {
  name: TrainName;
}

const plotOptions: PlotOptions = {
  train_1: true,
  train_2: false,
  train_3: true,
  train_4: true
};

const trains: TrainInfo[] = [
  { name: "train_1" },
  { name: "train_2" },
  { name: "train_3" }
];

const visible = trains.filter(({ name }) => plotOptions[name]);

What happens step by step

1. PlotOptions defines allowed keys

TypeScript reads:

train_1 | train_2 | train_3 | train_4

as the only valid property names.

2. TrainName = keyof PlotOptions

This creates a union of the exact keys:

type TrainName = "train_1" | "train_2" | "train_3" | "train_4";

3. TrainInfo.name is restricted

interface TrainInfo {
  name: TrainName;
}

So this is valid:

{ name: "train_1" }

But this would fail:

{ name: "train_999" }

4. Filter callback runs

For each item in trains, .filter() extracts name.

First item:

name = "train_1"
plotOptions[name] // plotOptions["train_1"] => true

Item stays in the array.

Second item:

name = "train_2"
plotOptions[name] // false

Item is removed.

Third item:

name = "train_3"
plotOptions[name] // true

Item stays.

5. Final result

[
  { name: "train_1" },
  { name: "train_3" }
]

The important part is not the filtering itself. The important part is that TypeScript could safely allow plotOptions[name] because name is guaranteed to be a valid key.

Real World Use Cases

This pattern appears often in TypeScript projects.

Feature flags

type FeatureFlags = {
  search: boolean;
  billing: boolean;
  analytics: boolean;
};

A component may render sections based on keys like search or billing.

Form field visibility

type VisibleFields = {
  email: boolean;
  phone: boolean;
  address: boolean;
};

A UI can show or hide fields by checking visible[fieldName].

API response mapping

You may group data under known keys and dynamically access a section only if the key is valid.

Chart configuration

Your example is a very common case: a config object stores whether each series should be shown, and the chart data array is filtered using the series name.

Permissions

type Permissions = {
  canEdit: boolean;
  canDelete: boolean;
  canInvite: boolean;
};

A page may check permissions[action], but only if action is typed as a valid permission key.

Take Quiz

Real Codebase Usage

In real projects, developers usually avoid broad string types when the valid values are known.

Common pattern: derive keys from a config type

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

type TrainName = keyof PlotOptions;

This keeps key names in one place.

Pattern: reuse literal unions across state and data

interface TrainInfo {
  name: TrainName;
}

This links your data model to your state model, reducing mismatches.

Pattern: guard clauses for unknown external values

If a key comes from user input or an API, it may truly be an arbitrary string. In that case, narrow it before indexing:

function isTrainName(value: string): value is TrainName {
  return value in plotOptions;
}

const keyFromApi = "train_1";
if (isTrainName(keyFromApi)) {
  console.log(plotOptions[keyFromApi]);
}

Pattern: Record for dictionary-like objects

If your object really should allow many string keys, use Record<string, boolean> or an index signature.

const flags: Record<string, boolean> = {
  anything: true,
  somethingElse: false
};

This is useful for dynamic dictionaries, but not as precise as fixed-key objects.

Pattern: avoid unnecessary index signatures

When developers know the exact keys ahead of time, they prefer literal unions and keyof because these catch typos and invalid accesses earlier.

Common Mistakes

1. Typing a known key as plain string

Broken

interface TrainInfo {
  name: string;
}

Why it fails

string is too broad. TypeScript cannot guarantee that plotOptions[name] exists.

Fix

type TrainName = keyof PlotOptions;

interface TrainInfo {
  name: TrainName;
}

2. Adding an index signature when keys are fixed

Overly broad

type PlotOptions = {
  [key: string]: boolean;
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

Why this is risky

It tells TypeScript that any string key is valid, which weakens safety.

If you know the exact keys, prefer:

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

3. Using type assertions to silence the error

Works, but can hide bugs

plotOptions[name as keyof PlotOptions]

This is acceptable only when you truly know the value is valid. If name really can be wrong, this can hide a problem.

A better fix is usually to type the data correctly at the source.

4. Typing state too loosely

Less clear

interface IState {
  [key: string]: PlotOptions;
  plotOptions: PlotOptions;
}

This suggests any string property on state maps to PlotOptions, which is probably not true.

Better

interface IState {
  plotOptions: PlotOptions;
}

5. Confusing dynamic objects with fixed objects

Use a fixed object type for known keys:

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
};

Use Record<string, boolean> only when keys are truly unknown until runtime:

type PlotOptions = Record<string, boolean>;

Comparisons

Fixed keys vs index signature

ApproachExampleBest forProsCons
Fixed object type{ train_1: boolean; train_2: boolean }Known keysStrong safety, catches typosLess flexible
Index signature{ [key: string]: boolean }Dynamic keysFlexibleAllows too many keys
Record<string, boolean>Record<string, boolean>Dynamic dictionariesShort syntaxSame weakness as index signature
keyof derived unionkeyof PlotOptionsSafe dynamic accessPrecise and reusableRequires proper typing upstream

string vs keyof

TypeMeaningSafe for plotOptions[name]?
stringAny possible string valueNo
keyof PlotOptionsOnly valid keys of PlotOptionsYes

Type assertion vs proper typing

ApproachExampleWhen to use
Proper typingname: keyof PlotOptionsPreferred in most cases
Type assertionplotOptions[name as keyof PlotOptions]Only when you cannot type it earlier

In your case

The best fit is:

  • keep plotOptions as a fixed object type
  • derive TrainName with keyof PlotOptions
  • type TrainInfo.name as TrainName

That preserves type safety and removes the error cleanly.

Take Quiz

Cheat Sheet

Quick fix

type PlotOptions = {
  train_1: boolean;
  train_2: boolean;
  train_3: boolean;
  train_4: boolean;
};

type TrainName = keyof PlotOptions;

interface TrainInfo {
  name: TrainName;
}

Then this works:

plotOptions[name]

Rule to remember

If an object has specific keys, you cannot safely index it with a plain string.

Use:

keyof SomeType

when the variable should be one of that object's keys.

Common patterns

Safe object indexing

function getValue(obj: PlotOptions, key: keyof PlotOptions) {
  return obj[key];
}

Dynamic dictionary

const values: Record<string, boolean> = {};

Use this only if keys are truly unknown.

Avoid

interface TrainInfo {
  name: string;
}

if name should only be one of a fixed set of known values.

Useful fallback

If you cannot change the source type yet:

plotOptions[name as keyof PlotOptions]

But prefer fixing the type definition instead.

React-specific tip

When state keys and data names are related, define one shared key type and reuse it across:

  • state
  • props
  • data objects
  • filter/map logic

FAQ

Why does TypeScript say a string cannot be used to index an object?

Because a plain string could be any value, but your object only supports specific keys. TypeScript requires proof that the key is valid.

What does keyof do in TypeScript?

keyof creates a union of an object's property names. For example, keyof PlotOptions becomes "train_1" | "train_2" | "train_3" | "train_4".

Should I use an index signature to fix this error?

Only if your object truly supports arbitrary string keys. If the keys are known ahead of time, keyof is a better and safer solution.

Is plotOptions[name as keyof PlotOptions] a good fix?

It can work, but it is a fallback. It is better to type name correctly so the compiler can verify it naturally.

Why didn't my index signature solve the problem cleanly?

Because the real issue was that name was still typed too broadly. Also, an index signature may make the type less precise than your actual data.

How do I type React state for this case?

Keep the state shape simple and exact:

interface IState {
  plotOptions: PlotOptions;
}

Then use keyof PlotOptions anywhere a key must match plotOptions.

Can this happen outside React?

Yes. This is a general TypeScript object-indexing rule and appears in functions, API handling, form code, and configuration objects.

Take Quiz

Mini Project

Description

Build a small TypeScript module for toggling which chart series are visible. This demonstrates safe object indexing with keyof, fixed object shapes, and filtering an array based on typed keys.

Goal

Create a typed chart-filtering utility where each series name must match a valid key in the visibility settings object.

Requirements

  • Define a PlotOptions type with at least three fixed series keys.
  • Derive a key type from PlotOptions using keyof.
  • Create a TrainInfo or SeriesInfo type whose name property uses that key type.
  • Build an array of series objects and filter it based on the visibility settings.
  • Print or return only the visible series.
Take Quiz

Keep learning

Related questions

View more