Learn React by creating a comment app

If you are coming from VueJs background learning React can be challenging at first but If you look closer its not, I would say its easier than VueJs. React is a library developed by Facebook and has a small API. But it doesn’t mean its not powerful, it has a very big community and currently ranks #1 in user interface building libraries pack. It has lots of plugins to extend it capabilities. In this post lets get started with react by creating a comment app.

Source Code Demo

What is React

React is a library to build fast & interactive user interfaces. It was built by Facebook in 2011 and it has been used by facebook and many big players. At the heart of react is Component system with a VirtualDOM which make it super fast.

Component

Components are isolated reusable pieces of UI elements, they are like JavaScript functions, which accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen. Here is a simple functional component.

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

JSX

As you can see above component has some funky syntax. Its looks like JavaScript mixed with HTML, Its called JSX (JavaScript XML) which gives us easy way to write HTML markup inside javascript file, later it will be transpiled  into JavaScript (React elements) by babel which will look like something this.

function Welcome(props) {
  return React.createElement(
    "h1",
    null,
    "Hello, ",
    props.name
  );
}

Functional & Class Component

If you want to just render some html markup on the screen you don’t need a class component, above Welcome component will be enough, but if you want to give the component some state and wants to utilize component lifecycle hooks then you must use Class Component. A simple Class Component looks like this:

import React, { Component } from "react";

class Welcome extends Component {
   state = {};
   render() {
      return<div>Hello {this.props.name}</div>;
   }
}

export default Welcome;
Enough with the theory let’s dive into the code which will make everything clear. Here is the app we are going to building.
react comment app

Building Comment App

Now we have basic understanding of react, let’s use it to create something practical, in this case our comment app.

Create React App CLI

We will use official create-react-app (https://github.com/facebook/create-react-app) CLI to scaffold our app, lets first install it globally by running npm install -g create-react-app.

(npx comes with npm 5.2+ and higher, see instructions for older npm versions)

Now we can run npx create-react-app react-component which will create a brand new react app inside react-component folder.

cd into the react-component and run npm run start which will start a node dev server on http://localhost:3000/ and load generated app.

Component Tree

Before getting into coding lets take a minute and see how we can split our app into multiple smaller components.

As you can see we have broken our comment app in simple components. Let’s now code each component.

App Component

Every app has some root component, we are going to use src/App.js root component, it will hold some loading state and all the comments as array.

import React, { Component } from "react";

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      comments: [],
      loading: false
    };
  }

  render() {
    const loadingSpin = this.state.loading ? "App-logo Spin" : "App-logo";
    return (
      <div className="App container bg-light shadow">
        <header className="App-header">
          <img src={logo} className={loadingSpin} alt="logo" />
          <h1 className="App-title">
            React Comments
            <span className="px-2" role="img" aria-label="Chat">
              💬
            </span>
          </h1>
        </header>

        <div className="row">
          <div className="col-4  pt-3 border-right">
            <h6>Say something about React</h6>
            {/* Comment Form Component */}
          </div>
          <div className="col-8  pt-3 bg-white">
            {/* Comment List Component */}
          </div>
        </div>
      </div>
    );
  }
}

export default App;

In constructor we have initialized out state with empty comments array and a loading state which we will use to spin the react logo while we fetch the data from API later. Now we have the markup of this component which is returned as JSX from render function.

Any interpolation in JSX is done by curly braces, just open {varName} inside JSX and write any javascript expression it will be printed on the screen.

React DOM uses camelCase property naming convention instead of HTML attribute names. For example, class becomes className in JSX, and tabindex becomes tabIndex.

Render function will be called whenever our state or props changes. Unlike Vue.js you have to call this.setState({loading: true}) to update the state and then react will re render the component. It makes react fast since it don’t waste time comparing DOM automatically for changes. I have used loadingSpin const in render function with a ternary operator to get the css classes. You will see lots of this convention in react world. Since React don’t have templating engine like Vue you need to use old javascript to render ui based on some condition.  Now lets move on to CommentList and Comment component.

Comment List

This component will be responsible to list all the Comments passed via props as array. Since this component do not need any state we can get away with a Functional component, which is just a function taking props and argument and returning some JSX.

import React from "react";
import Comment from "./Comment";

export default function CommentList(props) {
  return (
    <div className="commentList">
      <h5 className="text-muted mb-4">
        <span className="badge badge-success">{props.comments.length}</span>{" "}
        Comment{props.comments.length > 0 ? "s" : ""}
      </h5>

      {props.comments.length === 0 && !props.loading ? (
        <div className="alert text-center alert-info">
          Be the first to comment
        </div>
      ) : null}

      {props.comments.map((comment, index) => (
        <Comment key={index} comment={comment} />
      ))}
    </div>
  );
}

As you can say its pretty simple component, most of the part is just html, we have some inline if-else with conditional operator to show the Be the first to comment msg only if it has some comments which is passed to this component from App component via props like this:

<CommentList
   loading={this.state.loading}
   comments={this.state.comments}
/>

This way comment list component can get the data from parent component, in React data flows from top to bottom. Not only variable you can pass functions too via props to which you can call from child. You will see this in action once we create CommentForm component.

Another interesting thing is map to comment part in above JSX.

props.comments.map((comment, index) => (
  <Comment key={index} comment={comment} />
))

In React we loop through array of data like this, it doesnt have any v-for or ng-repeat directive to list items from an array. Instead we use javascript array map function which builds the list of all the comment and it will be shown to user. When rendering list react also asks for a unique key props which it uses to optimize the performance of list and it helps react to know which element it needs to update on data change.

Comment

Comment is also a functional component, it just takes the comment and render some comment in formatted html markup.

import React from "react";

export default function Comment(props) {
  const { name, message, time } = props.comment;

  return (
    <div className="media mb-3">
      <img
        className="mr-3 bg-light rounded"
        width="48"
        height="48"
        src={`https://api.adorable.io/avatars/48/${name.toLowerCase()}@adorable.io.png`}
        alt={name}
      />

      <div className="media-body p-2 shadow-sm rounded bg-light border">
        <small className="float-right text-muted">{time}</small>
        <h6 className="mt-0 mb-1 text-muted">{name}</h6>
        {message}
      </div>
    </div>
  );
}

There is nothing new going on, it just personalize piece of html with passed comment in prop. Now let’s work on Interesting FormComponent.

FormComponent

In react form handling is a little complicated, there is no v-model orng-model directive which can wire up the two way binding with state for you. You have to do it by yourself, lets see how it works:

import React, { Component } from "react";

export default class CommentForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: false,
      error: "",

      comment: {
        name: "",
        message: ""
      }
    };

    // bind context to methods
    this.handleFieldChange = this.handleFieldChange.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
  }

  /**
   * Handle form input field changes & update the state
   */
  handleFieldChange = event => {
    const { value, name } = event.target;

    this.setState({
      ...this.state,
      comment: {
        ...this.state.comment,
        [name]: value
      }
    });
  };

  /**
   * Form submit handler
   */
  onSubmit(e) {
    // prevent default form submission
    e.preventDefault();
    //...
  }

  renderError() {
    return this.state.error ? (
      <div className="alert alert-danger">{this.state.error}</div>
    ) : null;
  }

  render() {
    return (
      <React.Fragment>
        <form method="post" onSubmit={this.onSubmit}>
          <div className="form-group">
            <input
              onChange={this.handleFieldChange}
              value={this.state.comment.name}
              className="form-control"
              placeholder="😎 Your Name"
              name="name"
              type="text"
            />
          </div>

          <div className="form-group">
            <textarea
              onChange={this.handleFieldChange}
              value={this.state.comment.message}
              className="form-control"
              placeholder="🤬 Your Comment"
              name="message"
              rows="5"
            />
          </div>

          {this.renderError()}

          <div className="form-group">
            <button disabled={this.state.loading} className="btn btn-primary">
              Comment ➤
            </button>
          </div>
        </form>
      </React.Fragment>
    );
  }
}

First thing first, we are initializing some state in constructor and binding the context to onSubmit() and handleFieldChange()methods so inside these methods this can refer to current component class.

In order to work with form you need to add two attributes (props) on the input, first is value and then an onChange event handler which will update the passed value. I know its lots of work to just able to type into fields but you will have full control over input data by doing this.

<input
    onChange={this.handleFieldChange}
    value={this.state.comment.name}
    className="form-control"
    placeholder="😎 Your Name"
    name="name"
    type="text"
/>

In above name input we are hooking its value to this.state.comment.name and when user types in it we are firing this.handleFieldChange(event) method which is updating name property of comment state to the typed value. If you remove the handler your Input will be read only, you cant type in this.

Submit Form

Now let’s implement the submit comment feature, it will be done by onSubmit() handler.

Update the App component and pass the addComment(comment) via props:

<h6>Say something about React</h6>
   <CommentForm addComment={this.addComment}/>

Write addComment() method on App component, it just updates the state comments array by prepending comment.

class App extends Component {
  constructor(props) {
    super(props);
    // ...
    this.addComment = this.addComment.bind(this);
  }

  addComment(comment) {
    this.setState({
      loading: false,
      comments: [comment, ...this.state.comments]
    });
  }
}

Now inside our onSubmit add following code.

onSubmit(e) {
    // prevent default form submission
    e.preventDefault();

    if (!this.isFormValid()) {
      this.setState({ error: "All fields are required." });
      return;
    }

    // loading status and clear error
    this.setState({ error: "", loading: true });

    // persist the comments on server
    let { comment } = this.state;
    fetch("http://localhost:7777", {
      method: "post",
      body: JSON.stringify(comment)
    })
      .then(res => res.json())
      .then(res => {
        if (res.error) {
          this.setState({ loading: false, error: res.error });
        } else {
          // add time return from api and push comment to parent state
          comment.time = res.time;
          this.props.addComment(comment);

          // clear the message box
          this.setState({
            loading: false,
            comment: { ...comment, message: "" }
          });
        }
      })
      .catch(err => {
        this.setState({
          error: "Something went wrong while submitting form.",
          loading: false
        });
      });
  }

  isFormValid() {
    return this.state.comment.name !== "" && this.state.comment.message !== "";
  }

Let’s go through this, first we are preventing default action on form submit which will prevent the page refresh. Then we are running simple validation. Pass that we turn on the loading state and making call to api via javascript fetch().

Once request goes through I am calling this.props.addComment(comment) by passing the comment which will update the parent state. After this just setting the message to empty string so user can type new comment if he/she wanted.

Now we can post a comment list hook the CommentList with comments to show the list of comment posted.

Fetch Comments

We want to show list of comments posted as soon our app loads. React gives you lots of component lifecycle hooks method where you can do any initialisation needed.

React Component lifecycle methods

react-lifecycle-methods

  • componentWillMount is executed before rendering, on both the server and the client side.
  • componentDidMount is executed after the first render only on the client side. This is where AJAX requests and DOM or state updates should occur. This method is also used for integration with other JavaScript frameworks and any functions with delayed execution such as setTimeout or setInterval.
  • componentWillReceiveProps is invoked as soon as the props are updated before another render is called. We triggered it from setNewNumber when we updated the state.
  • shouldComponentUpdate should return true or false value. This will determine if the component will be updated or not. This is set to true by default. If you are sure that the component doesn’t need to render after state or props are updated, you can return false value.
  • componentWillUpdate is called just before rendering.
  • componentDidUpdate is called just after rendering.
  • componentWillUnmount is called after the component is unmounted from the DOM.

You should checkout this Hackernoon post on lifecycle hook for more info

In our case we can use componentDidMount to call the api and get list of comments posted.

componentDidMount() {
    // loading
    this.setState({ loading: true });

    // get all the comments
    fetch("http://localhost:7777")
      .then(res => res.json())
      .then(res => {
        this.setState({
          comments: res,
          loading: false
        });
      })
      .catch(err => {
        this.setState({ loading: false });
      });
}

With this we can see React logo spinning in header as loading state, once fething is done we see list of comments displayed.

Conclusion

I hope you have learned the basics of react in this post, if you check the docs there isn’t much in react. Its very simple library with small api to learn. I think its worth to invest some time in react since it has very active community and very popular. Also checkout Getting started with Vue.js by making a comment app if you want to learn vuejs which is also great.

Source Code Demo