javascript - Typescript-fel TS2554: Förväntade 0 argument, men fick 1. vid användning av useReducer och useContext med React


Jag har ett antal useReducers och useContext där jag får liknande fel som det som anges nedan (TS2554). Jag har valt ut AuthReducer eftersom det är den enklaste. Jag får samma fel för varje Action dispatch. Jag har experimenterat med att titta på andra lösningar och prova dem, lagt till några fler definitioner men tydligtvis gör jag inte vad jag borde göra!

Det är mitt första projekt som jag arbetar med i Typescript, så var snäll mot mig!

Så vitt jag kan förstå förväntar sig Dispatch inga argument men får ett. Men när jag deklarerar min AuthReducer har den ett action-argument av typen Actions och returnerar AuthState.

/AuthState.tsx:40:16 - error TS2554: Expected 0 arguments, but got 1.

40       dispatch({
41         type: USER_LOADED,
42         payload: res.data,
43       });

AuthReducer.tsx

import {
  USER_LOADED,
  AUTH_ERROR,
  LOGIN_SUCCESS,
  LOGIN_FAIL,
  LOGOUT,
  CLEAR_ERRORS,
} from '../types';
import { initialState } from './AuthState';

type Actions =
  | {
      type: 'USER_LOADED';
      payload: {
        name: string;
        id: string;
      };
    }
  | {
      type: 'AUTH_ERROR';
      payload?: string;
    }
  | {
      type: 'LOGIN_SUCCESS';
      payload: { token: string };
    }
  | {
      type: 'LOGIN_FAIL';
      payload: string;
    }
  | {
      type: 'LOGOUT';
      payload?: string;
    }
  | {
      type: 'CLEAR_ERRORS';
    };

interface AuthState {
  token?: string | null;
  isAuthenticated?: boolean;
  loading: boolean;
  user?: {
    name: string;
    id: string;
  } | null;
  error?: string | undefined | null;
}

const AuthReducer = (
  state: typeof initialState,
  action: Actions
): AuthState => {
  switch (action.type) {
    case USER_LOADED:
      return {
        ...state,
        isAuthenticated: true,
        loading: false,
        user: action.payload,
      };
    case LOGIN_SUCCESS:
      localStorage.setItem('token', action.payload.token);
      return {
        ...state,
        ...action.payload,
        isAuthenticated: true,
        loading: false,
      };
    case AUTH_ERROR:
    case LOGIN_FAIL:
    case LOGOUT:
      localStorage.removeItem('token');
      return {
        ...state,
        token: null,
        isAuthenticated: false,
        loading: false,
        user: null,
        error: action.payload,
      };
    case CLEAR_ERRORS:
      return {
        ...state,
        error: null,
      };
    default:
      return state;
  }
};

export default AuthReducer;

AuthContext.tsx

import { createContext } from 'react';
import { initialState } from './AuthState';

type authContextType = {
  loadUser: () => Promise<void> | null;
  login: (formData: {
    email: string;
    password: string;
  }) => Promise<void> | null;
  logout: () => void;
  clearErrors: () => void;

  error: string | null;
  isAuthenticated: boolean;
  loading: boolean;
  user: {
    name: string;
    id: string;
  };
  token: string;
};

const authContext = createContext<authContextType>(initialState); //TODO A more robust type is possible

export default authContext;

AuthState.tsx

import React, { useReducer } from 'react';
import axios from 'axios';
import AuthContext from './AuthContext';
import AuthReducer from './AuthReducer';
import setAuthToken from '../../utils/setAuthToken';
import {
  USER_LOADED,
  AUTH_ERROR,
  LOGIN_SUCCESS,
  LOGIN_FAIL,
  LOGOUT,
  CLEAR_ERRORS,
} from '../types';

export const initialState = {
  loadUser: null,
  login: null,
  logout: () => {},
  clearErrors: () => {},

  token: localStorage.getItem('token'),
  isAuthenticated: null,
  loading: true,
  error: null,
  user: null,
};

const AuthState: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(AuthReducer, initialState);

  //Load User
  const loadUser = async () => {
    if (localStorage.token) {
      setAuthToken(localStorage.token);
    }

    try {
      const res = await axios.get('api/auth');

      dispatch({
        type: USER_LOADED,
        payload: res.data,
      });
    } catch (err) {
      dispatch({ type: AUTH_ERROR });
    }
  };
  //Login User
  const login = async (formData: { email: string; password: string }) => {
    const config = {
      headers: {
        'Content-Type': 'application/json',
      },
    };
    try {
      const res = await axios.post('/api/auth', formData, config);

      dispatch({
        type: LOGIN_SUCCESS,
        payload: res.data,
      });

      loadUser();
    } catch (err) {
      dispatch({
        type: LOGIN_FAIL,
        payload: err.response.data.msg,
      });
    }
  };
  //Logout
  const logout = () => {
    dispatch({ type: LOGOUT });
  };
  //Clear Errors
  const clearErrors = () => {
    dispatch({ type: CLEAR_ERRORS });
  };

  return (
    <AuthContext.Provider
      value={{
        token: state.token,
        isAuthenticated: state.isAuthenticated,
        loading: state.loading,
        user: state.user,
        error: state.error,
        loadUser,
        login,
        logout,
        clearErrors,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export default AuthState;

Svar #1

Problemet är att här:

const AuthReducer = (
  state: typeof initialState,
  action: Actions
): AuthState ...

TypeScript kan inte relatera dina typer typeof initialState och AuthState. Men useReducers överlagringar beror på typen av reducer för att härleda korrekt typ av resultat.

Du har flera alternativ för refaktorering här. Du kan helt enkelt tala om för TypeScript att de är samma typer:

const AuthReducer = (
  state: AuthState,
  action: Actions
): AuthState

eller gör din initialState till en lite mer avslappnad typ:

const initialState: AuthState = {....}

Kärnan i problemet är att typerna TypeofA och A:

const a = { a: null }
type TypeofA = typeof a

type A = { a: string | null }

är inte exakt samma. Du förlorar | string när du deklarerar ett fält endast som null. För att göra dem lika kan du antingen skriva ett a-objekt med typkontroller:

const a = { a: null as string | null }

eller ange direkt typen av a:

const a: A = { a: null }