1

I'm trying to figure out how to display & populate a table with data after the AJAX call has taken place and the data has been retrieved.

The user starts off at AppointmentHome, which has a datepicker, where they can pick the day of appointments they want to retrieve. Once they choose the date and click on Search, getAppointmentData() is called, and the data is retrieved.

Currently, I'm retrieving the data into the var appointmentArray in the callback for getAppointmentData(), and then call CreateTableRows() in the callback, so that it gets called after the data has been retrieved and not before.

Now, I'm having a difficult time figuring out how to get the output of CreateTableRows() in the render of AppointmentList so that it gets populated along with the rest of the table. I believe I may need to move some code into some lifecycle methods, but am stuck on how to move from one component to the next without messing up retrieving the data.

var appointmentArray = null;
var table;

function getAppointmentData(){
  window.getAppointments(date, 'callback');
}

window.callback = function(objects){
  appointmentArray = objects;
  table = createTableRows();
}

function createTableRows(){
  var output = "<tbody>";
  for (var i = 0; i < 1; i++){
    output += "<tr><th scope='row'>"+appointmentArray[i]+ "</th></tr>";
  }
  output += "</tbody>";
  return output;
}


export class AppointmentList extends React.Component {
  constructor(props){
    super(props);
  }

  render(){
    return(
      <div className = "appointment-table">
        <table className="table table-hover">
          <thead>
            <tr>
              <th scope="col">Patient Name</th>
              <th scope="col">Time</th>
              <th scope="col">Duration</th>
              <th scope="col">Provider</th>
              <th scope="col">Appointment Status</th>
            </tr>
          </thead>
        </table>
      </div>
    );
  }
}

export class AppointmentHome extends React.Component {
  constructor(props){
    super(props);

    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(){
    getAppointmentData();
  }

  render(){
    return(
      <div>
        <h3> Appointment Search </h3>
        <div className = 'search'>
          <h3> Date </h3>
          <Calendar />
          <div className = 'searchButton'>
            <Link to="appointments">
                <RaisedButton label="Search" onClick = {this.handleClick}/>
            </Link>
          </div>
        </div>
      </div>
    );
  }
}
fa01
  • 13
  • 3

1 Answers1

1

The lifecycle method recommended by Facebook is componentDidMount. Quoting from the official lifecycle documentation:

componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.

Note that loading the data after mounting means that in your render method, you'll have to take care of the case that your data has not been loaded yet. Usually, this is done with a conditional statement that renders a spinner or something like that when there is no data, and the actual data when it is available.

Appointments Example

While this (hopefully) answers your question about which lifecycle method to use, it actually is not relevant in your case, since you are loading the data after the user clicks on a button. In this case, you would do it in the click handler method and provide a callback function that sets your component's state.

For your example, I would recommend adding a state to the AppointmentHome component that adds the appointment data to its state after it has been loaded, and provides that data as props to the AppointmentList component.

Like this:

class AppointmentHome extends React.Component {
  constructor(props) {
    super(props);

    this.handleClick = this.handleClick.bind(this);
    this.state = {
      appointments: null,
      isLoading: false
    };
  }

  handleClick() {
    this.setState({ appointments: null, isLoading: true });
    getAppointmentData(appointments =>
      this.setState({ appointments, isLoading: false })
    );
  }

  render() {
    const { appointments, isLoading } = this.state;
    return (
      <div>
        <div className="search">
          <button onClick={this.handleClick}>Search</button>
        </div>
        {isLoading && <div>Please wait...</div>}
        {appointments && <AppointmentList appointments={appointments} />}
      </div>
    );
  }
}

Some notes:

  • I've left out the layout components (MaterialUI?) and react-router stuff from your code for brevity's sake
  • While the data is being loaded, the component's state gets an isLoading flag which I use to show a “please wait” message
  • The AppointmentList is only rendered if there are appointments in the state – that's what the appointments && does

Appointment List

The AppointmentList could look something like this:

class AppointmentList extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const { appointments } = this.props;
    return (
      <div className="appointment-table">
        <table className="table table-hover">
          <thead>
            <tr>
              <th scope="col">Patient Name</th>
              <th scope="col">Time</th>
              <th scope="col">Duration</th>
              <th scope="col">Provider</th>
              <th scope="col">Appointment Status</th>
            </tr>
          </thead>
          <tbody>
            {appointments.map((appointment, i) => (
              <tr key={`appointment${i}`}>
                <td scope="col">{appointment.patientName}</td>
                <td scope="col">{appointment.time}</td>
                <td scope="col">{appointment.duration}</td>
                <td scope="col">{appointment.provider}</td>
                <td scope="col">{appointment.appointmentStatus}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
}

As you see, there is no need to concatenate any HTML code into a string as you are trying to do in your code, it's all handled elegantly by the component:

  • The appointments array is a prop of the component
  • The array is mapped to create a table row for each appointment

Loading JSONP Data

You are loading the data from your API using JSONP, presumably to circumvent the cross origin resource sharing restriction.

That's fine, but you need to be able to pass an arbitrary callback function to the function that does the JSONP magic. If you do it with global variables the way you do it in your code example, it won't work, because the AppointmentApp is hard to wire up with that.

You can use this solution suggested in another StackOverflow question:

function jsonp(url, callback) {
    var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
    window[callbackName] = function(data) {
        delete window[callbackName];
        document.body.removeChild(script);
        callback(data);
    };

    var script = document.createElement('script');
    script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName;
    document.body.appendChild(script);
}

Putting It All Together

Take a look at this code sandbox that I've created which shows the code in action:

https://codesandbox.io/s/j3j31qvq2v

Patrick Hund
  • 19,163
  • 11
  • 66
  • 95
  • Would componentDidMount() be called in AppointmentHome or AppointmentList? If you could provide an example, that would be great! – fa01 Jan 04 '18 at 20:00
  • The actual tricky part about your question is that you're using a global callback, presumably because the API you're calling is using JSONP, correct? – Patrick Hund Jan 04 '18 at 20:11
  • So, was my answer helpful? If yes, please accept it – Patrick Hund Jan 06 '18 at 18:38