What does the concept of a state include? In short, the state is the data of our application at any given time, such as open tabs in an editor or browser. The amount of data and their contents change depending on what buttons we press and what we try to load. In general, if there's any visual change in an application or page, then the state will also change, without exception. There can be no case where something on a webpage or application changes, but the state stays the same. The state must change in order for anything shown to change.
You might think that CSS animations won't change anything in our application. Yes, the CSS animation isn't related to our application, but this state is there inside the browser, and it does change.
One great example of a state is forms. Imagine a field that you can put a phone number in, and that field tracks typos and shows them straight away. If there are no errors, it allows you to submit the form, but if there are errors, the button will be grayed out, and you won't be able to send anything. What is the state in this case? Firstly, it's all the data entered into the form. Secondly, it's whether or not the form data is correct or incorrect (valid/invalid). The state is then used to determine whether the field should be surrounded by a red frame or not. And the errors themselves are also part of the state, as they need to be output.
See the Pen js_dom_state_object by Hexlet (@hexlet) on CodePen.
As you can see from the example, the state is described by a typical JS object, which is created when the application is started:
// A constant because the state object always remains the same
const state = {
// The state of the form is put into a separate property,
// because there may be multiple forms on the page
registrationForm: {
valid: true,
errors: [],
}
};
But the logic of the handler has been divided into two independent processes: changing the state, and displaying the state on the screen:
input.addEventListener('keyup', (e) => {
const inputValue = e.target.value;
// Saving the input data
state.registrationForm.value = inputValue;
// The state of the form and its validity are determined depending on the value entered.
// If the value is invalid, we mark the form as invalid,
// otherwise, we mark it as valid
if (inputValue.match(/^\d+$/)) {
state.registrationForm.valid = true;
state.registrationForm.errors = [];
} else {
state.registrationForm.valid = false;
state.registrationForm.errors.push('wrong format');
}
// Whether or not the button is blocked/grayed out is determined by the validity state
submit.disabled = !state.registrationForm.valid;
// Whether or not there are errors (marked by a red frame) depends on whether the form is valid or not
if (state.registrationForm.valid) {
input.style.border = null;
} else {
input.style.border = 'thick solid red';
}
});
This is how all modern frontend frameworks are set up. The very concept of separating application data from the appearance is far from new; moreover, there are simply no other options in backend development (fortunately).
Recommendations for organizing the state
What's stored in the state
Not all data can change over time in applications. Such data may include configuration data such as timeouts, various access keys or library settings. These data are not stored in the state (we can call them static data). It's enough to have separate constants created directly where they're used, or imported from a special module with settings:
// Not great.
const state = {
// there are all sorts of things
httpTimeout: 1000,
apiKey: 'my super secret hash',
};
// Very
const httpTimeout = 1000;
const apiKey = 'my super secret hash';
State object
The state itself is best organized as a single constant containing the object. Such an object is much easier to follow than a set of different constants or variables. What's more, as you'll see later, it allows for easy change tracking to implement the MVC architecture that we're slowly getting to covering.
// Not great.
const state = {
formState: { /* ... */ },
};
const posts = [];
let connected = false;
// Very
const state = {
formState: { /* ... */ },
posts: [],
connected: false,
};
Naming
Separating the state itself is great, but naming the data inside the state is just as important. What the properties valid
or connected
in the state object mean is something we can guess, but what they refer to, without analyzing the code, is impossible to understand. These words are too general, they don't give any context. It is always better to specify what you mean:
// not great.
const state = {
valid: false,
connected: true,
}
// very
const state = {
registrationFormValid: false,
chatConnected: true,
}
Grouping
Another way to make it easier to understand states is to group properties related to the same process. For example, one form can operate with a dozen different properties. If there are several such forms on the page, the state object will grow quickly, and it will be difficult to understand what belongs to what, even if everything has a good name.
// not great.
const state = {
registrationFormValid: false,
registrationFormErrors: [],
registrationFormNameValue: '',
}
// very
const state = {
registrationForm: {
valid: false,
errors: [],
fields: {
name: '',
},
},
};
Visual structure
A common error when creating a state is tying the structure of the state to the visual design. The problem with this structure is that if you change the design (even a small arrangement of elements), the state object will no longer reflect reality and will have to be corrected.
// Not great.
const state = {
centralBlock: {
valid: true,
},
sideBar: {
formValue: 'value'
},
};
// Very
const state = {
registrationForm: {
valid: false,
},
searchForm: {
value: 'value'
},
};
Storing DOM elements
The state of the application is the data, not how they're displayed. This is why storing DOM elements in the state is considered bad practice. In fact, it is a return to the structure before the introduction of the state when the data were stored inside the view.
Recommended materials
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.