I'm new to typescript and to Index signatures and I don't find how to solve that error in my code. I assume that I need to something like on variable sortProperty on the variable sorted, but I can't find the answer. I have 2 errors. One on the setData(sorted)
Argument of type { id: number; jobName: string; created: string; modified: string; }[]' is not assignable to parameter of type 'SetStateAction<never[]>'
and the other on (b[sortProperty])
Element implicitly has an 'any' type because expression of type 'string' can't be used to index
data.js
export const tasks = [
{
id: "1",
jobName: 'MK-00544 • Spacecraft',
created: '2016-09-03',
modified: '2016-09-04'
},
{
id: "2",
jobName: 'MK-00545 • Spacecraft',
created: '2017-09-26',
modified: '2018-09-29'
},
{
id: "3",
jobName: 'MK-00546 • Spacecraft',
created: '2019-10-03',
modified: '2022-12-08'
},
{
id: "4",
jobName: 'MK-00547 • Spacecraft',
created: '2020-09-12',
modified: '2021-09-03'
},
{
id: "5",
jobName: 'MK-005478 • Spacecraft',
created: '2019-09-03',
modified: '2019-09-03'
},
{
id: "6",
jobName: 'MK-00549 • Spacecraft',
created: '2019-09-03',
modified: '2019-09-03'
}
]
Job.tsx :
interface Task {
[key: string]: string
}
const [data, setData] = useState<Task>([])
const [sortType, setSortType] = useState('jobName')
useEffect(() => {
const sortArray = (type: string) => {
interface StringIndexedObject {
[key: string]: string
}
const types: StringIndexedObject = {
jobName: 'jobName',
created: 'created',
modified: 'modified'
}
interface AnotherStringIndexedObject {
[key: string]: keyof Task
}
const sortProperty = types[type]
const sorted: AnotherStringIndexedObject = [...tasks].sort(
(a, b) => new Date(b[sortProperty]) - new Date(a[sortProperty])
)
console.log(sorted)
setData(sorted)
}
sortArray(sortType)
}, [sortType])
return (
<div className="font-bold">Sort by:</div>
<select
onChange={(e) => setSortType(e.target.value)}
className="text-center px-5 bg-gray-200 rounded-xl"
>
<option value="id">Id</option>
<option value="jobName">Job name</option>
<option value="created">Newest</option>
<option value="modified">Last modified</option>
</select>
<div>({tasks.length} users)</div>
{data.map((task) => (
<tr
key={task.id}
className="tableau text-center cursor-pointer"
onClick={() => router.push(`/job/${task.id}`)}
>
<td className="py-3">{task.jobName}</td>
<td className="py-3">
<div className="flex items-center gap-5 justify-center">
{task.created}
</div>
</td>
<td className="py-3">
<div className="flex items-center gap-5 justify-center">
{task.modified}
</div>
</td>
))}
You've got a bunch of issues here which are all related. When the type for one variable is incomplete or incorrect it can cause issues in other places where you use that variable.
Your component's job is to sort and render an array of task objects. It internally stores two states:
(Note: the sorted array one doesn't need to be a state as it is a derived value. Personally I would use a useMemo
instead of a useState
and a useEffect
but what you have will work so let's keep it and focus on the TypeScript issues)
Here are the things that I've fixed:
1. Describe the type of a task object.
Not every string is a valid key here. We have a few known properties and that's it.
interface Task {
id: string;
jobName: string;
created: string;
modified: string;
}
2. Set a type for the data
state.
It is an array of tasks aka Task[]
. You need a type here because the initial value is an empty array which has type never[]
. If the initial array was not empty then it could be inferred automatically.
const [data, setData] = useState<Task[]>([])
3. Defined your types
object as mapping from string
to keyof Task
.
Now the sortProperty
has the type keyof Task
, meaning that it is always a property of the Task
.
const sortArray = (type: string) => {
interface StringIndexedObject {
[key: string]: keyof Task
}
const types: StringIndexedObject = {
jobName: 'jobName',
created: 'created',
modified: 'modified'
}
const sortProperty = types[type];
You can also do:
const types: Record<string, keyof Task> = {
4. Removed the AnotherStringIndexedObject
type.
The sorted array is not an object. It is an array Task[]
. You can write const sorted: Task[]
, but you don't need to.
5. Removed the new Date
handling.
This doesn't make sense if the sort type is 'jobName'
or 'id'
. What is new Date('MK-005478 • Spacecraft')
? For the fields which are dates, we can compare the ISO strings as strings.
6. Used lodash sortBy
for the sorting.
You can define a custom compare function but I find this really annoying.
const sorted = sortBy(tasks, sortProperty);
7. Renamed your data file from data.js
to data.ts
If it is a TypeScript file then your imported tasks
variable will have proper types based on its values.
Job.tsx
import React, { useState, useEffect } from 'react';
import { sortBy } from 'lodash';
import { useRouter } from 'next/router';
import { tasks } from './data';
export interface Task {
id: string;
jobName: string;
created: string;
modified: string;
}
export const Job = () => {
const router = useRouter();
const [data, setData] = useState<Task[]>([])
const [sortType, setSortType] = useState('jobName')
useEffect(() => {
const sortArray = (type: string) => {
interface StringIndexedObject {
[key: string]: keyof Task
}
const types: StringIndexedObject = {
jobName: 'jobName',
created: 'created',
modified: 'modified'
}
const sortProperty = types[type];
const sorted = sortBy(tasks, sortProperty);
console.log(sorted)
setData(sorted)
}
sortArray(sortType)
}, [sortType]);
return (
<div>
<div className="font-bold">Sort by:</div>
<select
onChange={(e) => setSortType(e.target.value)}
className="text-center px-5 bg-gray-200 rounded-xl"
>
<option value="id">Id</option>
<option value="jobName">Job name</option>
<option value="created">Newest</option>
<option value="modified">Last modified</option>
</select>
<div>({tasks.length} users)</div>
{data.map((task) => (
<tr
key={task.id}
className="tableau text-center cursor-pointer"
onClick={() => router.push(`/job/${task.id}`)}
>
<td className="py-3">{task.jobName}</td>
<td className="py-3">
<div className="flex items-center gap-5 justify-center">
{task.created}
</div>
</td>
<td className="py-3">
<div className="flex items-center gap-5 justify-center">
{task.modified}
</div>
</td>
</tr>
))}
</div>
)
}