Search code examples
reactjstypescriptreduxredux-saga

React Redux Saga with Typescript error property does not exist


Setting up a React, Next js app with TS for the first time and getting a beating at it.

TS throws below error on redux-saga set up:

Property 'name' does not exist on type 'Input | DataPostGreeting | ErrorPostGreeting'. Property 'name' does not exist on type 'DataPostGreeting'

It's accurate that name does not exist on type DataPostGreeting, which is the type of the successful response, but it does exist in Input

The action in question is a POST to create a greeting on a test app.

I thought I had properly typed the payload as Input which is the name of the greeting, but TS does not seem happy with that. Even though it does recognizes Input as part of the applicable types.

This is the saga with the error trigger highlighted:

/store/sagas/greetings.ts

import { all, call, put, takeLatest } from 'redux-saga/effects';
import { actionPostGreetingOk, actionPostGreetingError } from '../actions/creators';
import { Actions } from '../actions/names';
import { ActionTypes } from '../actions/types';
import { apiQl } from 'lib/functions';

function* postGreeting(action: ActionTypes) {
  const queryQl = `mutation createGreeting(
    $name: String!
  ) {
      createGreeting(input: {
          name: $name
      }) {
          greeting {
              id
          }
      }
  }`;


  const variables = {
     name: action.payload.name,        // *** ERROR IS THROWN HERE ***
  };


  try {
    const data = yield call(apiQl, queryQl, variables);
    if (data.errors) {
        yield put(
            actionPostGreetingError({
                error: data.errors,
            }),
        );
    } else {
        yield put(
            actionPostGreetingOk({
                greeting: data.data.createGreeting,
            }),
        );
    }
} catch (error) {
    if (error.response === undefined || error.code === 'ECONNABORTED') {
        console.log('ERROR RESPONSE UNDEFINED, ABORTED');
    }
}

export default function* greeting() {
   yield all([takeLatest(Actions.POST_GREETING, postGreeting)]);
}

On the types file:

...
export interface Input {
   name: string;
}
export interface ActionPostGreeting {
   type: typeof Actions.POST_GREETING;
   payload: Input;
}
...

export type ActionTypes =
  | ActionPostGreeting
  | ActionPostGreetingInit
  | ActionPostGreetingOk
  | ActionPostGreetingError
  | ActionHydrate;

The action creator:

export const actionPostGreeting = (payload: Input): ActionPostGreeting => {
  return {
    type: Actions.POST_GREETING,
    payload,
 };
};

Solution

  • You are passing the whole ActionTypes union to your saga, but your function should only take a specific payload type (e.g. ActionPostGreeting). Otherwise it means your saga could be called with any payload, to fix your issue you can restrict the type like this:

    function* postGreeting(action: ActionPostGreeting) {
      // do stuff
    
      const variables = {
        name: action.payload.name, // will be good
      };
    }