Newish to TS. I have a very simple demo app that uses the useReducer()
hook to manage the selected
state of items in a list. I thought I did a good job of making the reducer type safe but when I call the useReducer()
hook I get the TS2769: No overload matches this call
error. I will list the contents of the reducer file, the component which calls useReducer()
and then the exact error text, and a sandbox link:
Here is my reducer file:
// src/Components/List/Reducer.ts
export type ListAction = {
type: 'TOGGLE_SELECTED'
index: number
}
export type ListItemState = {
name: string
color: string
selected: boolean
}
const reducer = (state: ListItemState[], action: ListAction): ListItemState[] => {
const {index, type} = action
switch(type) {
case 'TOGGLE_SELECTED':
const item = state[index]
const newState = state.slice()
newState[index] = {...item, selected: !item.selected}
return newState
default:
return state
}
}
export default reducer
Here is the component which calls useReducer()
:
//src/Components/List/List.tsx
import React, {useReducer} from "react";
import Reducer, {ListItemState} from "./Reducer";
import InitialState from "./InitialState";
import ListItem from "../ListItem/ListItem";
import SelectedItems from "../SelectedItems/SelectedItems";
import "./List.css";
const List = () => {
const [state, dispatch] = useReducer(Reducer, InitialState);
const selected = state
.filter((item: ListItemState) => item.selected)
.map((selected: ListItemState) => selected.name);
return (
<>
<SelectedItems items={selected} />
<ul className="List">
{state.map((item: ListItemState, index: number) => (
<ListItem {...item} key={item.name} index={index} dispatch={dispatch} />
))}
</ul>
</>
);
};
export default List
Here is the exact error text:
Compiled with problems:X
ERROR in src/Components/List/List.tsx:10:31
TS2769: No overload matches this call. Overload 1 of 5, '(reducer: ReducerWithoutAction, initializerArg: any, initializer?: undefined): [any, DispatchWithoutAction]', gave the following error. Argument of type '(state: ListItemState[], action: ListAction) => ListItemState[]' is not assignable to parameter of type 'ReducerWithoutAction'. Overload 2 of 5, '(reducer: (state: ListItemState[], action: ListAction) => ListItemState[], initialState: ListItemState[], initializer?: undefined): [...]', gave the following error. Argument of type 'string[]' is not assignable to parameter of type 'ListItemState[]'. Type 'string' is not assignable to type 'ListItemState'. 8 | 9 | const List = () => {
10 | const [state, dispatch] = useReducer(Reducer, InitialState); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 11 | const selected = state 12 | .filter((item: ListItemState) => item.selected) 13 | .map((selected: ListItemState) => selected.name);
Sandbox: https://codesandbox.io/s/romantic-ride-j0y9y4
CodePudding user response:
Here's a type-safe simplification of your code in a single file. The reduce
pyramid you had in your InitialState
file is throwing TS off.
In other words, your reducer and state are fine, it's just that your initial data indeed wasn't soundly typed.
import React, { useReducer } from "react";
function getInitialState(): ListItemState[] {
const sizes = ["tiny", "small", "medium", "large", "huge"];
const colors = ["blue", "green", "orange", "red", "purple"];
const fruits = ["apple", "banana", "watermelon"];
const items = [];
for (const size of sizes) {
for (const color of colors) {
for (const fruit of fruits) {
items.push({
name: `${size} ${color} ${fruit}`,
color,
selected: false
});
}
}
}
return items;
}
type ListAction = {
type: "TOGGLE_SELECTED";
index: number;
};
type ListItemState = {
name: string;
color: string;
selected: boolean;
};
function reducer(state: ListItemState[], action: ListAction): ListItemState[] {
const { index, type } = action;
switch (type) {
case "TOGGLE_SELECTED":
const item = state[index];
const newState = [...state];
newState[index] = { ...item, selected: !item.selected };
return newState;
default:
return state;
}
}
function List() {
const [state, dispatch] = useReducer(reducer, null, getInitialState);
const selected = state
.filter(({ selected }) => selected)
.map(({ name }) => name);
return (
<>
{JSON.stringify(selected)}
<div>
{state.map(({ name, color }, index) => (
<button
key={name}
onClick={() => dispatch({ type: "TOGGLE_SELECTED", index })}
style={{ color }}
>
{name}
</button>
))}
</div>
</>
);
}
export default function App() {
return (
<div className="App">
<List />
</div>
);
}