I have a Nextjs project using next v10.1.2 and Typescript v4.4.4
I would like to have a class SortableList
that encapsulates a list of a custom type. The only requirement is that the list extends my abstract class OrderedItem
.
Currently, I am getting an error when I try to access a property on a class that extends OrderedItem
from a new SortableList
object.
Is this possible in Typescript and what is the correct way to set this up?
Code:
OrderedItem.ts
// The purpose of this class is to ensure that items in a list can be ordered.
abstract class OrderedItem {
order: number
constructor(order: number) {
this.order = order
}
}
export default OrderedItem
CustomListItem.ts
import OrderedItem from './OrderedItem'
// This is an example of a class that I would like to be orderable.
class CustomListItem extends OrderedItem {
name: string
jsx: JSX.Element
constructor(order: number, name: string, jsx: JSX.Element) {
super(order)
this.name = name
this.jsx = jsx
}
}
export default CustomListItem
SortableList.ts
import OrderedItem from './OrderedItem'
class SortableList {
// I want this array to be overridden by a custom class like CustomListItem
list: OrderedItem[]
constructor(list: OrderedItem[]) {
this.list = list
}
sortByOrder = (): void => {
this.list = this.list.sort((a, b) => a.order - b.order)
}
sortByReverseOrder = (): void => {
this.list = this.list.sort((a, b) => b.order - a.order)
}
}
export default SortableList
example.tsx
import CustomListItem from './CustomListItem'
import SortableList from './SortableList'
export default function Example() {
const customList: CustomListItem[] = [
new CustomListItem(3, 'name3', <div></div>),
new CustomListItem(1, 'name1', <div></div>),
new CustomListItem(2, 'name2', <div></div>),
]
const sortableList: SortableList = new SortableList(customList)
sortableList.sortByOrder()
return sortableList.list.map(item => item.jsx)
}
The error I am getting from this example is in the return statement of example.tsx
when trying to access the jsx
property on item
since item is typed as OrderedItem
and not CustomListItem
.
Error: Property 'jsx' does not exist on type 'OrderedItem'.
What is the correct way to do this? I tried messing around with declaring SortableList
with <T>
and initializing it like
const sortableList = new SortableList<CustomListItem>(customList)
, but I'm not familiar with how that is supposed to work.
The problem is when you access an element of your SortableList
the compiler knows just that it is an OrderedItem
which has no property jsx
.
There are two main solutions to this:
CustomListItem
in the list and has no property jsx
. This is how it is done:sortableList.list.map(item => (item as CustomListItem).jsx)
OrderedItem
class. In this way there are no pratical cases where you will get an unexpected error, because the ts compiler prevent them by checking your types. This is how it is done in your case:SortableList.ts
import OrderedItem from './OrderedItem'
// T is the generic
// T extends OrderedItem is the constraint
class SortableList<T extends OrderedItem> {
// this array can contains only elements
// that are descendant of T
// T is always a descendant of OrderedItem
list: T[]
constructor(list: T[]) {
this.list = list
}
sortByOrder = (): void => {
this.list = this.list.sort((a, b) => a.order - b.order)
}
sortByReverseOrder = (): void => {
this.list = this.list.sort((a, b) => b.order - a.order)
}
}
export default SortableList
example.tsx
import CustomListItem from './CustomListItem'
import SortableList from './SortableList'
export default function Example() {
const customList: CustomListItem[] = [
new CustomListItem(3, 'name3', <div></div>),
new CustomListItem(1, 'name1', <div></div>),
new CustomListItem(2, 'name2', <div></div>),
]
const sortableList: SortableList<CustomListItem> =
new SortableList<CustomListItem>(customList)
sortableList.sortByOrder()
return sortableList.list.map(item => item.jsx)
}