0

I'm prototyping a UI and fetching data from an API like this in React:

  componentWillUpdate() {
    fetch(this.state.api + this.state.query  +".json" + this.state.key)
      .then(response => {
        if (response.ok) {
          return response.json();
        } else {
          throw new Error('Oops')
        }
      })
      .then(data => this.setState({ results: data.results, isLoading: false}))
      .catch(error => this.setState({ error, isLoading: false }))
  }

There's a list of other possible categories that I'd like to give users access to. Given a link like this:

<li data-facet="arts" onClick={this.handleCategory}>Arts</li>

and a handler method like this:

handleCategory(event) {
    this.setState({
      query: event.target.getAttribute('data-facet')
    })
  }

how do I get the UI to update and show data from the new category? I've been banging my head against this for a couple of days now. I've written several ways to update the fetch string successfully, but none are forcing the component to re-render and show new items. Thanks.


Updating with complete component code:

import React, { Component } from 'react'
import Moment from 'moment'
import MaterialIcon from 'material-icons-react'
import '../assets/styles/app.scss'

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
      error: null,
      isLoading: false,
      isMenuOpen: false,
      results: [],
      api: "the//api//here",
      query: "",
      key: "?api-key=XXXXXX"
    };

    this.handleMenu = this.handleMenu.bind(this)
    this.handleCategory = this.handleCategory.bind(this)
  }

  handleMenu() {
    this.setState({
      isMenuOpen: !state.isMenuOpen
    })
  }

  handleCategory(event) {
    this.setState({
      query: event.target.getAttribute('data-facet')
    })
  }

  componentDidMount() {
    fetch(this.state.api + this.state.query  +".json" + this.state.key)
      .then(response => {
        if (response.ok) {
          return response.json();
        } else {
          throw new Error('Oops')
        }
      })
      .then(data => this.setState({ results: data.results, isLoading: false}))
      .catch(error => this.setState({ error, isLoading: false }))
  }

  render() {
    const { results, isLoading, error } = this.state

    if (error) {
      return <div className="cards">{error.message}</div>
    }

    if (isLoading) {
      return <div className="cards">Loading...</div>;
    }

    return (
      <div className="wrapper">

        <div className="menu-trigger"><MaterialIcon icon="menu"/></div>
        <div className="menu">
          <ul>
            <li data-facet="arts" onClick={this.handleCategory}>Arts</li>
            <li data-facet="automobiles" onClick={this.handleCategory}>Automobiles</li>
            <li data-facet="books" onClick={this.handleCategory}>Books</li>
            <li data-facet="business" onClick={this.handleCategory}>Business</li>
            <li data-facet="fashion" onClick={this.handleCategory}>Fashion</li>
            <li data-facet="food" onClick={this.handleCategory}>Food</li>
            <li data-facet="health" onClick={this.handleCategory}>Health</li>
            <li data-facet="home" onClick={this.handleCategory}>Home</li>
          </ul>
        </div>

        <div className="cards">
          {results.slice(0, 10).map(result => (
            <div className="cards__card" key={result.title}>
              <a href={result.url} target="_blank"><h5>{result.title}</h5></a>
              <p>{result.abstract}</p>
              <p className="small">Published {Moment(result.published_date).format('MMMM Do YYYY, h:mm a')}</p>
            </div>
          ))}
        </div>
      </div>
    );
  }

}

export default App
7
  • can you tell me why are you calling api in componentWillUpdate instead of componentDidMount ? also share complete code so that i could check. Commented Oct 31, 2019 at 3:21
  • I've updated the question include the full code. I had the fetch in componentWillUpdate() just because I was messing around. It had been in componentDidMount and was working. Commented Oct 31, 2019 at 3:29
  • your performance won't be good as you are using multiple same click function in li tag. i think you should loop that and handle through id/value. Commented Oct 31, 2019 at 3:35
  • can you share in sandbox so that i could improve. Commented Oct 31, 2019 at 3:36
  • Gladly! How would I do that? You mean make a Codepen or something? Commented Oct 31, 2019 at 3:38

1 Answer 1

1

In order for your component to re-render with the updated data you need to update the state variable.

Assuming you're storing your data in this.state.results - However, you're not updating this state in handleCategory.

There are a few things you can do:

  • abstract your fetch call so it's reusable
  • use arrow functions to get rid of binds in constructor
  • get rid of componentWillUpdate lifecycle as it's redundant
class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isLoading: false,
      results: [],
      error: null,
    };
  }

  fetchData = (url, query, key) => {
    fetch(`the/api/${url}${query}.json?api-key${key}`)
      .then(res => res.ok && res.json())
      .then(data => this.setState({ results: data.results, isLoading: false }))
      .catch(error => this.setState({ error, isLoading: false }));
  }

  componentDidMount() {
    fetchData('api_url', 'all', 'key_here');
  }

  handleCategory = (e) => {
    const cat = e.target.getAttribute('data-facet');
    fetchData('api_url', cat, 'key_here');
  }

  render() {
    const { isLoading, results, error } = this.state;

    if (isLoading) {
      return <div className="loading-icon"></div>
    }

    if (error) {
      return <div className="error"></div>    
    }

    return (
      <div className="wrapper">
        <button data-facet="arts" onClick={this.handleCategory}>Arts</button>
        {results.map(res => ...)}
      </div>
    );
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This is great, thanks. To clarify, in the componentDidMount phase, should I be hardcoding the api_url, cat, and key as defaults for the first load?
Sure. You can fallback to defaults so in cDM you don't have to call with params. ES6 has default params so you can just do fetchData = (url = 'foo', query = 'all', key = '0') => ...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.