I'm trying to convert the following data [{ year: "yyyy" }, { month: "mm" }, { day: "dd" }]
into this { year: "yyyy", month: "mm", day: "dd" }
. So in the below code base, I used reduce method, but I'm getting type script error message on curr
, saying "Type '{ [x: string]: any; }' is not an array type or a string type." Also on acc[key]
, I get another error message "Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}'" In other IDE, I don't get type error, but get this error on console "TypeError: Invalid attempt to iterate non-iterable instance.
In order to be iterable, non-array objects must have a Symbol.iterator method."
I know there are other ways to manipulate the data such as for loop, but I would like to know how I can get this working using reduce method. https://codesandbox.io/s/sharp-glitter-43ql15?file=/src/App.tsx:0-550
import "./styles.css";
import { useEffect } from "react";
export default function App() {
interface IData {
day: string;
month: string;
year: string;
}
let data = [{ year: "yyyy" }, { month: "mm" }, { day: "dd" }];
const updateData = (): IData => {
const updatedData = data.reduce((accu, curr: { [x: string]: any }) => {
for (const [key, val] of curr) {
accu[key] = val;
}
return accu;
}, {});
console.log(updatedData);
return updatedData;
};
return <div className="App"></div>;
}
CodePudding user response:
Using
any
in TypeScript defeats the purpose of using TypeScript, because you lose type safety. Best to avoid it whenever possible - then you'll be able to more reliably debug things at compile-time instead of at runtime. Here, the values of the properties are strings, so you can use that instead. Or, you could leave out the explicit annotation entirely and TypeScript will infer it just fine on its own.While iterating, each object being iterated over is in the
curr
parameter. It's not an array; it's a plain object like{ year: "yyyy" }
. So, trying to iterate over the object withfor..of
doesn't make sense. UseObject.entries
to iterate over each key and each value.for (const [key, val] of Object.entries(curr)) {
But the returned object still won't be typed properly. While you could call
.reduce
with a generic to indicate the type of the accumulator, because the accumulator isn't fully constructed until the final iteration, you'd have to usePartial
- and then you'd have to cast it back to the fullIData
at the end. A problem is that all approaches to iterate over the keys of an object will result in the key being typed as astring
, even if the object's keys are more specific (likeyear | month | day
) - so some sort of casting will be necessary somewhere (or you'll have to explicitly check against every possible key at runtime).Rather than wrestling with the typing of
.reduce
, consider just doing:const updateData = () => Object.assign({}, ...data) as IData;
If you really had to use .reduce
, it'd look something like:
interface IData {
day: string;
month: string;
year: string;
}
let data = [{ year: "yyyy" }, { month: "mm" }, { day: "dd" }] as const;
const updateData = (): IData => {
const updatedData = data.reduce<Partial<IData>>((accu, curr) => {
for (const [key, val] of Object.entries(curr)) {
if (key !== 'year' && key !== 'month' && key !== 'day') {
throw new Error('Non-matching key found');
}
// eslint-disable-next-line no-param-reassign
accu[key] = val;
}
return accu;
}, {});
return updatedData as IData;
};