Foreword
First and foremost, I want to mention that I have already referenced seemingly related questions here on Stack Overflow, such as this one Stack Overflow #52204566, but none of them appear to actually resolve the issue I am currently experiencing, so I am opening this new question.
My Code
Here is my TSX source on the TypeScriptLang.org Playground, where you can see the error on the Errors tab on the right side of the page.
I am also attaching my TSX code here (below) for reference, but you will not be able to see the TS errors here on Stack Overflow.
import React from 'react'
// Types
interface PersonType { name: string, nameLower: string, species: string[], films: string[], homeworld: string, starships: string[], previous: string | null, next: string | null };
interface SpeciesType { url: string, name: string };
interface FilmType { url: string, director: string, release_date: string, title: string };
interface StarshipType { url: string, name: string, model: string, cost_in_credits: string };
interface PlanetType { url: string, name: string };
interface PersonProps {
person: PersonType,
allSpecies: SpeciesType[],
allFilms: FilmType[],
allStarships: StarshipType[],
allPlanets: PlanetType[]
}
function filterByList(select: string | string[], all: {url: string, [key: string]: any}[], matchKey: string, desiredKey: string): string[];
function filterByList(select: string | string[], all: {url: string, [key: string]: any}[], matchKey: string, desiredKey: string[]): { [desiredKey: string]: string }[];
function filterByList(select: string | string[], all: {url: string, [key: string]: any}[], matchKey: string, desiredKey: string | string[]) {
return (
[select].flat().map((mK: string) => {
const matched = all.find(item => item[matchKey] === mK);
if (matched) {
if (Array.isArray(desiredKey)) {
return desiredKey.reduce((obj: Record<string, any>, key: string) => {
obj[key] = matched[key] ?? null;
return obj;
}, {});
}
return matched[desiredKey] ?? null;
}
}) ?? []
)
};
const generateLabeledList = (label: string, className: string, list: string[]): false | JSX.Element => (
list.length > 0 &&
<div className={`person--${className}`}>
<strong>{label}: </strong>
{
list.length > 1
? <ul className={`person--${className}--items person--${className}--items__list`}>
{list.map((item: string) => <li key={item}>{item}</li>)}
</ul>
: <span className={`person--${className}--items person--${className}--items__single`}>{list[0]}</span>
}
</div>
);
export default function Person({ person, allSpecies, allFilms, allStarships, allPlanets }: PersonProps) {
const searchTerms = ['Luke', 'Skywalker'],
{ name, species: speciesURLs, films: filmURLs, starships: starshipURLs, homeworld: homeworldURL } :
{ name: string, species: string[], films: string[], starships: string[], homeworld: string } = person,
films: string[] = filterByList(filmURLs, allFilms, 'url', 'title'),
species: string[] = filterByList(speciesURLs, allSpecies, 'url', 'name'),
starships: { name: string, model: string }[] = filterByList(starshipURLs, allStarships, 'url', ['name', 'model']),
homeworld: string[] = filterByList(homeworldURL, allPlanets, 'url', 'model');
let highlightedName = name;
if (searchTerms.length) {
const regexSearch = new RegExp(searchTerms.map(term => `(${term})`).join('|'), 'gi');
highlightedName = highlightedName
.replaceAll(regexSearch, '<mark>$&</mark>')
.replaceAll(/(<\/mark>)<mark>/g, '$1<mark class="subsequent">')
.replaceAll(/(<\/mark>\s)<mark>/g, '$1<mark class="subsequent space">');
}
console.log({starships});
return (
<div className="person">
<div className="person--name" dangerouslySetInnerHTML= />
{generateLabeledList('Species', 'species', species)}
{generateLabeledList('Homeworld', 'homeworld', homeworld)}
{generateLabeledList('Appears in', 'films', films)}
{generateLabeledList('Piloted', 'starships', starships
.map(({ name, model } : { name: string, model: string }) => name === model ? name : `"${name}" ${model}`))
}
</div>
)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>The Issue & My Approach
I am building a function in TypeScript which—for parameter—accepts any arbitrary property name, or array of property names. If a single property name (string) is provided, the function returns an array of the values of that property for the objects being evaluated.
If an array of property names is provided, the function returns an array of keyed objects, where each object in the array contains the strings passed to the function as an array as its keys and the values pertaining to each for the objects being evaluated.
I am using function overloading to handle my different return types which resolved many of the errors I was experiencing initially. This final error appears to relate to the return type when I pass an array of property names (which will be used as the keys in the objects returned as an array from the function).
The Error
The error I'm getting is this:
Type '{ [desiredKey: string]: string; }[]' is not assignable to type '{ name: string; model: string; }[]'.
Type '{ [desiredKey: string]: string; }' is missing the following properties from type '{ name: string; model: string; }': name, model
The main lines to pay mind to, regarding the error, are:
- Line 17: Creating the function overload for this return type, where
desiredKeyis an array of stringsstring[]and the return type is currently set to{ [desiredKey: string]: string }[](tried making use of computed property names)
function filterByList(select: string | string[], all: {url: string, [key: string]: any}[], matchKey: string, desiredKey: string[]): { [desiredKey: string]: string }[];
- Line 55: Invoking that function with the argument for parameter
desiredKeyas an array of stringsstring[], per the overload format I created for this case
starships: { name: string, model: string }[] = filterByList(starshipURLs, allStarships, 'url', ['name', 'model']),
- Lines 72-74: Iterating/mapping over the returned array of objects, destructuring the
nameandmodelproperties from each object as I iterate over the array
{generateLabeledList('Piloted', 'starships', starships
.map(({ name, model } : { name: string, model: string }) => name === model ? name : `"${name}" ${model}`))
}
I've been troubleshooting this for several hours now with no "good" solution working as it should for this, without having to break the function into several sub-functions, and even then, this issue of dynamic keys usually results in an error. As this function should be able to be used for any object, I cannot explicitly list the keys as optional properties.
I am looking for a way, preferably using my current approach or one similar, to resolve this error so the function can take in an array of strings and return an array of objects with those strings as its keys as expected.
Any ideas here as to how to get this working would be greatly appreciated. Thanks in advance. 🙏🏼
A few resources I've been referencing while working through this:
- Conditional Types (artsy.github.io): https://artsy.github.io/blog/2018/11/21/conditional-types-in-typescript/
- Conditional Types (typescriptlang.org): https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
- Function Overloading (dmitripavlutin.com): https://dmitripavlutin.com/typescript-function-overloading/
from Accepting different function return types for dynamic property names in TypeScript
No comments:
Post a Comment