React.js: a better introduction to the most powerful UI library

React.js: a better introduction to the most powerful UI library

  • 2018-09-10 01:41 AM
  • 38

React.js: a better introduction to the most powerful UI library , Unfortunately, most of the React tutorials out there have no consideration for best practices and don’t always teach you the “right” way to do React.

A better introduction to React?

Unfortunately, most of the React tutorials out there have no consideration for best practices and don’t always teach you the “right” way to do React.

In this tutorial, I will go over the basics of React, and the most common bad practices that you might encounter.

This tutorial is going to be long, so make sure to get yourself some coffee!

Why choose React?

Before we get started, let’s stop for a moment and see why React indeed is the best choice. There are many other UI frameworks out there. Why should you choose React? Let’s compare the two most popular UI frameworks — React and Angular. Although Vue.js is gaining popularity, we won’t be taking a look at it in this comparison.

Declarative

In React, you describe what to render (instead of telling the browser how to do it). This also means that the amount of boilerplate is greatly reduced.

Angular, on the other hand, even has a CLI which generates all of the component boilerplate for you. Does this seem a little bit off? There’s such a huge amount of boilerplate in Angular that they’ve even created a special tool to generate it for you…

In React you simply start coding, it has no component boilerplate that you have to generate. There’s some setup involved, but when it comes to the components, you can express them as pure functions.

Clear syntax

Using the ng-model ,ngIf and ngFor directives in Angular feels awkward. On the other hand, JSX in React feels just like HTML, there’s no special syntax to learn:

const Greetings = ({ firstName }) => (
   <div>Hi, {firstName}</div>
);

react_example.jsx

Learning curve

Learning curve is very important when picking a UI framework. React has the least abstractions. If you know JavaScript then you can probably start writing React code in a single day. Yes, it takes time to pick up best practices, but you will be able to start very fast.

On the other hand, if you want to start using Angular, you have to pick up a new language (Angular uses TypeScript), Angular CLI, directives, among other things.

Data binding

Angular has two-way data binding, which means that changes to a form element will automatically update the state. This is a big downside and makes debugging much harder. You may never be 100% sure what caused your state to update.

React, on the other hand, has one-way data binding. Which is a big benefit — you will always know what caused your state to change, and debugging is so much easier.

Functional

In my opinion, React’s greatest strength comes from the fact that you aren’t even forced to use classes. In Angular all of the components have to be implemented as classes. Classes over-complicate codebase without providing any benefits.

In React, all of the UI can be expressed as a set of pure functions, and using pure functions to render the UI feels like a breath of fresh air.

Let’s start coding!

Now that I’ve hopefully convinced you to go with React, let’s write some code!

Node.js

Node.js is a JavaScript runtime environment which enables us to compile cool React code!

First of all, let’s make sure that you have Node.js installed. If not, you can download it from here: https://nodejs.org/en/download

create-react-app

We’ll be using create-react-app from Facebook to scaffold our application. This is the most popular way to set up your environment and start coding. It comes with many required tools built-in, which helps us to eliminate many decisions.

To install create-react-app globally:

npm i -g create-react-app

Then to scaffold your first project run:

create-react-app react-intro

That’s all! Now, to start the application:

cd react-intro
npm start

This will launch a development server and will allow you to open the new and shiny React application by going to http://localhost:3000/ in your browser.

Under the hood

Now, let’s see how things are working under the hood. Using your IDE of choice (I recommend Visual Studio Code) open the newly-created project.

index.html

Within the project go to the public/index.html file. This is what you’ll see:

The part that interests us is the <div id="root"></div>. This is where our React application will go. The entire root div will simply be replaced with the contents of our React application. Everything else will remain unchanged.

index.js

Now let’s open src/index.js. This is the file that bootstraps the entire React application. And by the way, all of our React source code will go into the src directory.

The line that does the magic is:

ReactDOM.render(<App />, document.getElementById('root'));

This line is a way of telling React to take our App component (we’ll discuss it in a bit), and place it within the root div that was defined above within the index.html file.

Let’s now focus on the <App /> part. This looks a lot like HTML, doesn’t it? This is called JSX, and is a special JavaScript syntax that React uses to do its magic. Note that it starts with a capital A — it is <App /> , not <app /> . This is a convention used by React, which allows it to disambiguate between regular HTML tags and React components that we’ve created. If you don’t start your components with a capital letter, then React won’t be able to render your components.

Whenever using JSX, we always have to import React by adding the following line within our .js file:

import React from 'react';

App.js

Now we’re ready to take a look at our first component. Let’s open src/app.js :

In order to create a React component, we have to create a class that inherits from React.Component. That is exactly what the line class App extends Component does. All React components should implement a render method — as you may have guessed, all of the rendering is happening within this method. The render method has to return the markup to be rendered.

A small side-note: the className attribute is equivalent to the class attribute in HTML, and is used to assign CSS classes for styling. class is a reserved keyword in JavaScript, and cannot be used for an attribute name.

Let’s recap:

  1. The component is named App (capital A)
  2. It extends the React.Component class
  3. It has to implement the render method, which returns the markup.

Bad practice #1 — Class Components everywhere

There are two ways to create components in React — Class Components and Functional Components. As you may have noticed, the example above uses a Class Component. And, unfortunately, most beginner React tutorials encourage the use of Class Components.

What’s wrong with Class Components? They are hard to test, tend to grow really big, prone to poor separation of concerns, couple logic with presentation (which makes debugging and testing harder). In general, you will shoot yourself in the foot by using Class Components. Especially if you’re a beginner, I would recommend staying away from them altogether.

Ok, Class Components are bad, I get it. But what are the alternatives? Functional Components. If a component has nothing but the render method, then it is a great candidate for refactoring into a functional component. Let’s see how the App component created by create-react-app can be improved:

function App() {
  return (
    <div className="App">
      ...
    </div>
  );
}

export default App;

reactintro_function.jsx
See what we have done here? We’ve removed the class and replaced the render method with function App() {...}. And if we make use of ES6 arrow functions, it is going to look even better:

const App = () => (
  <div className="App">
    ...
  </div>
);

export default App;

reactintro_arrow.jsx
We’ve turned the class component into a function that returns the markup to be rendered.

Think about it for a moment… A function that returns the markup, there’s no unnecessary boilerplate code, just pure markup! Isn’t it beautiful?

The functional component reads much better and has a higher signal-to-noise ratio.

In this article, we’ll stick with Class Components, because they involve fewer abstractions, and are easier to demonstrate the core React concepts. Once you’re comfortable with React basics, I strongly recommend you to read my more in-depth article — Mastering React Functional Components with Recompose.

Introducing props

Props is a concept central to React. What props are exactly? Think for a second about parameters passed to a function. Props are just that — parameters passed down to a component.

const Greetings = (props) => <div>Hey you! {props.firstName} {props.lastName}!</div>;

const App = () => (
  <div>
    <Greetings firstName="John" lastName="Smith" />
  </div>
);

reactintro_props.jsx
Here we have created a Greetings component, and we’re using it to greet John Smith from within the App component. This will result in the following markup:

<div>
   <div>Hey you! John Smith!</div>
</div>

The curly brackets in {props.name} denote JavaScript code. The Greetings component was passed firstName and lastName as parameters, and we simply retrieve them by accessing the props object.

Note that the component got passed a single props object, not two values for firstName and lastName.

We can further simplify the code by making use of the ES6 object destructuring syntax:

const Greetings = ({ firstName, lastName }) => <div>Hey you! {firstName} {lastName}!</div>;

reactintro_props_destructured.jsx
Note that (props) was replaced with ({ firstName, lastName }). This means that we’re only interested in those two properties of the props object. And this, in turn, allows us accessing the firstName and lastName values directly, without having to explicitly specify props.firstName.

What if we’ve been using class components instead?

class Greetings extends React.Component {
  render() {
    return (
      <div>Hey you! {this.props.firstName} {this.props.lastName}!</div>
    );
  }
}

reactintro_props_class.jsx

I don’t know about you, but to me, this looks much more bloated! We always have to explicitly use this.props.

Single Responsibility Principle

Single-Responsibility Principle is the most important programming principle to follow. It states that a module should do one thing, and it should do it well. Not following this principle alone can turn any codebase into a nightmare that is impossible to maintain.

How can we violate this principle? The most common way is placing unrelated things in the same file.

I’ll refer to the Single Responsibility Principle multiple times in this tutorial.

Beginners usually place multiple components in the same file. Here we’ve placed the Greetings and App components within the same file. This is a bad practice because this violates the Single Responsibility Principle.

Even the smallest components (like the Greetings component above) should be placed in a separate file.

Let’s place the Greetings component into its own file:

import React from "react";

const Greetings = ({ firstName, lastName }) => (
    <div>
        Hey you! {firstName} {lastName}!
    </div>
);

export default Greetings;

reactintro_greetings.jsx
And then to use it within the App component:

import Greetings from "./Greetings";
const App = () => (
  ...
);

Make sure that the filename matches the component name. App component should be placed in App.js, Greetings component should be placed in Greetings.js, and so on.

Introducing state

State is another concept central to React. This is where you want to keep your data — things that may change. Storing the value typed into a form element? Use state. Keeping track of score within your game? Use state.

Let’s build a simple form that takes in user’s first name. Note that I’m purposefully using a class component to demonstrate the concept. Later on, we’ll refactor it into a functional component.

import React from "react";

class SimpleForm extends React.Component {
  render() {
    return (
      <div>
        <input type="text" name="firstName" />
        <Greetings firstName="John" />
      </div>
    );
  }
}

const App = () => (
  <div>
    <SimpleForm />
  </div>
);

reactintro_input_simple.jsx
Ok, the user can type his email into the form, which is great! If you’ve been paying attention, then you’ve noticed that no matter what, the name John will be used in the greeting. What if not all of our users’ names are John? We’d place ourselves in a very uncomfortable situation.

How can we use the value typed into the input? In React we aren’t supposed to query the DOM directly. This is where input handlers and state come in.

class SimpleForm extends React.Component {
  state = {
    firstName: "",
  };

  onFirstNameChange = event =>
    this.setState({
      firstName: event.target.value
    });

  render() {
    return (
      <div>
        <input type="text" name="firstName" onChange={this.onFirstNameChange} />

        <Greetings firstName={this.state.firstName} />
      </div>
    );
  }
}

reactintro_form_state.jsx
State is basically a plain JavaScript object that is stored as a property within the SimpleForm class component. Here we’re adding value firstName to the class.

Our firstName input now has the onChange event handler. It fires every time when the user types a key into the input. And the propertythis.onFirstNameChange in our class handles the onChange events.

Let’s take a look at the onFirstNameChange property:

this.setState(...)

This is how we update the state of our components. We’re not supposed to update the component state directly, only via the setState method. And to update the value of the firstName state value we simply pass an object with the updated values to the setState method:

{ firstName: event.target.value }

In this case, event.target.value is the value that was typed into the form input. In this case, this is the user’s name.

A side note: we haven’t defined onFirstNameChange as a method. This is extremely important to define it as an arrow function property on the class, and not a method. If we had defined it as a method instead, then this would be bound to the form input that called the method, not to the class as we would have expected. This small detail often trips up beginners. This is another reason to avoid classes in JavaScript.

Form validation

Now let’s implement simple form validation using regular expressions — let’s ensure that the first name is at least three characters long, and contains only letters.

We will add another event handler for the onBlur event —it will fire whenever the user leaves the input. We will also add another property to the state — firstNameError. And then we’ll display the validation error right under the input (if errors are present).

class SimpleForm extends React.Component {
  state = {
    firstName: "",
    firstNameError: "",
  };

  validateName = name => {
    const regex = /[A-Za-z]{3,}/;

    return !regex.test(name)
      ? "The name must contain at least three letters. Numbers and special characters are not allowed."
      : "";
  };

  onFirstNameBlur = () => {
    const { firstName } = this.state;

    const firstNameError = this.validateName( firstName );

    return this.setState({ firstNameError });
  };

  onFirstNameChange = event =>
    this.setState({
      firstName: event.target.value
    });

  render() {
    const { firstNameError, firstName } = this.state;

    return (
      <div>
        <div>
          <label>
            First name:
            <input
              type="text"
              name="firstName"
              onChange={this.onFirstNameChange}
              onBlur={this.onFirstNameBlur}
            />
            {firstNameError && <div>{firstNameError}</div>}
          </label>
        </div>

        <Greetings
          firstName={firstName}
        />
      </div>
    );
  }
}

// reactintro_validation.jsx

State

First, we’ve added a firstNameError property to the state:

state = {
   ...
   firstNameError: "",
};

Validation function

The validation itself is happening in the validateName arrow function above. It simply tests the input name against the regular expression:

validateName = name => {
  const regex = /[A-Za-z]{3,}/;
  return !regex.test(name)
     ? "The name must contain at least three letters..."
     : "";
}

If the validation fails, we return the validation error. If the validation succeeds, then we return an empty string (which signifies lack of error). We’re using JavaScript ternary expressions here to make the code terser.

onBlur event handler

Let’s take a look at the onBlur event handler (fires whenever the user leaves the input):

onFirstNameBlur = () => {
  const { firstName } = this.state;
  const firstNameError = this.validateName( firstName );
  return this.setState({ firstNameError });
};

Here we extract the firstName from the state by using ES6 object destructuring syntax. The first line is equivalent to:

const firstName = this.state.firstName;

Then we run the validation function defined above with the firstName, and then we set the firstNameError state property with the error returned. If the validation failed, the firstNameError will be set. If it succeeds, then it will be set to an empty string.

render method

And now let’s take a look at the render() method:

render() {
   const { firstNameError, firstName} = this.state;
   ...
}

Here we’re once again using ES6 object destructuring to extract values from the state.

<input
  ...
  onBlur={this.onFirstNameBlur}
/>

This line assigns the onFirstNameBlur function as the event handler for the onBlur event.

{firstNameError && <div>{firstNameError}</div>}

Here we’re using short circuit evaluation feature of JavaScript. The div containing the firstNameError will be rendered only if the value itself is truthy.

Styling

If you’ve been following along, then you might have noticed that our form isn’t particularly pretty… Let’s change that by adding some inline styling!

render() {
  const { firstNameError, firstName } = this.state;

  return (
    <div
        style={{
          margin: 50,
          padding: 10,
          width: 300,
          border: "1px solid black",
          backgroundColor: "black",
          color: "white"
        }}
      >
      <div style={{marginBottom: 10}}>
        <label>
          First name:
          <input
            style={{backgroundColor: '#EFEFFF', marginLeft: 10}}
            type="text"
            name="firstName"
            onChange={this.onFirstNameChange}
            onBlur={this.onFirstNameBlur}
          />
          {firstNameError && <div style={{color: 'red', margin: 5}}>{firstNameError}</div>}
        </label>
      </div>

      <Greetings
        firstName={firstName}
      />
    </div>
  );
}
view raw

// reactintro_style.jsx

Styles in React are added simply by passing the styles in the style attribute.

I will admit that I’m not a designer, but my programmer art is looking much better now. Here’s the form with validation error:

Bad practice #3 — styling within the component

Here we’ve encountered another bad practice, that unfortunately is too common — placing the styles within the render method of our components. Why is this bad? This violates the Single Responsibility Principle. It also clutters our component with styles, which significantly impairs readability.

What are the remedies? Create a special style object that will contain all of our styles. It is considered a good practice to place the styles in a separate file:

// style.js:
const style = {
    form: {
        margin: 50,
        padding: 10,
        width: 300,
        border: "1px solid black",
        backgroundColor: "black",
        color: "white"
    },
    inputGroup: {
        marginBottom: 10
    },
    input: {
        backgroundColor: "#EFEFFF",
        marginLeft: 10
    },
    error: {
        color: "red",
        margin: 5
    }
};

export default style;

reactintro_style.js
And then to use it within our SimpleForm component:

import style from './style';

class SimpleForm extends React.Component {
    ...
    
    render() {
      const { firstNameError, firstName, lastName } = this.state;
  
      return (
        <div style={style.form}>
          <div style={style.inputGroup}>
            <label>
              First name:
              <input
                style={style.input}
                type="text"
                name="firstName"
                onChange={this.onFirstNameChange}
                onBlur={this.onFirstNameBlur}
              />
              {firstNameError && (
                <div style={style.error}>{firstNameError}</div>
              )}
            </label>
          </div>
  
          <Greetings firstName={firstName} lastName={lastName} />
        </div>
      );
    }
  }

  export default SimpleForm;

reactintro_form_styled.jsx
This looks much cleaner!

Takeaway: place your styles in a separate file.

Adding more form fields

Let’s make the form a little more interesting by adding a field to enter the last name:

class SimpleForm extends React.Component {
  state = {
    ...
    lastName: "",
    lastNameError: ""
  };

  validateName = ...;

  onFirstNameBlur = ...;

  onFirstNameChange = ...;

  onLastNameBlur = () => {
    const { lastName } = this.state;

    const lastNameError = this.validateName(lastName);

    return this.setState({ lastNameError });
  };

  onLastNameChange = event =>
    this.setState({
      lastName: event.target.value
    });

  render() {
    const { firstNameError, firstName, lastName, lastNameError } = this.state;

    return (
      <div style={style.form}>
        <div style={style.inputGroup}>
          <label>
            First name:
            <input
              style={style.input}
              type="text"
              name="firstName"
              onChange={this.onFirstNameChange}
              onBlur={this.onFirstNameBlur}
            />
            {firstNameError && <div style={style.error}>{firstNameError}</div>}
          </label>
        </div>

        <div style={style.inputGroup}>
          <label>
            Last name:
            <input
              style={style.input}
              type="text"
              name="lastName"
              onChange={this.onLastNameChange}
              onBlur={this.onLastNameBlur}
            />
            {lastNameError && <div style={style.error}>{lastNameError}</div>}
          </label>
        </div>

        <Greetings firstName={firstName} lastName={lastName} />
      </div>
    );
  }
}

export default SimpleForm;

reactintro_form_two_fields.jsx
Not much has changed here — we’ve simply duplicated the code for the firstName input, and also duplicated its event handlers.

Did I just say duplicated? Duplicate code is a big NO in software development and should be avoided at all costs.

Bad practice #4 — not splitting your components.

This bad practice once again comes back to the violation of Single-Responsibility Principle. Good written code should read like a poem, and I bet that the render method of our component doesn’t read like a poem. Let’s change that.

The inputs are almost identical, and both require some sort of validation. Let’s apply some refactor-fu to our component, and create a reusable TextField component:

import React from 'react'
import style from "./style";

const TextField = ({name, onChange, onBlur, error, label}) => (
  <div style={style.inputGroup}>
    <label>
      {label}
      <input
        style={style.input}
        type="text"
        name={name}
        onChange={onChange}
        onBlur={onBlur}
      />
      {error && <div style={style.error}>{error}</div>}
    </label>
  </div>
);

export default TextField;

reactintro_TextField.jsx
I’ve simply extracted one of the components from the render method, converted it into a functional component, and passed it things that change as props:

...

import TextField from './TextField';

class SimpleForm extends React.Component {
  ...

  render() {
    const { firstNameError, firstName, lastName, lastNameError } = this.state;

    return (
      <div style={style.form}>
        <TextField name="firstName"
                   label="First name:"
                   onChange={this.onFirstNameChange}
                   onBlur={this.onFirstNameBlur}
                   error={firstNameError} />

        <TextField name="lastName"
                   label="Last name:"
                   onChange={this.onLastNameChange}
                   onBlur={this.onLastNameBlur}
                   error={lastNameError} />

        <Greetings firstName={firstName} lastName={lastName} />
      </div>
    );
  }
}

reactintro_render_refactored.jsx
Nice, this reads much better now! We can even go one step further, and create dedicated TextField components for the first and last name:

import React from 'react';
import TextField from './TextField';

const FirstNameField = ({...rest}) => (
  <TextField name="firstName"
              label="First name:"
              {...rest} />
);

export default FirstNameField;

reactintro_FirstNameField.jsx
Here we’re simply returning a pre-made component to display the first name. The ({...rest}) is the new Object Rest syntax — this means that whatever is passed as props will be saved into the rest object. Then to pass the props down to the TextField we’re using the Object Spread syntax {...rest} . This takes the rest object, spreads its properties, and passes them down to the TextField component.

In other words: we take whatever was passed into FirstNameField, and pass it unchanged to TextField.

And similarly, the LastNameField:

import React from 'react';
import TextField from './TextField';

const LastNameField = ({...rest}) => (
    <TextField name="lastName"
                label="Last name:"
                {...rest} />
);

export default LastNameField;

reactintro_LastNameField.jsx
This is what our form is looking like now:

...

import FirstNameField from './FirstNameField';
import LastNameField from './LastNameField';

class SimpleForm extends React.Component { 
  ...

  render() {
    const { firstNameError, firstName, lastName, lastNameError } = this.state;

    return (
      <div style={style.form}>
        <FirstNameField onChange={this.onFirstNameChange}
                        onBlur={this.onFirstNameBlur}
                        error={firstNameError} />

        <LastNameField onChange={this.onLastNameChange}
                        onBlur={this.onLastNameBlur}
                        error={lastNameError} />

        <Greetings firstName={firstName} lastName={lastName} />
      </div>
    );
  }
}

reactintro_form_refactored2.jsx
Much better!

Why are class components so bad anyways?

  1. Class components are hard to test (unlike functional components).
  2. Poor separation of concerns: If we’re being lazy, then we’ll keep putting everything into one single class, which may grow into a 1000-line monstrosity over time (I’ve seen this happen, multiple times).
  3. Class components tend to put logic and presentation together in one single class. Which is once again bad for separation of concerns.
  4. Class components are not pure, and are hard to reason about. Functional components, on the other hand, are pure — they will always result in the same markup being rendered for the same input props.
  5. Functional components enforce good design. You’re forced to think about design.
  6. No need for the this keyword, which has always been a major source of confusion.

Source code

The accompanying source code can be found at GitHub

What’s next?

This tutorial has gotten a little longer than I was expecting. There’s still a lot to cover, especially relating to code organization, and a few other best practices. Let me know if you’re interested in hearing more in the comments.

If you truly want to master React, I would strongly recommend you to check out my other article: Mastering React Functional Components with Recompose.

Make sure to follow me for more articles on React and JavaScript!

Recommended Courses:

React Redux React-Router: From Beginner to Paid Professional

The Complete Web Development Tutorial Using React and Redux

React for visual learners

React Native Projects

Modern React with Redux