0

I'm working on a React game search app that pulls API data from a third-party source. When the user searches an input that can't be found, the fetch just hangs there without stopping. Basically what I'm stuck on figuring out how to cancel the fetch request onces a setTimeout if finished after say, 10 seconds. If no response is received by then I want it to be canceled and render an error message. Thanks for your help in advance!

class Search extends Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      games: [],
      error: false,
      loading: false
    }
  }

  updateInput = (event) => {
    this.setState({
      title: event.target.value
    });
  }

  handleGames = (search) => {
    const proxyUrl = `https://cors-anywhere.herokuapp.com/`;
    const key = `8cd10a7136710c1003c8e216d85941ace5a1f00e`;
    const endpoint = `https://www.giantbomb.com/api/search/?api_key=`;
    const url = `${proxyUrl}${endpoint}${key}&format=json&resources=game&query=${search}&limit=30`;

    this.setState({ loading: true }, () => {

      fetch(url)
        .then(res => res.json())
        .then(data => {
          const response = data.results;

          response.forEach(game => {
            this.setState(prevState => ({
              games: prevState.games.concat(game),
              loading: false
            }))
          });
        }).catch(error => {
          console.log('Request failed', error);
        });

      this.setState({
        games: []
      })
    })

  }

  handleSubmit = (e) => {
    const { title } = this.state;
    e.preventDefault();

    if (!title) {
      this.setState({ error: true })
    } else {
      this.setState({ error: false })
      this.handleGames(title);
    }
  }

  render() {
    const { games, error, loading } = this.state;
    return (
      <div className="App">
        <div className="search-bar">
          <form>
            <input
              className="input-field"
              type="text"
              placeholder="Search Game"
              onChange={this.updateInput}
            />
            <button
              className="search-button"
              onClick={this.handleSubmit}
            >Search</button>
          </form>
          <span className="error">{error ? "You kind of need to type something first, genius." : ""}</span>
        </div>
        <div className="games-container">
          {loading ? (
            <div className="loading-div">
              <i className="fa fa-3x fa-spinner fa-spin" />
              <p className="loading">Loading....</p>
            </div>
          ) : (
              games.map(game => {
                return <Game
                  key={game.id}
                  game={game}
                  icon={game.image.icon_url}
                  gameTitle={game.name}
                />
              })
            )

          }
        </div>
      </div>

    );
  }
}
envincebal
  • 205
  • 1
  • 2
  • 12
  • 3
    `fetch` isn't cancellable. This is one of reasons to not use it. You can reject a promise on timeout but a request itself won't be aborted. – Estus Flask Nov 17 '18 at 09:49

1 Answers1

2

Instead of directly using fetch you can nest it inside a Promise. You will find a lot of implementation doing a search. That's one I used.

const advFetch = (url, ...) => {
  const TIMEOUT = 10000;
  let didTimeOut = false;

  return new Promise(function(resolve, reject) {
    const timeout = setTimeout(() => {
      didTimeOut = true;
      reject(new Error('Request timed out'));
    }, TIMEOUT);

    fetch(url, ...).then(function(response) {
      clearTimeout(timeout);
      if (!didTimeOut) {
        resolve(response);
      }
    })
    .catch(function(err) {
      if (didTimeOut) {
        return;
      }
      reject(err);
    });
  })
}

Note: the fetch isn't really cancelled, this is not possible (not true anymore, see below!), it will continue until network timeout is reached, but it will be ignored by your application.

2022 update

On modern browsers it's finally possible to also cancel a fetch/promise by using an AbortController.

keul
  • 7,673
  • 20
  • 45
  • Thanks a lot. I learned something new. My 2 cents: one can wrap `fetch` inside a timeout to simulate throttling, but remember to clear both timeouts in the `finally` clause, so that the other is not run. – sanjarcode Jul 18 '22 at 16:42