I'm am new to TypeScript. I am in the process of converting a rather intricate Redux slice from JS to TS. I'm getting permutations of the same error in virtually all of my reducers. The error is TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft...
I will include the full source of the reducer slice with comments to indicate where the errors are showing in my IDE. Could someone please point me in the right direction as far as how I can remediate this? Thanks in advance!
Update: After receiving a very helpful reply from @ij7 I was able to fix most of the errors. However, I still have a few that I'm struggling with. Here is the updated code and errors:
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
export type Column = {
status: 'TO_DO' | 'IN_PROGRESS' | 'DONE'
title: 'To do' | 'In progress' | 'Done'
taskIds: [string]
}
export type Columns = {
[key in 'TO_DO' | 'IN_PROGRESS' | 'DONE']: Column
}
export type Task = {
id: string
title: string
description?: string
status: 'TO_DO' | 'IN_PROGRESS' | 'DONE'
board: string,
column: 'TO_DO' | 'IN_PROGRESS' | 'DONE'
}
export type Tasks = {
[key: string]: Task
}
interface TaskPayload {
task: Task
}
interface DeleteTaskPayload {
column: 'TO_DO' | 'IN_PROGRESS' | 'DONE'
id: string
}
interface MoveTaskPayload {
taskId: string
sourceIndex: number
sourceColumn: 'TO_DO' | 'IN_PROGRESS' | 'DONE'
destinationIndex: number
destinationColumn: 'TO_DO' | 'IN_PROGRESS' | 'DONE'
}
interface HydrateTasksPayload {
board: {
id: string
name: string
description?: string
TO_DO: [string]
IN_PROGRESS: [string]
DONE: [string]
}
tasks: [Task]
}
export type TaskSlice = {
boardId: string
boardName: string
boardDescription?: string
tasks: Tasks
columns: Columns
columnOrder: ['TO_DO', 'IN_PROGRESS', 'DONE']
}
export const reduceTasks = (tasks: [Task]) => {
return tasks.reduce((acc, curr) => {
return {
...acc,
[curr.id] : {
...curr
}
}
}, {})
}
export const initialState = {
boardId: '',
boardName: '',
boardDescription: '',
tasks: {},
columns: {
TO_DO: {
status: 'TO_DO',
title: 'To do',
taskIds: []
},
IN_PROGRESS: {
status: 'IN_PROGRESS',
title: 'In progress',
taskIds: []
},
DONE: {
status: 'DONE',
title: 'Done',
taskIds: []
}
},
columnOrder: ['TO_DO', 'IN_PROGRESS', 'DONE']
}
const taskSlice = createSlice({
name: 'task',
initialState,
reducers: {
addTask (state, { payload }: PayloadAction<TaskPayload>) {
const { task } = payload
return {
...state,
tasks: {
...state.tasks,
[task.id]: task
},
columns: {
...state.columns,
[task.status]: {
...state.columns[task.status],
taskIds: [
...state.columns[task.status].taskIds,
task.id
]
}
}
}
},
deleteTask (state, { payload }: PayloadAction<DeleteTaskPayload>) {
const { column, id } = payload
const { [id]: deleted, ...restTasks }: Tasks = state.tasks
return {
...state,
tasks: {
...restTasks
},
columns: {
...state.columns,
[column]: {
...state.columns[column],
taskIds: [
...state.columns[column]?.taskIds?.filter((taskId:string) => taskId !== id)
]
}
}
}
},
editTask (state, { payload }: PayloadAction<TaskPayload>) {
const { task } = payload
return {
...state,
tasks: {
...state.tasks,
[task.id]: task
},
columns: {
...state.columns,
[task.column]: {
...state.columns[task.column],
taskIds: [
...state.columns[task.column].taskIds
]
}
}
}
},
replaceTask (state, {payload}: PayloadAction<TaskPayload>) {
const { task } = payload
return {
...state,
tasks: {
...state.tasks,
[task.id]: task
}
}
},
moveTask (state, { payload }: PayloadAction<MoveTaskPayload>) {
const {
taskId,
sourceIndex,
sourceColumn,
destinationIndex,
destinationColumn
} = payload
const sourceTaskIdsClone = [...state.columns[sourceColumn].taskIds] as string[]
if (sourceColumn === destinationColumn) {
sourceTaskIdsClone.splice(sourceIndex, 1)
sourceTaskIdsClone.splice(destinationIndex, 0, taskId)
return {
...state,
columns: {
...state.columns,
[sourceColumn]: {
...state.columns[sourceColumn],
taskIds: sourceTaskIdsClone
}
}
}
} else {
sourceTaskIdsClone.splice(sourceIndex, 1)
const destinationTaskIdsClone = [...state.columns[destinationColumn].taskIds]
destinationTaskIdsClone.splice(destinationIndex, 0, taskId) //TS2345: Argument of type 'string' is not assignable to parameter of type 'never' (error is on `taskId)
return {
...state,
tasks: {
...state.tasks,
[taskId]: {
...state.tasks[taskId], // TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'WritableDraft<{}>'. No index signature with a parameter of type 'string' was found on type 'WritableDraft<{}>'.
column: destinationColumn,
status: initialState.columns[destinationColumn].status
}
},
columns: {
...state.columns,
[sourceColumn]: {
...state.columns[sourceColumn],
taskIds: sourceTaskIdsClone
},
[destinationColumn]: {
...state.columns[destinationColumn],
taskIds: destinationTaskIdsClone
}
}
}
}
},
hydrateTasks (state, { payload }: PayloadAction<HydrateTasksPayload>) {
const { board, tasks } = payload
const { TO_DO, IN_PROGRESS, DONE } = board
return {
boardId: board.id,
boardName: board.name,
boardDescription: board.description,
tasks: reduceTasks(tasks),
columnOrder: ['TO_DO', 'IN_PROGRESS', 'DONE'],
columns: {
TO_DO: {
status: 'TO_DO',
title: 'To do',
taskIds: [...TO_DO]
},
IN_PROGRESS: {
status: 'IN_PROGRESS',
title: 'In progress',
taskIds: [...IN_PROGRESS]
},
DONE: {
status: 'DONE',
title: 'Done',
taskIds: [...DONE]
}
}
}
}
}
})
export const { addTask, deleteTask, editTask, moveTask, addBoard, editBoard, hydrateTasks, replaceTask } = taskSlice.actions
export default taskSlice.reducer
CodePudding user response:
The main problem seems to be that you're not telling the compiler the types of the actions that your reducers are expecting.
Let's use the first one as an example:
const taskSlice = createSlice({
name: 'task',
initialState,
reducers: {
addTask (state, { payload }) {
const { task } = payload
return // ...
}
// ...
}
}
and specifically these two lines:
addTask (state, { payload }) {
const { task } = payload
In that function signature, the compiler can correctly infer the type of state
because it knows that this is a reducer, but you have to tell it the type of the payload in your action. Without being explicit about that, the compiler doesn't know what the type of payload
(and by extension, task
) is.
If you add this:
interface TaskPayload {
task: Task;
}
// ...
addTask (state, { payload } : PayloadAction<TaskPayload>) {
const { task } = payload
(where PayloadAction
is another import from @reduxjs/toolkit
), the compiler now knows that task
is a Task
, and therefore it knows that state.columns[task.status]
is valid.
This gets rid of the first two "... expression of type 'any' can't be used ..." errors; the others are similar.
Aside from getting rid of those errors, you're also making sure that your actions are correctly typed, meaning now the compiler can validate that in
store.dispatch(taskSlice.actions.addTask({ task: whatever }))
the value whatever
is actually a Task
, and complain if it's not.