I have items of varying types (Item
) and these can exist at different levels in my state (Section.items
and subSection.items
).
I need to pass these to a different React component based on their type (Str
or Num
).
The following code works however it’s not ideal data structuring.
type I_Str = {
type: "str";
value: string;
};
type I_Num = {
type: "num";
value: number;
};
type Item = I_Str | I_Num;
type SubSection = {
name: string;
items: Item[];
};
type Section = {
name: string;
items: Item[];
subSection?: SubSection[];
};
type State = {
section: Section[];
};
const state: State = {
section: [
{
name: "Sec1",
items: [
{ type: "str", value: "im a string" },
{ type: "num", value: 1 },
],
subSection: [
{
name: "Sub1",
items: [
{ type: "num", value: 999 },
{ type: "str", value: "sub section string" },
],
},
],
},
],
};
function Str({ item }: { item: I_Str }) {
return <div>Value: {item.value.toUpperCase()}</div>;
}
function Num({ item }: { item: I_Num }) {
return <div>Value: {item.value * 10}</div>;
}
function Items({ items }: { items: Item[] }) {
return (
<div style={{ border: "1px solid blue", padding: 20 }}>
{items
.sort((a, b) => {
if (a.type > b.type) return 1;
if (a.type < b.type) return -1;
return 0;
})
.map((item) => {
if (item.type === "str") return <Str item={item} />;
if (item.type === "num") return <Num item={item} />;
})}
</div>
);
}
function SubSection({ subSection }: { subSection: SubSection }) {
return (
<div>
<div>{subSection.name}</div>
<Items items={subSection.items} />
</div>
);
}
function Section({ section }: { section: Section }) {
return (
<div key={section.name} style={{ border: "1px solid grey", padding: 20 }}>
<div>{section.name}</div>
<Items items={section.items} />
{section.subSection && section.subSection.length > 0
? section.subSection.map((sub) => {
return <SubSection key={sub.name} subSection={sub} />;
})
: null}
</div>
);
}
export default function Page() {
return (
<div>
{state.section.map((section) => {
return <Section key={section.name} section={section} />;
})}
</div>
);
}
It’s not ideal as items
is an array but the order isn’t important.
Also you should only be able to have one item of each type eg this shouldn't be allowed:
items: [
{ type: "str", value: “String 1” },
{ type: "str", value: “String 2” },
]
I can change Items
to be an object and then im happy with the data structure, but TypeScript errors:
type Items = {
str?: string;
num?: number;
};
type SubSection = {
name: string;
items: Items;
};
type Section = {
name: string;
items: Items;
subSection?: SubSection[];
};
type State = {
section: Section[];
};
const state: State = {
section: [
{
name: "Sec1",
items: {
str: "Im a st",
num: 1,
},
subSection: [
{
name: "Sub1",
items: {
num: 999,
str: "sub section string",
},
},
],
},
],
};
function Str({ value }: { value: string }) {
return <div>Value: {value.toUpperCase()}</div>;
}
function Num({ value }: { value: number }) {
return <div>Value: {value * 10}</div>;
}
function Items({ items }: { items: Items }) {
return (
<div style={{ border: "1px solid blue", padding: 20 }}>
{["num", "str"].map((key) => {
const value = items[key];
if (!value) return;
if (key === "num") return <Num value={value} />;
if (key === "str") return <Str value={value} />;
})}
</div>
);
}
function SubSection({ subSection }: { subSection: SubSection }) {
return (
<div>
<div>{subSection.name}</div>
<Items items={subSection.items} />
</div>
);
}
function Section({ section }: { section: Section }) {
return (
<div key={section.name} style={{ border: "1px solid grey", padding: 20 }}>
<div>{section.name}</div>
<Items items={section.items} />
{section.subSection && section.subSection.length > 0
? section.subSection.map((sub) => {
return <SubSection key={sub.name} subSection={sub} />;
})
: null}
</div>
);
}
export default function Page() {
return (
<div>
{state.section.map((section) => {
return <Section key={section.name} section={section} />;
})}
</div>
);
}
const value = items[key]; // Error occurs here
TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Items'. No index signature with a parameter of type 'string' was found on type 'Items'.
What is the best way to model this? Im simplified my example code, in reality I have many more types than just str
and num
and their values can also be more complex (eg varying objects not just primitive types).
The first thing I did is rename the component name (to ItemsComp
) because you had used the same names for both type and component. eg:- type Items
and function Items()
My question is do you have a dynamic count of types? I guess not, because you have created separate components for each type. eg:- function Str()
, function Num()
If I am correct can you do something like that?
function ItemsComp({ items }: { items: Items }) {
return (
<div style={{ border: "1px solid blue", padding: 20 }}>
{items.str && <Str value={items.str} />}
{items.num && <Num value={items.num} />}
{/* other types */}
</div>
);
}
And I am not getting any errors for this one
function ItemsComp({ items }: { items: Items }) {
return (
<div style={{ border: "1px solid blue", padding: 20 }}>
{(["num", "str"] as const).map((key) => {
const value = items[key];
if (!value) return null;
if (key === "num") return <Num key={key} value={Number(value)} />;
if (key === "str") return <Str key={key} value={`${value}`} />;
return null;
})}
</div>
);
}