2

I have a number of useReducers and useContext where I am getting similar errors as the one listed below (TS2554). I have picked out the AuthReducer as it is the simplest. I get the same error for each Action dispatch. I've toyed around looking at other solutions and trying them out, Added some more definitions but clearly I'm not doing what I'm supposed to be!

Its my first project that I'm working on with Typescript so go easy on me!

as far as I can tell Dispatch is not expecting any arguments but is receiving one. However when I declare my AuthReducer it has an action argument of type Actions and returns 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;

user3927638
  • 101
  • 2
  • 5
  • `state: typeof initialState,` this line doesn't look right though, shouldn't it be `state: typeof AuthState,`? – Tom Finney Jul 06 '21 at 12:30
  • Hey Tom, Thanks for the reply. I gave it a go and played around a bit more but unfortunately it Didn't seem to make a difference for the given error – user3927638 Jul 06 '21 at 12:50
  • I can't see anything else that looks weird -> can you tell me what `USER_LOADED` is from the `../types` folder – Tom Finney Jul 06 '21 at 13:06

1 Answers1

0

The problem is that here:

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

typescript cannot relate your types typeof initialState and AuthState. But useReducer's overloads are depending on the type of reducer to infer the correct type of result.

You have several refactoring options here. You can just tell typescript that those are the same types:

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

or make your initialState a bit more relaxed type:

const initialState: AuthState = {....}

The core of the problem is that types TypeofA and A:

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

type A = { a: string | null }

are not exactly the same. You're loosing | string when declaring a field simply as null. To make them equal you can either write a object with type assertions:

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

or directly state the type of a:

const a: A = { a: null }
aleksxor
  • 7,535
  • 1
  • 22
  • 27