Using a React with JSON data from AJAX

This tutorial shows how to utilize JSON data obtained via AJAX on initial load, and how to pass that data to components.

react,ajax,axios,json

01/12/2017

I recently developed a react app and wanted to organize the data in a central place for easy updating. I initially used a javascript object that I referenced throughout my code, but wanted to be able to access it via AJAX, as I was planning to make the data editable via some sort of CMS by people who might not be able to just open the code and make changes.

Ordinarily, without React, I might've just thrown in jQuery for an easy AJAX call, but jQuery doesn't work well with AJAX, so I looked to a library called Axios, which handles HTTP requests using promises.

Here's a basic request using Axios:

axios.get('/[data_url]')
.then(function (response) {
 //pass the data on to the app
})
.catch(function (error) {
console.log(error);
});

Easy! So normally you would just load the data and then render your app. But React expects some initial state for all that data, and needs to update itself once that AJAX request is complete. For this reason, you need to store all that data in the app's state. Here's how that looks:

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {appData: {}};
  }

  componentDidMount() {
    axios.get('/data/appData.json')
    .then((result)=> {
      const thisData = result.data;
      this.setState({
        appData: result.data
      });
    })
  }

  componentWillUnmount() {
    this.serverRequest.abort();
  }

  render() {

    const theData = this.state.appData;
    if (Object.keys(theData).length > 0 && theData.constructor === Object){ //if the object is not empty
      return (
        <div className="App">
          {theData.dataPoint}
        </div>
      );
    } else {
      return (
        <div className="App"></div>
      )
    }

  }
}

Note that we're running a check here to make sure that The appData object is not empty (if (Object.keys(theData).length > 0...). When react first loads, it won't have the data, so any attempt to reference it (e.g. {theData.dataPoint} will throw an error if we are not checking for this.

Now, in a real-world application, you wouldn't just inline all that data in the App component. You'd want subcomponents to pass that data down to. Here's what that looks like:

if (Object.keys(theData).length > 0 && theData.constructor === Object){ //if the object is not empty
  return (
    <div className="App">
      <AppHeader headerData={theData.headerData} />
      <MainCarousel carouselData={theData.carouselData} />
    </div>
  );
}

function AppHeader(props) { //use the data in a function component
  return (
    <div className="App-header">
      <h2 className="App-title">{props.headerData.title}</h2>
    </div>
  );
}

class MainCarousel extends Component { //use the data in a class component

  constructor(props) {
    super(props);
  }

  render() {
    const carouselItems = this.props.carouselData.carouselItems.map((carouselItem) =>
      <AppCarouselItem key={carouselItem.id} image={carouselItem.image} />
    );
    return (
      <div className="App-carousel">
        <Carousel>
          {carouselItems}
        </Carousel>
      </div>
    );
  }
}

Here we have two subcomponents. One is written as a function (AppHeader), and the other as a class (MainCarousel). They receive the data slightly different, in that the function can just take the data as a props parameter, and the class needs to receive the data as props in the constructor. Also note that, while we could just pass all the data down to each component, it's best to pass only the data your component will need, so there's less payload each time the app's state needs to be refreshed.