'React and TypeScript—which types for an Axios response?

I am trying to present a simple user list from an API which returns this:

[{"UserID":2,"FirstName":"User2"},{"UserID":1,"FirstName":"User1"}]

I do not understand fully how to handle Axios responses with types. The TypeScript error is

Type '{} | { id: number; firstName: string; }' is not assignable to type 'IntrinsicAttributes & UserListProps & { children?: ReactNode; }'.

Property 'items' is missing in type '{}' but required in type 'UserListProps'.

from the <UserList /> element in the Users.tsx file below. Is my User interface wrong?

import React, {useEffect, useState, Fragment } from 'react';
import UserList from './UserList';
import axios, {AxiosResponse} from 'axios';

interface User {
    id: number;
    firstName: string;
}

const Users: React.FC = (props) => {
    const [users, setUserList] = useState<User>();

    useEffect(() => {
        // Use [] as second argument in useEffect for not rendering each time
        axios.get('http://localhost:8080/admin/users')
        .then((response: AxiosResponse) => {
            console.log(response.data);
            setUserList( response.data );
        });
    }, []);

    return (
        <Fragment>
            <UserList {...users} />
        </Fragment>

    );
};
export default Users;

Below is my UserList.tsx.

import React, {Fragment } from 'react';

interface UserListProps {
    items: {id: number, firstName: string}[];
};

const UserList: React.FC<UserListProps> = (props) => {
    return (
        <Fragment>
            <ul>
            {props.items.map(user => (
                <li key={user.id}>
                    <span>{user.firstName}</span>
                    {/* not call delete function, just point to it
                    // set this to null in bind() */}
                </li>
            ))}
            </ul>
        </Fragment>
    );
};

export default UserList;


Solution 1:[1]

There is generic get method defined in axios/index.d.ts

get<T = never, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig<T>): Promise<R>;

Example

interface User {
    id: number;
    firstName: string;
}


axios.get<User[]>('http://localhost:8080/admin/users')
        .then(response => {
            console.log(response.data);
            setUserList( response.data );
        });

I think you are passing list the wrong way to child component.

const [users, setUserList] = useState<User[]>([]);
<UserList items={users} />
interface UserListProps {
    items: User[];
};
const UserList: React.FC<UserListProps> = ({items}) => {
    return (
        <Fragment>
            <ul>
            {items.map(user => (
                <li key={user.id}>
                    <span>{user.firstName}</span>
                </li>
            ))}
            </ul>
        </Fragment>
    );
};

Solution 2:[2]

You need to provide a type argument when calling axios.get if you do not want Axios to infer the type for the value response as any.

And you are passing an incorrect type argument when you useState to create the array of users.

The correct way

interface User {
  id: number;
  firstName: string;
}

// Initialized as an empty array
const [users, setUserList] = useState<User[]>([]); // 'users' will be an array of users

For example,

import React, {useEffect, useState, Fragment } from 'react';
import UserList from './UserList';
import axios from 'axios';

interface User {
  id: number;
  firstName: string;
}

// You can export the type TUserList to use as -
// props type in your `UserList` component
export type TUserList = User[]

const Users: React.FC = (props) => {
   // You can also use User[] as a type argument
    const [users, setUserList] = useState<TUserList>();

    useEffect(() => {
        // Use [] as a second argument in useEffect for not rendering each time
        axios.get<TUserList>('http://localhost:8080/admin/users')
        .then((response) => {
            console.log(response.data);
            setUserList(response.data);
        });
    }, []);

    return (
        <Fragment>
            <UserList {...users} />
        </Fragment>

    );
};
export default Users;

If you choose to export the type type TUserList = User[], you can use it in your UserList component as the type for props. For example,

import React, {Fragment } from 'react';
import { TUserList } from './Users';

interface UserListProps {
    items: TUserList // Don't have to redeclare the object again
};

const UserList: React.FC<UserListProps> = (props) => {
    return (
        <Fragment>
            <ul>
            {props.items.map(user => (
                <li key={user.id}>
                    <span>{user.firstName}</span>
                    { /* Do not call the delete function. Just point
                         to it. Set this to null in bind(). */}
                </li>
            ))}
            </ul>
        </Fragment>
    );
};

export default UserList;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Peter Mortensen
Solution 2 Peter Mortensen