Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Inferno JS: Build and Authenticate an App - Part II

DZone's Guide to

Inferno JS: Build and Authenticate an App - Part II

In Part II of this series, we are going to create a loading component, list component, and a detail component!

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

Did you miss Part I? Catch up here.

Create a Loading Component

In our App.js file, we're simply showing <p>Loading...</p> when there isn't any dinosaur data. However, this should be an error instead if the API isn't available. You may have noticed that we put error in the state but didn't use it in render() yet. Let's create a small component that conditionally shows a loading image or an error message.

Create a new folder: src/components. All components from now on will go into this folder. Next create a subfolder called Loading to contain our new component's files.

Let's add a loading image. You can grab the raptor-running.gif from GitHub here. Download the image into the src/components/Loading directory.

Create an empty file for the loading CSS: Loading.css.

Loading Component JS

Next, add a new JS file called Loading.js:

// src/components/Loading/Loading.js

import Inferno from 'inferno';
import Component from 'inferno-component';
import loading from './raptor-loading.gif';
import './Loading.css';

class Loading extends Component {
  render(props) {
    return(
      <div className="Loading"> { !props.error ? ( <img className="Loading-img" src={loading} alt="Loading..." /> ) : ( <p className="alert alert-danger"><strong>Error:</strong> Could not retrieve data.</p> ) } </div> ); } } export default Loading; 

Let's import the loading image and CSS. Whenever we use our Loading component, we're going to pass the parent component's error state as a property. We can then pass props to our render() function to access properties without needing this. We'll render the loading image if there's no error, and show an alert if there is.

Loading Component CSS

Add the following CSS to Loading.css:

/* src/components/Loading/Loading.css */

.Loading-img {
  display: block;
  margin: 10px auto;
}

Let's verify that our src file structure looks like this:

public
server
src
  |-components |-Loading
      |-Loading.css |-Loading.js
      |-running-raptor.gif |-utils
    |-ApiService.js |-App.css
  |-App.js |-App.test.js
  |-index.css |-index.js

Add Loading Component to App

Now we can use this component in our App.js and replace the <p>Loading...</p> element we added earlier:

// src/App.js

...
import Loading from './components/Loading/Loading';

...
  render(props, state) {
    ...
            {
              state.dinos ? (
                ...
              ) : (
                <Loading error={state.error} /> ) } 

Now while data is being fetched from the API, the running dinosaur gif is shown. If we stop the API server and reload the app, we'll see our error message instead of an infinite loading state:

Inferno app with API

Create a List Component

Let's explore creating a component that displays the list of dinosaurs and lets users select one to show additional details. This will replace the simple unordered list that we put in the App.js file.

Create a new folder containing CSS and JS files for the DinoList component:

src
  |-components |-DinoList
      |-DinoList.css |-DinoList.js

List Component JS

Add the following code to DinoList.js:

// src/components/DinoList/DinoList.js

import Inferno, { linkEvent } from 'inferno';
import Component from 'inferno-component';
import ApiService from './../../utils/ApiService';
import Loading from './../Loading/Loading';
import './DinoList.css';

/* This function is pulled out of the class to demonstrate how we could easily use third-party APIs */
function getDinoById(obj) {
  const id = obj.id;
  const instance = obj.instance;

  // Set loading state to true while data is being fetched
  // Set active state to index of clicked item
  instance.setState({
    loading: true,
    active: id
  });

  // GET dino by ID
  // On resolve, set detail state and turn off loading
  ApiService.getDino(id)
    .then(
      res => {
        instance.setState({
          detail: res,
          loading: false,
          error: false
        });
      },
      error => {
        instance.setState({
          error: error,
          loading: false
        });
      }
    );
}

class DinoList extends Component {
  constructor() {
    super();

    // Set default loading state to false
    this.state = {
      loading: false
    };
  }

  render(props, state) {
    return(
      <div className="DinoList"> <div className="col-sm-3"> <ul className="DinoList-list"> { props.dinos.map((dino) => ( <li key={dino.id}> <a className={state.active === dino.id ? 'active' : ''} onClick={linkEvent({id: dino.id, instance: this}, getDinoById)}> {dino.name} </a> </li> )) } </ul> </div> <div className="col-sm-9"> { !state.loading && !state.error && state.detail ? ( <p>{state.detail.name}</p> ) : ( <Loading error={state.error} /> ) } </div> </div> ); } } export default DinoList; 

We'll import linkEvent from inferno. linkEvent() is an excellent helper function unique to Inferno. It allows attachment of data to events without needing bind(this), arrow functions, or closures.

The render() function will use the props and state parameters. We'll pass the dinosaurs array to our DinoList component from App.js as a property.

The getDinoById(obj) function at the top of the file is the event handler for when a user clicks a dinosaur's name to get dino details. This is not a method on the DinoList class. The function is pulled out to demonstrate how components can easily leverage methods from third-party APIs with linkEvent. The obj parameter comes from the linkEvent() in render() like so:

<a
  className={state.active === dino.id ? 'active' : ''}
  onClick={linkEvent({id: dino.id, instance: this}, getDinoById)}>
  {dino.name}
</a> 

linkEvent can pass data (as well as the event) to a handler. We're using it here to pass the clicked dinosaur's id to call the API and apply a class to the active dinosaur in the list. We're also passing the instance (this) so we can use instance.setState() in our getDinoById() function without context errors or binding.

List Component CSS

Next, add the following to the DinoList.css file to style the list of dinosaurs:

/* src/components/DinoList/DinoList.css */

.DinoList a {
  cursor: pointer;
}
.DinoList a.active {
  font-weight: bold;
  text-decoration: underline;
}

Add List Component to App

In the App.js file, let's replace our unordered list with the new DinoList component:

// src/App.js

...
import Loading from './components/DinoList/DinoList';

...
  render(props, state) {
    ...
            {
              state.dinos ? (
                <DinoList dinos={state.dinos} /> ) : ( ... ) } 

At this point, when a dinosaur in this list is clicked, the only "detail" we're showing is the dinosaur's name. Also, because we don't make an API call automatically on load, the UI will show the loading image in the details area until the user clicks on a dinosaur in the list. Clearly this isn't ideal. We'll create a DinoDetail component next to display this in a much nicer way.

Create a Detail Component

Let's make a new folder for our DinoDetail component: src/components/DinoDetail. We'll only use Bootstrap to style this component, so a CSS file won't be necessary.

Detail Component JS

Let's build the DinoDetail.js:

// src/components/DinoDetail/DinoDetail.js

import Inferno from 'inferno';
import Component from 'inferno-component';

class DinoDetail extends Component {
  render(props) {
    let dino = props.dino;

    return(
      <div className="DinoList">
        {
          dino ? (
            <div className="list-group">
              <div className="list-group-item list-group-item-info">
                <h3 className="list-group-item-heading text-center">{dino.name}</h3>
              </div>
              <div className="list-group-item">
                <h4 className="list-group-item-heading">Pronunciation</h4>
                <p className="list-group-item-text">{dino.pronunciation}</p>
              </div>
              <div className="list-group-item">
                <h4 className="list-group-item-heading">Meaning of Name</h4>
                <p className="list-group-item-text">"{dino.meaningOfName}"</p>
              </div>
              <div className="list-group-item">
                <h4 className="list-group-item-heading">Period</h4>
                <p className="list-group-item-text">
                  {dino.period} ({dino.mya} million years ago)
                </p>
              </div>
              <div className="list-group-item">
                <h4 className="list-group-item-heading">Diet</h4>
                <p className="list-group-item-text">{dino.diet}</p>
              </div>
              <div className="list-group-item">
                <h4 className="list-group-item-heading">Length</h4>
                <p className="list-group-item-text">{dino.length}</p>
              </div>
              <div className="list-group-item">
                <p
                  className="list-group-item-text lead"
                  dangerouslySetInnerHTML={{__html: dino.info}}></p>
              </div>
            </div>
          ) : (
            <p className="lead">
              <em>Select a dinosaur to see details.</em>
            </p>
          )
        }
      </div>
    );
  }
}

export default DinoDetail;

Despite the large amount of JSX, this is a very simple component. All it does is take a dino property and display data. If there is no dino available, it shows a message that instructs the user to select a dinosaur.

Note: The API returns HTML in some dinosaurs' info properties. We render this using dangerouslySetInnerHTML. You can read more about this in the DOM Elements section of the React docs.

Add Detail Component to List Component

Now we'll replace the detail dinosaur name in the DinoList component with our new DinoDetail component:

// src/components/DinoList/DinoList.js

...
import DinoDetail from './../DinoDetail/DinoDetail';

...
  render(props, state) {
          ...
          {
            !state.loading && !state.error ? (
              <DinoDetail dino={state.detail} /> ) : ( ... ) } 

Note that we've also changed the expression to !state.loading && !state.error. We no longer want to check for state.detail here because we still want to display the DinoDetail component even if there is no detail information available yet. We only added this in the previous step to avoid errors.

When no dinosaur is selected, our app now looks like this:

Inferno app displaying list of dinosaurs from API with no details

When a dinosaur is clicked, its details are fetched from the API and displayed. The selected dinosaur receives an .active class in the list, which we styled as bold and underlined in the DinoList CSS previously:

Inferno app displaying list of dinosaurs from API with details

Stay tuned for the final part in this series.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
inferno ,javascript ,web dev ,components

Published at DZone with permission of Kim Maida, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}