Register to get access to free programming courses with interactive exercises

Rendering state JS: Frontend architecture

State-based appearance updates are usually put in a separate layer called a view. In the simplest case, a view is a function that takes the state as input, analyzes it, and makes the necessary changes in the DOM.

// it's better to put it in a separate file because there's usually a lot of code
import render from './view.js';

const state = /* state data  */;

input.addEventListener('keyup', (e) => {
  state.registrationForm.value = e.target.value;
  if (input.value === '' || input.value.match(/^\d+$/)) {
    state.registrationForm.valid = true;
    state.registrationForm.errors = [];
  } else {
    state.registrationForm.valid = false;
    state.registrationForm.errors.push('wrong format');
  }

  render(state);
});

An example of how render() can be arranged internally:

const render = (state) => {
  const submit = document.querySelector(/* .... */);
  const input = document.querySelector(/* .... */);

  submit.disabled = !state.registrationForm.valid;

  if (state.registrationForm.valid) {
    input.style.border = null;
  } else {
    input.style.border = 'thick solid red';
  }
};

This approach can have performance issues because you need to constantly search the DOM for elements. To avoid this, you can turn a view into an object and only save the parts that you need once, during initialization.

const view = new View();
view.init(); // here we make selections
view.render(state);

As the application grows, so does the number of handlers. Each of them can only change part of the page. What should we do in such a case? Should you create a renderer for each situation or define all the possible changes in one render function?

The easiest solution would be to bind such functions to state elements. Suppose we have a page to manage a list of lessons in a course. In the state it looks like this:

const state = {
  lessons: [/* list of lessons */],
  // the rest of the state
};

The renderLessons() function is good for rendering this list, and it will be called in all the handlers that modify the list, either by deleting or adding items.

// Adding
el1.addEventListener('submit', (e) => {
  // logic
  renderLessons(state.lessons);
})

// Deleting
el2.addEventListener('submit', (e) => {
  // logic
  renderLessons(state.lessons);
})

And the best part. What happens inside this function? It seems that inside the rendering function, you have to determine what happened and then change the necessary part of the DOM, for example, by removing some element that no longer exists. In reality, this is a very costly approach, it's difficult to program because you end up with a great many conditional constructs. It's easier to carry out the rendering again in any situation. Then the code will remain as simple as possible.

This approach has a serious disadvantage – performance. But make sure to keep two important points in mind. First, performance isn't always a problem. For example, if you need to implement autocomplete, this is exactly what you need to do. It will always work quickly. Second, this is precisely the problem that modern frontend frameworks solve. They themselves know how to update the DOM most efficiently.

Our application is now divided into three independent parts: state (application data), handlers, and rendering. This way of working with looks redundant with trivial applications (with only a few handlers), but if you have at least 10 handlers, you'll find that the application is quite convenient to work with. You can see the data flow, i.e., the movement of data from one part of the application to another, and from the handler to rendering in the DOM. You can always keep track of what has changed and how some parts of the application depend on others. It also reduces duplication. For example, state changes can come from different parts of the application, but the rendering logic remains the same. When the state needs to be changed, all you need to do is define a new way of changing an already existing state, and rendering will do the rest.

In addition to being divided into three parts, how they interact with each other is equally important:

  • The state doesn't know anything about the rest of the system – it's a core.
  • The rendering process itself naturally uses the state for rendering, and it adds new handlers to the DOM.
  • Handlers know about the state because they update it and initiate rendering.

This method of separation still has one important disadvantage, which we'll deal with in the lesson on MVC.


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

The Hexlet support team or other students will answer you.

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:

Bookmate
Health Samurai
Dualboot
ABBYY
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.