Register to get access to free programming courses with interactive exercises

State JS: React

You have already mastered JSX and the process of working with React components in a non-interactive manner. Now we delve into the most crucial aspect of React - user interaction. The lesson will cover essential concepts such as events and state.

Let us begin with the example:

See the Pen js_react_state_example by Hexlet (@hexlet) on CodePen.

In the previous lessons, we learned how to create stateless components. They didn't contain any state and could only render properties passed to them. However, the component demonstrated above is stateful as it can store the current time's state within itself.

Let's examine this stateful component step by step:

  1. Within the component, in the constructor, we define an initial state with which the component will initialize after rendering. The only state React requires is the data type to be an object. The application itself determines what is stored inside.

    The way to set the initial state is as follows:

    class Clock extends React.Component {
      constructor(props) {
        super(props); // always mandatory
        this.state = { date: new Date() };
      }
    }
    

    It is the only place where we can directly change or create the state. Everywhere else, this.state must be read-only. We'll see more about it further on.

  2. The render function uses data from state to render. There is no surprises here.

  3. A button has a click handler attached to it. Unlike in HTML, a function is passed to the onClick property and called automatically when the event is triggered. The current date is determined, and the new state is set inside the handler.

    Remember that it's crucial not to manipulate the state directly. Instead, use React's setState function to establish a new state. It will cause the component to render eventually. However, note that the setState function operates asynchronously and strives to enhance the rendering process through its internal magic.

Another point to note is how the handleClick function is defined. Since we're working with a class, it would be logical to use this style of definition:

class Clock extends React.Component {
  handleClick() {
    this.setState({ date: new Date() });
  }
}

But this approach doesn't work well in React for two reasons.

The first reason is that we call handlers asynchronously, and methods in classes are normal functions with late binding. So you can't just attach a handler like that because it'll lose this. With this definition, you'll have to write this kind of code all the time:

  • onClick={this.handleClick.bind(this)}
  • or onClick={() => this.handleClick()}

The second reason has to do with performance. The previous handler pass examples spawn new handlers for every call to the render function. For React, it is critical since we compare functions by reference, not content.

Therefore, the correct way to define it is with the arrow function:

class Counter extends React.Component {
  handleClick = () => {
    this.setState({ date: new Date() });
  }
}

Another example:

See the Pen by Hexlet (@hexlet) on CodePen.

It is logical to expect the counter to increment by 2 with each click, but this does not happen. As mentioned above, this.setState is not executed by React immediately. Consequently, a situation may arise where the state or props have changed before the state change.

For such cases where the new state is determined based on the previous state (or props), setState can take a function instead of an object:

this.setState((state, props) => {
  const { count } = state;

  return { count: count + 1 };
})

Try to rewrite the counter example using the described setState form.

Basically, the mechanisms described above open almost all the doors. You can now easily create interactive components and animate your UI. Everything else is finesse provided for different situations.

Initializing

Here we will continue working with the component created above. Imagine you should initialize the counter with the count property passed from outside. And only put 0 in its absence.

To solve this problem, you need to add two things:

  1. Use the count property as the initial counter value
  2. Add a default value for the count property

See the Pen js_react_state_init by Hexlet (@hexlet) on CodePen.

setState

In the following example, we implement two buttons, each one controlling its state:

See the Pen js_react_state_set by Hexlet (@hexlet) on CodePen.

In this example, the state object includes two properties: count for one button and primary for the other. The main trick of this example is the process of updating the state:

// The first button
this.setState(({ count }) => ({ count: count + 1 }));

// The second button
this.setState(({ primary }) => ({ primary: !primary }));

The setState function replaces the values of the keys in the previous state with their values in the new state. We do not touch anything that was not returned inside the function. We merge the old state and the new one.

In practice, this behavior is convenient. Otherwise, we would have to do the merge work by hand every time.

Structure of the state object

There are many ways to organize data within a state. Most likely, you will want to store them in some way:

const blogPosts = [
  {
    id : "post1",
    author : {username : "user1", name : "User 1"},
    body : "......",
    comments : [
      {
        id : "comment1",
        author : {username : "user2", name : "User 2"},
        comment : ".....",
      },
      {
        id : "comment2",
        author : {username : "user3", name : "User 3"},
        comment : ".....",
      }
    ]
  },
  {
    id : "post2",
    author : {username : "user2", name : "User 2"},
    body : "......",
    comments : [
      {
        id : "comment3",
        author : {username : "user3", name : "User 3"},
        comment : ".....",
      },
    ]
  }
  // And repeat it many times
]

In this approach, entities that are dependent on others are inside. Taking the example above, this means that each post contains both an author and a list of comments.

In its turn, each comment contains a related entity of the same author. With this approach, it turns out that the state is a dependency tree.

Although this way of organization seems quite natural, it is hard to work with:

  1. The same data will start to be duplicated in different places. You will have to synchronize changes in it, which creates problems for no reason
  2. Updates to such data become complicated, especially in an immutable style
  3. The whole state is one big chunk, making any update a complete copy of it. It can be an expensive operation, depending on the size of the state and the number of updates per unit of time

A general recommendation that React developers give is to make the structure as flat as possible. It is similar to the way we store data in a database. And preferably in a well-normalized form. In other words, you don't want to duplicate data in the state. An example of how to do this properly:

const state = {
  articles: [/*...*/],
  comments: [/*...*/],
}

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.