Reputation: 634
I want to make an API call that's based on the current state, but can't make the setState function work as an asynchronous function.
handlePage = (direction: number) => {
this.setState( async (state) => {
const tickets: Ticket[] = await api.getTickets(`?page=${state.pageNum + direction}`)
const pageNum: number = state.pageNum + direction;
return { pageNum, tickets };
});
}
Gets me the error:
Argument of type '(state: Readonly) => Promise<{ pageNum: number; tickets: Ticket[]; }>' is not assignable to parameter of type 'AppState | ((prevState: Readonly, props: Readonly<{}>) => AppState | Pick<AppState, "tickets" | "search" | "theme" | "pageNum"> | null) | Pick<...> | null'. Type '(state: Readonly) => Promise<{ pageNum: number; tickets: Ticket[]; }>' is not assignable to type '(prevState: Readonly, props: Readonly<{}>) => AppState | Pick<AppState, "tickets" | "search" | "theme" | "pageNum"> | null'. Type 'Promise<{ pageNum: number; tickets: Ticket[]; }>' is not assignable to type 'AppState | Pick<AppState, "tickets" | "search" | "theme" | "pageNum"> | null'. Type 'Promise<{ pageNum: number; tickets: Ticket[]; }>' is missing the following properties from type 'Pick<AppState, "tickets" | "search" | "theme" | "pageNum">': search, theme, pageNumts(2345)
It works if I fetch the data outside the setState method, but I'm afraid to make an API call to an outdated page number:
handlePage = async (direction: number) => {
const tickets: Ticket[] = await api.getTickets(`?page=${this.state.pageNum + direction}`)
this.setState( (state) => {
const pageNum: number = state.pageNum + direction;
return { pageNum, tickets };
});
}
Upvotes: 0
Views: 553
Reputation: 42160
There are a lot of ways to handle this. I agree that this is an XY problem because my suggestion is to re-structure this so that you simply don't have this issue.
My best recommendation is that your state stores the tickets from all previously loaded pages in arrays keyed by the page number.
When the pageNum
changes you (conditionally) fetch the results for that page. Each response knows where it belongs in the state so it doesn't matter when storing it whether this is still the current page or not. If pageNum
changes twice in quick succession you would execute both calls and store both results but there won't be any confusion about which one is the correct result because each result is associated with its page number.
When it comes to accessing the tickets for the current page, just look up the tickets array for the current page number. It will either be an array Ticket[]
or undefined
if it is still loading. So that's extra information that you didn't have before because previously you couldn't know if the tickets that you were showing were the current ones or leftovers from the previous page.
Another benefit is that you don't need to repeat API calls if the user is going back and forth between pages. You can check if you already have data for that page before fetching.
import React from "react";
interface Ticket {
// actual properties
}
interface State {
tickets: Record<number, Ticket[] | undefined>;
pageNum: number;
}
class MyComponent extends React.Component<{}, State> {
state: State = {
tickets: {},
pageNum: 1,
}
handlePage = async (direction: number) => {
const page = this.state.pageNum + direction;
// can skip the API call if already loaded!
if (this.state.tickets[page] !== undefined) {
return;
}
const tickets: Ticket[] = await api.getTickets(`?page=${page}`);
// we will use the prevState when saving the tickets
this.setState(prevState => ({
tickets: {
...prevState.tickets,
[page]: tickets
}
}));
}
render() {
// get the tickets for the current page
const tickets = this.state.tickets[this.state.pageNum] ?? [];
// might also want to distinguish between an empty result and an incomplete fetch
const isLoading = this.state.tickets[this.state.pageNum] === undefined;
return <div/>
}
}
Upvotes: 1
Reputation: 1032
setState does only that job sets the state (and triggers a re render :P); I think you can change your function to this:
handlePage = async (direction: number) => {
const tickets: Ticket[] = await api.getTickets(`?page=${this.state.pageNum + direction}`);
this.setState({
pageNum: this.state.pageNum + direction,
tickets
})
}
Upvotes: 0