Enhancements in type narrowing using control-flow analysis for bracket element access have been introduced in TypeScript 4.7.
One thing I'd like to do is verify if the accessed field is an array or not. Currently, the type guard seems to be ineffective:
Error: Type 'never[]' is not compatible with type 'string'.(2322)
https://i.stack.imgur.com/vZxID.png
Despite utilizing isArray
, TypeScript still considers the accessed field as potentially a string
type instead of an array.
What could be missing here?
Below is a detailed example:
Let's say we define a type called Post
, where certain fields are arrays:
type Post = {
id: string;
title: string;
chapters: PostChapter[];
};
Now, suppose we have a list of strings (keys of a Post
) that we want to use to update the value in a Post
object dynamically:
const fieldNamesToReplace: (keyof Post)[] = ["chapters"];
When attempting to access these keys using brackets, TypeScript fails to recognize them as arrays, even after validation through Array.isArray
.
By the way, one workaround that functions is creating a new object and replacing the field, avoiding reliance on control-analysis for bracketed access.
Here is a link to the playground along with the complete example:
type PostChapter = {
id: string;
chapterTitle: string;
};
type Post = {
id: string;
title: string;
chapters: PostChapter[];
};
const fieldNamesToReplace: (keyof Post)[] = ["chapters"];
const posts: Post[] = [
{
id: "1",
title: "abc",
chapters: [{ id: "1.1", chapterTitle: "def" }],
},
];
const postsTransformed = posts.map((post) => {
let postNew = { ...post };
// works, because we don't rely on the type-narrowing for setting the value
fieldNamesToReplace.forEach((fieldName) => {
if (Array.isArray(postNew[fieldName])) {
postNew = { ...postNew, [fieldName]: [] };
}
});
// doesn't work
fieldNamesToReplace.forEach((fieldName) => {
if (Array.isArray(postNew[fieldName])) {
postNew[fieldName] = [];
// Error: Type 'never[]' is not assignable to type 'string'.(2322)
const placeholder = postNew[fieldName];
// ^?
}
});
return postNew;
});