Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

112
Views
how to properly handle input dependent async data calls in react component?

I am currently building some apps using Typescript and React. To this date i have used some smelly workarounds for the following situation which i would like to get rid of. Maybe you know a better way of doing such.

The Setup: Having a React Component which sould display data which is fetched from server on user input. Example use case: fetch the name of a city from an API based on a postal code entered by user. Example implementation:

import * as React from 'react';

export interface IXmplState {
  plc: string;
  name: string;
}

export default class Xmpl extends React.Component<{}, IXmplState> {

  constructor(props){
    super(props);
    this.state = {name: "", plc: ""};
  }

  private fetchName(plc: string): Promise<string> {
    //Fetch data from server.
  }

  private updateName(plc: string): void {
    this.fetchName(plc).then(newName => this.setState({name: newName}));
  }

  public render(): React.ReactElement<{}> {
    return(
      <div>
        <input value={this.state.plc} onChange={(event) => this.updateName(event.target.value)}/>
        <div>{this.state.name}</div>
      </div>
    );
  }

}

The problem: As soon as the user input changes, the updateName() is called, which then updates the state on resolved promise. Consider the following case:

  1. The input is changed and a an update promise (A) is created and now pending.
  2. The input is changed again and a new update promise (B) is created.
  3. Promise B is resolved while promise A is still pending.
  4. The state will now be changed to the result of promise B.
  5. Promise A is now resolved and will override the result of Promise B in the state.
  6. The user is now presented with the wrong name (schould be result of B but is result of A)

Are there ways to surpress such behavior? Are there specific ways/libs for doing this in React, typescript or javascript? Or is such kind of input handling generally to be avoided? Which would be a better way or the best way of handling such scenario in gernerally?

Greets and Thanks

EDIT: for the sake of completeness.

My current way of handling such scenarios is introducing a checksum in the component and only update the state if the checksum is still not altered.

export default class Xmpl extends React.Component<{}, IXmplState> {
  let nameCkSm: number = 0;
  ...
  private updateName(plc: string): void {
    let ckSm = ++this.nameCkSm;
    this.fetchName(plc).then(newName => this.setState(() => {
      if(this.nameCkSm === ckSm) return {name: newName};
    }));
  }
almost 4 years ago · Santiago Trujillo
2 answers
Answer question

0

AbortController (standard web feature, not a lib) is good for this, see *** comments:

export default class Xmpl extends React.Component<{}, IXmplState> {
    // *** An AbortController for the update
    pendingNameController: AbortController | null = null;

    constructor(props: IXmplState) {
        super(props);
        this.state = { name: "", plc: "" };
    }

    // *** Accept the signal
    private fetchName(plc: string, signal?: AbortSignal): Promise<string> {
        // Fetch data from server, pass `signal` if the mechanism supports
        // it (`fetch` and `axios` do, for instance)
    }

    private updateName(plc: string): void {
        // *** Cancel any outstanding call
        this.pendingNameController?.abort();
        // *** Get a controller for this call, and its signal
        this.pendingNameController = new AbortController();
        const { signal } = this.pendingNameController;
        // *** Pass the signal to the `fetchName` method
        this.fetchName(plc, signal)
        .then((name) => {
            // *** Don't update if the request was cancelled (ideally you'd
            // never get here because a cancelled request won't fulfill the
            // promise, but race conditions can mean you would)
            if (!signal.aborted) {
                this.setState({ name });
            }
        })
        .catch((error) => {
            // ...handle.report error...
        })
    }

    public render(): React.ReactElement<{}> {
        // ...
    }
}

You can write a utility that handles multiple outstanding requests for different things by wrapping the fetching method, etc. But that's the basic mechanism for using AbortController for this.

almost 4 years ago · Santiago Trujillo Report

0

Your biggest enemy here is the network request and the fact that some take longer than others - resulting in race conditions in which things are happening out of order.

The most important thing you need to do is to abort the previous request and let the "latest" one take precedence. This is done with the help of an AbortController.

I know you probably won't like this, but I highly advise you move to functional components and hooks. The reason I say this is because it's much easier to organize your code for the different things which are going on, which will make it easier to prevent the race condition. In particular, the useEffect hook has a mechanism for cancelling the previous effect... in this case your API request. Here's what it would look like (not tested, but it should be very close):

// This is a hook whose only purpose is to make an http request any time the searchQuery changes.
// It takes care of cancelling the previous request
const useSearchRequest = (searchQuery) => {
    const [state, setState] = useState({ isLoading: false, error: null, result: null });

    useEffect(() => {
        if (searchQuery) {
            // update the state to show we are loading
            setState(prevState => ({ ...prevState, isLoading: true }));
    
            try {
                // create the abort controller and pass the signal to the request
                const controller = new AbortController();
                fetch(`/search?query=${searchQuery}`, { signal: controller.signal })
                .then(req => req.json())
                .then(result => {
                    // update the state with the results
                    setState({ isLoading: false, error: null, result });
                });
            } catch(error) {
                // update the state with the error
                setState(prevState => ({ ...prevState, isLoading: false, error }));
            }
    
            // any time searchQuery changes, this method will be called and will cancel the previous request
            return () => controller.abort();
        }
    // this tells the effect to rerun every time searchQuery changes
    }, [searchQuery]);

    return state;
}

const Xmpl = () => {
    const [searchVal, setSearchVal] = useState('');
    const { isLoading, error, result } = useSearchRequest(searchVal);

    return(
        <div>
          {isLoading ? 'loading...' : null}
          {error ? `There was an error: ${err}` : null}
          <input value={searchVal} onChange={(event) => setSearchVal(event.target.value)}/>
          <div>{result}</div>
        </div>
    );
}
almost 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!