Reputation: 4923
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!
export type Task = {
id: string
title: string
description?: string
status: 'TO_DO' | 'IN_PROGRESS' | 'DONE'
board: string
}
export type Tasks = {
[key: string]: 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 }) {
const { task } = payload
return {
...state,
tasks: {
...state.tasks,
[task.id]: task
},
columns: {
...state.columns,
[task.status]: {
...state.columns[task.status], // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
taskIds: [
...state.columns[task.status].taskIds, // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
task.id
]
}
}
}
},
deleteTask (state, { payload }) {
const { column, id } = payload
const { [id]: deleted, ...restTasks }: Tasks = state.tasks
return {
...state,
tasks: {
...restTasks
},
columns: {
...state.columns,
[column]: {
...state.columns[column], // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
taskIds: [
...state.columns[column]?.taskIds?.filter((taskId:string) => taskId !== id) // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
]
}
}
}
},
editTask (state, { payload }) {
const { task } = payload
return {
...state,
tasks: {
...state.tasks,
[task.id]: task
},
columns: {
...state.columns,
[task.column]: {
...state.columns[task.column], // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
taskIds: [
...state.columns[task.column].taskIds // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
]
}
}
}
},
replaceTask (state, {payload}) {
const { task } = payload
return {
...state,
tasks: {
...state.tasks,
[task.id]: task
}
}
},
moveTask (state, { payload }) {
const {
taskId,
sourceIndex,
sourceColumn,
destinationIndex,
destinationColumn
} = payload
const sourceTaskIdsClone = [...state.columns[sourceColumn].taskIds] // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
if (sourceColumn === destinationColumn) {
sourceTaskIdsClone.splice(sourceIndex, 1)
sourceTaskIdsClone.splice(destinationIndex, 0, taskId)
return {
...state,
columns: {
...state.columns,
[sourceColumn]: {
...state.columns[sourceColumn], // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
taskIds: sourceTaskIdsClone
}
}
}
} else {
sourceTaskIdsClone.splice(sourceIndex, 1)
const destinationTaskIdsClone = [...state.columns[destinationColumn].taskIds] // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
destinationTaskIdsClone.splice(destinationIndex, 0, taskId)
return {
...state,
tasks: {
...state.tasks,
[taskId]: {
...state.tasks[taskId], // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
column: destinationColumn,
status: initialState.columns[destinationColumn].status // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
}
},
columns: {
...state.columns,
[sourceColumn]: {
...state.columns[sourceColumn], // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
taskIds: sourceTaskIdsClone
},
[destinationColumn]: {
...state.columns[destinationColumn], // TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'WritableDraft
taskIds: destinationTaskIdsClone
}
}
}
}
},
hydrateTasks (state, { payload }) {
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
Upvotes: 1
Views: 1904
Reputation: 369
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.
Upvotes: 1