Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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

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():

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

//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

>Solution :

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>
  );
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading