React.js: a clear guide for beginners

Original author: Ilya Suzdalnitski
  • Transfer
  • Tutorial
The author of the article, the translation of which we publish, believes that, unfortunately, most of the existing React guides do not pay due attention to valuable practical development techniques. Such guides do not always give the person who deals with them an understanding of what “the right approach” is to working with React. This guide, which is designed for novice developers with knowledge of HTML, JavaScript and CSS, will cover the basics of React and the most common mistakes that a programmer using this library may encounter.

image



Why do web developers choose React?


Before we get down to business, let's say a few words about why React can be considered the best alternative among the tools for developing web interfaces. There are many UI frameworks. Why choose React? In order to answer this question - let's compare the two most popular tools for developing interfaces - React and Angular. It should be noted that Vue.js framework, which is gaining popularity, could be included in this comparison, but we will limit ourselves to React and Angular.

▍Declarative approach to interface description


React-development consists in describing what needs to be displayed on the page (and not in drawing up instructions for the browser on how to do this). This, among other things, means a significant reduction in the volume of the template code.

Angular, on the other hand, has command-line tools that generate generic code for components. Doesn't this seem a bit different from what can be expected from modern interface development tools? In fact, we are talking about the fact that Angular has so many sample code that in order to generate it, even a special tool has been created.

In React, starting to develop, just start writing code. There is no sample code of components that you need to somehow generate. Of course, some preparation is needed before development, but when it comes to components, they can be described as pure functions.

▍Clear syntax


Angular code uses directives like ng-model, ngIfand ngFor. This code looks rather cumbersome. In React, on the other hand, JSX syntax is used, which is perceived as plain HTML, that is, in order to start React development, you do not need to learn fundamentally new things. Here's what it looks like:

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

▍The correct learning curve


The learning curve is an important factor to consider when choosing a UI framework. In this regard, it should be noted that there are fewer abstractions in React than in Angular. If you know JavaScript, then you can probably learn how to write React applications in just a day. Of course, in order to learn how to do it correctly, it will take some time, but you can get to work very, very quickly.

If you analyze Angular, then it turns out that if you decide to master this framework, you will have to learn a new language (Angular uses TypeScript), as well as learn how to use Angular command line tools and get used to working with directives.

▍Features of the data binding mechanism


Angular has a two-way data binding system. This, for example, is expressed in the fact that changes in the form of an element lead to automatic updating of the state of the application. This complicates debugging and is a big disadvantage of this framework. With this approach, if something goes wrong, the programmer cannot know exactly what caused the change in the state of the application.

In React, on the other hand, one-way data binding is used. This is a big plus of this library, as it is expressed in the fact that the programmer always knows exactly what caused the change in the state of the application. Such an approach to data binding greatly simplifies debugging applications.

▍Functional development approach


I believe that one of the strengths of React is the fact that this library does not force the developer to use classes. In Angular, all components must be implemented as classes. This leads to excessive code complication, without giving any advantages.

In React, all user interface components can be expressed as sets of pure functions. Using clean functions to form a UI can be compared to a breath of clean air.

Now that we have reviewed the reasons for the popularity of React, which, quite possibly, will incline you towards this particular library when choosing tools for developing user interfaces, let us turn to practice.

Practice of developing React applications


▍Node.js


Node.js is a server platform that supports the execution of JavaScript-code, the capabilities of which will be useful to us for React-development. If this platform is not yet installed, now is the time to fix it .

▍Preparation of the project


Here we will use the package create-react-appfrom Facebook to create the basis of the React-application . This is probably the most popular approach to setting up the working environment, which allows you to start developing. Thanks to the create-react-appprogrammer has at its disposal a variety of necessary tools, which saves him from having to pick them.

To globally install create-react-app, use this command:

npm i -g create-react-app

Then, to create an application template, run the following command:

create-react-app react-intro

At this preliminary training is completed. To start the application run the following commands:

cd react-intro
npm start

Here we go to the project folder and start the development server, which allows you to open a new React application by going to the browser at http: // localhost: 3000 / .

ПроектаProject structure


Let's understand now how the React-application is arranged. To do this, open the project you just created using your IDE (I recommend Visual Studio Code ).

Index.html file


While in the project folder, open the file that is located at public/index.html. Here is what you see when you do this.


File index.html

Here we are particularly interested in the line<div id="root">. This is where our React application will be located. All this element will be replaced with the application code, and everything else will remain unchanged.

Index.js file


Now open the file src/index.js. This file performs the deployment of React-application. And, by the way, the source code of the application will be placed in a directory src.


The index.js file

Here is the line of code that is responsible for outputting what we call the “React-application” to the page:

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

This line tells React that it is necessary to take a component App(very soon we will talk about it) and place it in the divelement root, which was defined in the file we just reviewed index.html.

Let's understand now with a design <App />. It is very similar to HTML code, but it is a sample of JSX code, which is a special JavaScript syntax used by React. Please note that this design begins with a capital letter A, that it is <App />, and not <app />. This is because of the entity naming convention used in React. This approach allows the system to distinguish between ordinary HTML tags and React components. If component names do not begin with a capital letter, React will not be able to display them on the page.

If you .jsplan to use JSX in a certain file, you need to import React there using the following command:

import React from'react';

App.js file


Now we are ready to look at the code of our first component. To do this, open the file src/App.js.


App.js File

In order to create a React component, you must first create a class that is a descendantReact.Component. This is the task that the string solvesclass App extends Component. All React components must contain an implementation of the methodrender, in which, as its name suggests, the component is rendered and a description of its visual presentation is formed. This method should return HTML markup to display it on the page.

Note that an attributeclassNameis the equivalent of an attributeclassin HTML. It is used to assign CSS class elements for styling. A keywordclassin javascript is reserved and cannot be used as an attribute name.

Repeat what we just found out about the components:

  1. Their names begin with a capital letter ( Ac App).
  2. They extend the class React.Component.
  3. They must implement a method renderthat returns markup.

Now let's talk about what, when developing React-applications, should be avoided.

▍Recommendation # 1: no need to use component classes everywhere


Components in React can be created using two approaches. The first is the use of component classes (Class Component), the second is the use of functional components (Functional Component). As you may have noticed, in the example above, classes are used. Unfortunately, most React beginner’s guides suggest using them.

What is wrong with describing components using the class mechanism? The fact is that such components are hard to test and they tend to grow excessively. These components are subject to the problem of poor quality of responsibility, mixing logic and visual presentation (and this complicates debugging and testing applications). In general, the use of classes of components leads to the fact that the programmer, figuratively speaking, "shoots himself in the foot." Therefore, especially if we are talking about novice programmers, I would recommend them not to use classes of components at all.

So, we found out that using classes to describe components is not the best idea. What alternatives do we have? The answer to this question are the functional components. If there is nothing but a method in a component created using a class, renderthen it is an excellent contender for processing it into a functional component. Armed with this idea, we will think about how to improve the component Appcreated by the tool create-react-app:

functionApp() {
  return (
    <divclassName="App">
      ...
    </div>
  );
}
exportdefault App;

See what we did here? Namely, we removed the class and replaced the method with rendera view construct function App() {...}. If we use the syntax of ES6 switch functions, then our code will look even better:

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

We turned the class into a function that returns the markup, which should be displayed on the page.

Ponder this. In the function that returns markup, there is no template code. This is almost a clean markup. Isn't that great?

The code of functional components is much easier to read, working with them has much less distraction to standard designs.

It should be noted here that although we have just said that functional components are preferable to component classes, we will mainly use classes in this material, since the code for component classes is clearer for beginners, it relies on fewer abstractions, it makes it easier to demonstrate key React concepts. But when you are sufficiently familiar with the development of React-applications, it is strongly recommended to take into account, when developing real projects, what was said above. In order to better understand the functional components - take a look at this material .

НаFamiliarity with properties


Properties (props) is one of the central concepts of React. What are “properties”? In order to understand this, remember the parameters that are passed to the functions. In essence, properties are the parameters that are passed to the components. Consider the following code:

const Greetings = (props) => <div>Hey you! {props.firstName} {props.lastName}!</div>;
const App = () => (
  <div>
    <GreetingsfirstName="John"lastName="Smith" />
  </div>
);

Here we created a component Greetingsand used it to greet the person called John Smithfrom the component App. All of this code will result in the following HTML markup:

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

Curly braces in expressions seem to be {props.name}used to extract JavaScript code. Components Greetingsare passed, in the form of parameters, properties firstNameand lastName. We work with them, referring to the properties of the object props.

Notice that a single object is passed to the component props, not two values ​​representing the firstNameand properties lastName.

The code can be simplified by taking advantage of ES6's ability to destruct the objects:

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

As you can see, the construction (props)was replaced by ({ firstName, lastName }). With this replacement, we inform the system that we are only interested in two properties of the object props. And this, in turn, allows us to directly access the values firstNameand lastName, without explicitly indicating the properties of an object like props.firstName.

What if to solve the same problem we, instead of functional components, would use components based on classes? In this case, the component code Greetingswould look like this:

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

I don’t know what sensations such code arouses in you, but it seems to me overloaded with auxiliary mechanisms. Here, in particular, to access the properties, it is necessary to use the constructions of the form this.props.

Един Principle of sole responsibility


The Single Responsibility Principle (SRP) principle is one of the most important programming principles to be followed. He tells us that the module should solve only one problem and should do it qualitatively. If you develop a project without following only this principle, the code of such a project can turn into a nightmarish structure that cannot be maintained.

How can one violate the principle of sole responsibility? Most often this happens when mechanisms unrelated to each other are placed in the same files. In this material we will often refer to this principle.

Beginners usually place many components in one file. For example, we have component code GreetingsandAppis in one file. In practice, this should not be done, as this violates the SRP.

Even very small components (like our component Greetings) need to be placed in separate files.

Put the component code Greetingsin a separate file:

import React from"react";
const Greetings = ({ firstName, lastName }) => (
    <div>
        Hey you! {firstName} {lastName}!
    </div>
);
exportdefault Greetings;

Then use this component in the component App:

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

Note that the file name must match the component name. That is, the component code Appshould be placed in the file App.js, the component code should be placed Greetingsin the file Greetings.js, and so on.

▍Familiarity with the state of the application


A state is another central concept of React. This is where the application data is stored - that is, what may change. Need to save something entered in a form field? Use condition. Need to save points scored by a player in a browser game? For this, you also need to use the state of the application.

Create a simple form with which the user can enter your name. Please note that here I intentionally use the class to describe the component, as this makes it easy to demonstrate the concept in question. You can read about how to convert a component created using a class into a functional component here .

importReact from "react";
classSimpleFormextendsReact.Component{
  render() {
    return (
      <div>
        <input type="text" name="firstName" />
        <Greetings firstName="John" />
      </div>
    );
  }
}
const App = () => (
  <div>
    <SimpleForm />
  </div>
);

The user can enter something in the form field, which is good. However, if you carefully read this code, then you may notice that the username in the welcome page is always used John. What if not all of our users call it that way? If so, then we put ourselves in an uncomfortable position.

How to use the value entered in the field? React does not rely on DOM elements to be accessed directly. Event handlers and application state will help us solve this problem.

classSimpleFormextendsReact.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>
    );
  }
}

A state can be thought of as a simple JavaScript object that, in the form of a property, is stored in a component class SimpleForm. We add a property to this object firstName.

Here we have equipped the field with firstNamean event handler. It starts every time the user enters at least one character in the field. In a class, a onChangeproperty is responsible for handling the event onFirstNameChange, in the function representing which a command is executed of the following form:

this.setState(...)

This is where the status of the component is updated. The state of the components is not updated directly. This is done only using the method setState. In order to write a new value to a property firstName, we simply pass to this method an object containing what needs to be written to the state:

{ firstName: event.target.value }

In this case event.target.value, this is what the user entered in the form field, namely his name.

Please note that we have not declared onFirstNameChangeas a method. It is very important that such things be declared in the form of class properties containing pointer functions, and not in the form of methods. If we declare a similar function as a method, then it thiswill be tied to a form element that calls this method, and not to a class, as we might expect. This little thing is often confusing for beginners. This is one of the reasons for recommending the use of functional components, rather than component classes.

▍Check data entered into the form


We implement a simple system for checking data entered into a form using regular expressions. Let's decide that a name must consist of at least three characters and can contain only letters.

Add an event handler to the component onBlurthat is called when the user leaves the input field. Add another property to the application state - firstNameError. If an error occurred while entering the name, we will display a message about this under the field.

Let's sort this code.

classSimpleFormextendsReact.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 );
    returnthis.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>
    );
  }
}

Application status


In developing the input validation system, we first added a new property to the state firstNameError::

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

Data verification function


Data verification is performed in the arrow function validateName. It checks the entered name with a regular expression:

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

If the check fails, we return an error message from the function. If the name passed the verification, we return an empty string, which indicates that no errors were found during the verification of the name. Here we, for the sake of brevity of the code, use the ternary JavaScript operator.

OnBlur event handler


Take a look at the event handler onBlurthat is called when the user leaves the input field:

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

Here we extract a property from the state firstName, using the capabilities of ES6 to destruct the objects. The first line of this code is equivalent to:

const firstName = this.state.firstName;

Then we call the above data check function, passing it to it firstName, and write to the state property firstNameErrorwhat this function returns. If the check fails, an error message will be sent to this property. If successful, an empty string will be written there.

Render method


Consider the component method render():

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

Here we again use destructive assignment to extract data from the state.

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

This line assigns the function to the onFirstNameBlurevent handler onBlur.

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

Here we use the features of the calculation of logical expressions in JavaScript. The element divcontaining the error message will be displayed only if the value firstNameErrorcan be cast to true.

▍ Styling


If you reproduced in yourself what we were talking about here, then you might notice that our form does not look very nice. Let's fix this by using the built-in styles:

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

Styling components in React is done by passing styles to an attribute style.

I admit that I am not a designer, but what I have done now looks much better than before. This is the form in which the error message is displayed.


Stylized error message form

▍Recommendation # 2: Avoid using styles inside components


Here we are again confronted with an example of what is best not to do when developing React-applications. The problem in question, that is - the placement of styles in the method render, unfortunately, is extremely prevalent. Why is that bad? The fact is that it violates the principle of sole responsibility. In addition, styles clutter up the component code, which significantly impairs the readability of the program.

How to avoid it? In fact, quite simple. It is enough to create a special object stylethat will contain styles. It is recommended to place styles in a separate file. For example, here is the code for a similar file named style.js:

// 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;

After this file is created, connect it to the component SimpleComponent:

import style from './style';
classSimpleFormextendsReact.Component{
    ...
    
      render() {
        const { firstNameError, firstName } = 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} />
          </div>
        );
      }
  }
  export defaultSimpleForm;

This code looks much cleaner than its previous version. So take it as a rule to place styles in separate files.

Add form fields


Let's make our form a bit more interesting by adding a field to enter the user's last name:

classSimpleFormextendsReact.Component{
  state = {
    ...
    lastName: "",
    lastNameError: ""
  };
  validateName = ...;
  onFirstNameBlur = ...;
  onFirstNameChange = ...;
  onLastNameBlur = () => {
    const { lastName } = this.state;
    const lastNameError = this.validateName(lastName);
    returnthis.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 defaultSimpleForm;

There are not so many changes in the updated component - we just copied the code used for the description firstNameand created copies of the event handlers.

Surely I wrote the word "copied"? Copying code is something that you should avoid.

▍Recommendation # 3: divide the code into small fragments


Monolithic code, not divided into parts, is a problem that, like many others, violates the principle of sole responsibility. Well-written code should be read like a poem, but I bet that the code of the method of renderour component looks much worse. Let's solve this problem.

The input fields used in the form are almost identical, and both of them need some kind of data verification mechanisms. Apply some refactoring techniques to our component and create a component TextFieldsuitable for reuse.

import React from'react'import style from"./style";
const TextField = ({name, onChange, onBlur, error, label}) => (
  <divstyle={style.inputGroup}>
    <label>
      {label}
      <input
        style={style.input}
        type="text"
        name={name}
        onChange={onChange}
        onBlur={onBlur}
      />
      {error && <divstyle={style.error}>{error}</div>}
    </label>
  </div>
);
export default TextField;

Creating this component, I simply extracted the code of one of the input fields from the method renderand designed it as a functional component. The fact that in different instances of such a component may change is transferred to it in the form of properties.

Here's how to use this new component in a component SimpleForm:

...
importTextField from './TextField';
classSimpleFormextendsReact.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>
    );
  }
}

This code is easier to read than the previous one. We can go even further by creating separate components TextFieldfor first and last names. Here is the component code FirstNameField:

import React from'react';
import TextField from'./TextField';
const FirstNameField = ({...rest}) => (
  <TextField name="firstName"
              label="First name:"
              {...rest} />
);
exportdefault FirstNameField;

Here we simply return the component that was prepared in advance for displaying the user name. The construction ({...rest})uses the syntax of the remaining parameters (the rest operator, which looks like three dots). Through the use of this operator, it turns out that everything that will be passed to the component in the form of properties will fall into the object rest. Then, in order to transfer the properties to the component TextField, the so-called “extension operator” is used, or the spread operator (in the construction {...rest}, it also looks like three points). Here the object is taken rest, from which its properties are allocated, which are passed to the component TextField.

In other words, all these constructions allow us to express the following idea: we want to take everything that came into the component FirstNameFieldand pass it on unchanged to the componentTextField.

The component is similarly arranged LastNameField:
Here is the form code now:

...
importFirstNameField from './FirstNameField';
importLastNameField from './LastNameField';
classSimpleFormextendsReact.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>
    );
  }
}

Now the component code looks much better.

▍About classes of components


We list the reasons for which it is not recommended to use components based on classes, preferring functional components:

  • Class-based components, unlike functional components, are more difficult to test.
  • Component classes poorly support the concept of shared responsibility. If the developer does not pay due attention to the quality of the code, he will add all the code into one class, which, over time, can grow into a monster with a size of 1000 lines (I have seen such classes more than once).
  • The use of component classes encourages the developer to place in the same place the logic of the application and the description of its interface. And this, again, is bad in terms of the concept of sharing responsibility.
  • Components created using classes cannot be called entities that resemble pure functions. This makes it difficult to predict the behavior of such components. Functional components, on the other hand, are pure functions. This is reflected in the fact that they always, when transmitting the same input data to them, produce the same markup.
  • The use of functional components helps the developer to reflect on the architecture of applications, which, in turn, improves it.
  • When using functional components, there is no need to use a keyword thisthat has always been a source of confusion.

Results


Although this material turned out to be quite long, you can talk about creating React-applications for a very long time. In particular, how to properly organize the code, and what development techniques are recommended and not recommended. Despite the fact that this guide is not comprehensive, we believe that if it was thanks to him that your first acquaintance with React took place, then you will be able to use this UI library effectively, using what you learned today.

→ The source code of the examples discussed in this material can be found here

Dear readers! If today, during the reading of this article, you have written your first lines of React-code, please share your impressions.


Also popular now: