Search code examples
reactjstypescriptreduxfirebase-authenticationredux-toolkit

How to fix 'property X does not exist on PayloadAction'?


I've recently decided to refactor an existing redux project to redux-toolkit. The project also uses typescript. In authSlice.ts I have created async thunk and added handler for 'fullfilled' case to extraReducers. And now typescript gives me a warning

Property 'idToken' does not exist on type 'PayloadAction<{ idToken: string; email: string; refreshToken: string; expiresIn: string; localId: string; registered: boolean; }, string, { arg: ReqPayload; requestId: string; requestStatus: "fulfilled"; }, never>'.ts(2339)

I'm quite novice to typescript, have no enough knowledge to spot an issue. Please help me to work it out.

import axios from 'axios'
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

interface ReqPayload {
  email: string,
  password: string,
  returnSecureToken: boolean
}

interface ResPayload {
  data: {
    idToken: string,
    email: string,
    refreshToken: string,
    expiresIn: string,
    localId: string,
    registered: boolean
  }
}

export const authenticate = createAsyncThunk(
  'auth',
  async ({ email, password, returnSecureToken }: ReqPayload) => {
    const API_KEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
    const payload: ReqPayload = { email, password, returnSecureToken: true }
    const response: ResPayload = await axios.post(
      `https://identitytoolkit.googleapis.com/v1/accounts:${returnSecureToken ? 'signInWithPassword' : 'signUp'}?key=${API_KEY}`,
      payload
    )
    const { localId, idToken, expiresIn } = response.data
    const expirationDate = new Date(new Date().getTime() + (+expiresIn * 1000))
    localStorage.setItem('idToken', idToken)
    localStorage.setItem('localId', localId)
    localStorage.setItem('expirationDate', '' + expirationDate)

    return response.data
  }
);
interface AuthState {
  idToken: string | null,
  localId: string | null,
  error: object | null,
  isLoading: boolean,
}

const initialState: AuthState = {
  idToken: null,
  localId: null,
  error: null,
  isLoading: false,
}

export const authSlice = createSlice({
  name: 'auth',
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: {
  },
  // The `extraReducers` field lets the slice handle actions defined elsewhere,
  // including actions generated by createAsyncThunk or in other slices.
  extraReducers: (builder) => {
    builder
      .addCase(authenticate.fulfilled, (state, action) => {
        state.idToken = action.idToken
        state.localId = action.localId
        state.error = null
        state.isLoading = false
      })
  },
});

Solution

  • You're trying to use action.idToken in your reducer, but that won't exist. Redux Toolkit always generates action objects that keep their data in an action.payload field. So, you need to use action.payload.idToken instead.

    TypeScript should actually be giving you autocompletion in your IDE and will show you that action.payload exists, and from there that action.payload.idToken exists.