React and TypeScript—which types for an Axios response?

ReactjsTypescriptAxios

Reactjs Problem Overview


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;

Reactjs Solutions


Solution 1 - Reactjs

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 - Reactjs

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;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionG. DebaillyView Question on Stackoverflow
Solution 1 - ReactjsJózef PodleckiView Answer on Stackoverflow
Solution 2 - ReactjssubashMahapatraView Answer on Stackoverflow