Before you go, check out these stories!

0
Hackernoon logoA Custom, Declarative AJAX Loader With React by@Bassaganas

A Custom, Declarative AJAX Loader With React

Author profile picture

@BassaganasJordi Bassagañas

Hi there! How are you today? I blog about technology, the Internet, SEO, programming tips, and more.

Personally I am not much of a fan of installing too many libraries in my applications with no good reason behind, especially those ones intended to perform minor common tasks in web development such as an AJAX loader or spinner.

But possibly this is a good thing to do in terms of security.

External dependencies may come in turn with additional packages, and so on, meaning the vulnerability surface of your application might probably degrade because of that habit.

In fact, today's tip is to do with controlling complexity.

The principle of least privilege states in general terms that a subject should be provided with certain capabilities only if requiring them.

So, why would you

npm install
a new package when it turns out that it is a piece of cake to just write your own AJAX loading component with React?

components/Loading.js

import loading from '../../images/loading.gif';
import React, { Component } from 'react';

class Loading extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    if (this.props.loading) {
      return (
        <div className="text-center">
          <img src={loading} alt="loading" />
        </div>
      );
    }
    return this.props.children;
  }
}

export default Loading;

Now, let's see how our React Loading component is used in a declarative way. Creating a new record, a restaurant for example, is quite a common use case for making an AJAX request.

components/private/restaurant/Create.js

import ApiRestaurantActions from '../../../actions/api/RestaurantActions';
import { Button, ButtonGroup, Form, FormGroup, Jumbotron } from 'reactstrap';
import { connect } from 'react-redux';
import { FormGroups } from './FormGroups';
import Loading from '../../Loading';
import React from 'react';
import RestaurantState from '../../../states/RestaurantState';
import Validation from '../../Validation';

const initialState = Object.assign({}, RestaurantState.initial(), { response: [] });

class RestaurantCreate extends React.Component {
  constructor(props) {
    super(props);
    this.state = initialState;
    this.handleChange = this.handleChange.bind(this);
    this.handleClickCancel = this.handleClickCancel.bind(this);
    this.handleSubmitForm = this.handleSubmitForm.bind(this);
  }

  handleChange = e => {
    let record = {...this.state.record};
    record[e.target.id] = e.target.value;
    this.setState({record});
  }

  handleClickCancel(e) {
    this.setState({
      ...initialState,
      response: []
    });
    e.preventDefault();
  }

  handleSubmitForm(e) {
    e.preventDefault();
    this.props.create(this.state.record)
      .then((res) => {
        switch (res.status) {
          case 201:
            this.props.fetchAll();
            this.setState({
              ...initialState,
              response: []
            });
            break;
          case 422:
            this.setState({ response: res.errors });
            break;
        }
      });
  }

  render() {
    return (
      <Jumbotron className="mt-3">
        <Form className="form" onSubmit={ (e) => this.handleSubmitForm(e) }>
          <FormGroups {...this.state.record} handleChange={this.handleChange} />
          <FormGroup>
            <ButtonGroup>
              <Button color="primary">Add restaurant</Button>
              <Button color="secondary" onClick={ (e) => this.handleClickCancel(e) }>Cancel</Button>
            </ButtonGroup>
          </FormGroup>
        </Form>
        <Loading loading={this.props.loading}>
          <Validation messages={this.state.response} />
        </Loading>
      </Jumbotron>
    );
  }
}

const mapStateToProps = state => {
  return state.ApiRestaurantCreateReducer;
};

const mapDispatchToProps = dispatch => {
  return {
    create: (record) => dispatch(ApiRestaurantActions.create(record)),
    fetchAll: () => dispatch(ApiRestaurantActions.fetchAll())
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(RestaurantCreate);

This is a particular implementation with Redux dealing with validation and error handling, but you get the point:

<Loading loading={this.props.loading}>
  <Validation messages={this.state.response} />
</Loading>

An AJAX loader GIF is displayed while waiting for the server's validation results.

Figure 1. AJAX request made when creating a record

By the way, the validation component is also a custom one aimed to be used declaratively.

import React, { Component } from 'react';

class Validation extends Component {
  render() {
    return (
      <ul className="text-danger">
        {
          this.props.messages.map(function(item, index) {
            return (<li key={index}>{item}</li>)
          })
        }
      </ul>
    );
  }
}

export default Validation;

For further details on this implementation please visit programarivm/warthog which is a real-worldish React app where I am currently playing around with Redux and Thunk.

I hope you liked this idea on writing your own declarative AJAX loading component!

Now I'd love to hear from you. What is your opinion about implementing least privilege concepts when writing apps? Please leave a comment below and let me know.

Thank you so much for reading.

Author profile picture

@BassaganasJordi Bassagañas

Read my stories

Hi there! How are you today? I blog about technology, the Internet, SEO, programming tips, and more.

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!