utnalove
utnalove

Reputation: 199

React Redux Typescript - how to set types?

I'm having hard time setting the types for this function:

interface fetchedCountries {
    mergedArray?: [];
}

export const fetchCountries = () => {
    return (dispatch:(() => void)) => {
        console.log(dispatch)
        fetch(countryListJsonFile)
            .then((response) => response.json())
            .then((jsonData: fetchedCountries) => {
                const array = Object.entries(jsonData)[0];
                const countries = array[1].map((el: any) => {
                    return el._id;
                }).sort();
                dispatch(setCountries(countries));
            })
            .catch(function (err) {
                console.error(err);
            });
    };
};

it says that setCountries expected 0 arguments, but got 1.

I've tried to follow different guides, such as this, but I couldn't make it work. I got lost. I want here to get rid of all the any types, and give the real ones.

The store has exported:

export type RootState = ReturnType<typeof store.getState>;
export type AppState = ReturnType<typeof rootReducer>;
export type AppDispatch = typeof store.dispatch;

Here is the slice:

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
    countryList: [],
};

const slice = createSlice({
    name: 'countryList',
    initialState,
    reducers: {
        setCountries(state, action) {
            state.countryList = action.payload;
        },
    },
});

export const { setCountries } = slice.actions;
export default slice.reducer;

Can anyone please help?

Upvotes: 3

Views: 2833

Answers (2)

T.D. Stoneheart
T.D. Stoneheart

Reputation: 901

Actually, the problem lies in the parameter type of the returning function.

You returned (dispatch: ((/* no parameters */) => void)) => {} but you called dispatch(setCountries(countries)) which has one argument setCountries(countries). Setting correct number of parameters will fix, like return (dispatch: ((something: unknown) => void)) => {}.

Upvotes: 1

Linda Paiste
Linda Paiste

Reputation: 42298

There are a few issues here in your code are all related. You need to properly define:

  1. The type of dispatch in your fetchCountries function.
  2. The type of the payload for setCountries.
  3. The type of your state.
  4. The type of your API response.

Incorrect or missing types higher up in the chain can cause issues further down. For example, when you find yourself setting a type in a .map() callback like this:

array[1].map((el: any) => {

It means that the array itself (array[1]) has the wrong type. So let's figure out where it's going wrong.


1. Dispatch Type

@T.D. Stoneheart is correct. The expected 0 arguments, but got 1 error comes from calling dispatch(...), not from calling setCountries(...).

Your definition dispatch:(() => void) says that dispatch does not take any arguments. This is obviously incorrect.

The good news is that you already have the correct type elsewhere in your code. It is the AppDispatch which you exported from your store file.

export const fetchCountries = () => {
    return (dispatch: AppDispatch) => {

This fix is enough to resolve all red-underlined errors. But there are some other mistakes and omissions that you may want to fix.


2. Payload Type

If you don't explicitly set the type for setCountries(state, action), then your payload type becomes any. This is fine, but not ideal. It says that "anything goes" which can make it hard to see genuine problems.

To assign the correct type to the action, import the PayloadAction utility type for redux toolkit:

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

And use it with the type of your payload, which is an array of countries. Looking at your thunk, these seem to be string?:

reducers: {
    setCountries(state, action: PayloadAction<string[]>) {
        state.countryList = action.payload;      
    },
},

3. State Type

Remember how I said that any can hide genuine problems? If you followed step 2 then you should be seeing one of those now.

The assignment of state.countryList = action.payload; is giving an error:

Type 'string[]' is not assignable to type 'never[]'.

Your state.countryList has type never[] because it had an initial value of [] and that's all that TypeScript knows. It doesn't know that this is supposed to be an array of country ids. You can fix that by assigning a more accurate type to your `initialState.

Either do this:

const initialState = {
    countryList: [] as string[],
};

Or this:

interface SliceState { // can name it whatever
    countryList: string[];
}

const initialState: SliceState = {
    countryList: [],
};

Having a correct type here will make it much, much easier to use data that you've selected from your state because now RootState has the correct type for the countryList property.


4. API Response Type

interface fetchedCountries {
    mergedArray?: [];
}

This type is saying that the JSON from your response is an object which maybe has a property mergedArray which is an empty array. That's it.

I'm not sure what the actual data looks like, but perhaps something like this?

interface Country {
    _id: string;
}

interface FetchedCountries {
    mergedArray: Country[];
}

So now you don't need to use (el: any) because TypeScript already knows that el is a Country object.

.then((jsonData: FetchedCountries) => {
    const countries = jsonData.mergedArray
        .map(el => el._id)
        .sort();
    dispatch(setCountries(countries));
})

TypeScript Playground Link

Upvotes: 1

Related Questions