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_1train_2train_3train_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.
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.
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
| Approach | Example | Best for | Pros | Cons |
|---|---|---|---|---|
| Fixed object type | { train_1: boolean; train_2: boolean } | Known keys | Strong safety, catches typos | Less flexible |
| Index signature | { [key: string]: boolean } | Dynamic keys | Flexible | Allows too many keys |
Record<string, boolean> | Record<string, boolean> | Dynamic dictionaries | Short syntax | Same weakness as index signature |
keyof derived union | keyof PlotOptions | Safe dynamic access | Precise and reusable | Requires proper typing upstream |
string vs keyof
| Type | Meaning | Safe for plotOptions[name]? |
|---|---|---|
string | Any possible string value | No |
keyof PlotOptions | Only valid keys of PlotOptions | Yes |
Type assertion vs proper typing
| Approach | Example | When to use |
|---|---|---|
| Proper typing | name: keyof PlotOptions | Preferred in most cases |
| Type assertion | plotOptions[name as keyof PlotOptions] | Only when you cannot type it earlier |
In your case
The best fit is:
- keep
plotOptionsas a fixed object type - derive
TrainNamewithkeyof PlotOptions - type
TrainInfo.nameasTrainName
That preserves type safety and removes the error cleanly.
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.
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
PlotOptionstype with at least three fixed series keys. - Derive a key type from
PlotOptionsusingkeyof. - Create a
TrainInfoorSeriesInfotype whosenameproperty 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.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.