Home > Enterprise >  React.useReducer() is throwing TS2769: No overload matches this call
React.useReducer() is throwing TS2769: No overload matches this call

Time:07-15

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.

Sandbox here.

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>
  );
}
  • Related