Search code examples
reactjstypescriptreact-typescript

Argument of type Promise<MemberEntityVM[]> is not assignable to parameter of type SetStateAction<MemberEntityVM[]>


I want to show a list of members of Github, filtered by organization (for instance, members of Github which are employees of Microsoft). I am using React + TS. So, I have the following API Model (the interface of the JSON data that I receive after doing the call to the GitHub API):

export interface MemberEntityApi {
    login: string;
    id: number;
    node_id: string;
    avatar_url: string;
    gravatar_id: string;
    url: string;
    html_url: string;
    followers_url: string;
    following_url: string;
    gists_url: string;
    starred_url: string;
    subscriptions_url: string;
    organizations_url: string;
    repos_url: string;
    events_url: string;
    received_events_url: string;
    type: string;
    site_admin: string;
}

But I dont wanna use so much information, so I will create a ViewModel (just with the information I want to use). Here my ViewModel:

export interface MemberEntityVM {
    id: number;
    login: string;
    avatar_url: string;
}

Here my call to the API:

export const getMembers = (organization: string, perpage, page): Promise<MemberEntityApi[]> => {
    return fetch(`https://api.github.com/orgs/${organization}/members?per_page=${perpage}&page=${page}`)
        .then((response) => {
            if (response.status == 200) {
                const data = response.json();
                return data;
            } else {
                throw new Error(response.statusText)
            }
        })
        .catch((e) => console.log(e));
}

Here my mapper function:

export const mapMemberEntityFromAPIModelToVM = (data: MemberEntityApi[]): MemberEntityVM[] => {
    return data.map((data) => {
        return {
            id: data.id,
            login: data.login,
            avatar_url: data.avatar_url
        }
    })
}

So, in my component, the problem is the following one:

const [members, setMembers] = React.useState<MemberEntityVM[]>([]);
const [organization, setOrganization] = React.useState<string>("Microsoft");

    useEffect(() => {
        const membersVM = getMembers(organization, perpage, page)
            .then((data) => mapMemberEntityFromAPIModelToVM(data));
        console.log("membersVM", membersVM);
        setMembers(membersVM);
        console.log("members", members);
    }, [])

This sentence console.log("membersVM", membersVM); is showing a response with an array of 3 members of a given organization (that is correct, since I just want to show 3 members per page) but this sentence console.log("members", members); shows an empty array. That means setMembers(membersVM); is not working. But I dont understand why. I am receiving the following mistake: "Argument of type Promise<MemberEntityVM[]> is not assignable to parameter of type SetStateAction<MemberEntityVM[]>" in the line I do setMembers(membersVM);. I dont understand this error message since membersVM has the following interface MemberEntityVM[] and the type of useState is the following one: React.useState<MemberEntityVM[]>. Where am I committing the mistake? Thanks in advance, kind regards.


Solution

  • Because getMembers(..).then(..) returns a promise of the mapped members, you will need to either await the value or deal with it in the then handler. Since useEffect does not allow an asynchronous function as its argument, the easiest solution is to call setMembers in the existing then handler:

    useEffect(() => {
      getMembers(organization, perpage, page)
        .then((data) => {
          const membersVM = mapMemberEntityFromAPIModelToVM(data);
          console.log("membersVM", membersVM);
          setMembers(membersVM);
          console.log("members", members);
        });
    }, [])
    

    If you do wish to use async/await, you can declare a local async function fetchMembers, and immediately call it:

    useEffect(() => {
      const fetchMembers = async () => {
        const data = await getMembers(organization, perpage, page);
        const membersVM = mapMemberEntityFromAPIModelToVM(data);
        console.log('membersVM', membersVM);
        setMembers(membersVM);
        console.log('members', members);
      }
      fetchMembers();
    }, [])