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.