React Tutorial Part 26: Application Architecture, Container / Component Pattern
- Transfer
- Tutorial
Part 9: properties of components
→ Part 10: workshop on working with properties of components and stylization
→ Part 11: dynamic markup formation and map arrays method
→ Part 12: workshop, third stage of work on a TODO application
→ Part 13: class-based components
→ Part 14: a workshop on class-based components, the state of components
→ Part 15: workshops on working with the state of components
→ Part 16: the fourth stage of work on a TODO application, event handling
→ Part 17: fifth stage of work on a TODO application, modification sost Component Description
→Part 18: the sixth stage of work on the TODO application
→ Part 19: the methods of the component life cycle
→ Part 20: the first lesson on conditional rendering
→ Part 21: the second lesson and the workshop on conditional rendering
→ Part 22: the seventh stage of work on the TODO application, loading data from external sources
→ Part 23: the first lesson on working with forms
→ Part 24: the second lesson on working with forms
→ Part 25: a workshop on working with forms
→ Part 26: application architecture, the Container / Component pattern
→ Part 27: course project
Lesson 44. Application Architecture, Container / Component Pattern
→ Original
Sometimes the amount of work for which the individual component is responsible is too large, the component has to solve too many tasks. Using the Container / Component pattern allows you to separate the logic of the application from the logic of the formation of its visual representation. This allows you to improve the structure of the application, to share responsibility for the performance of various tasks between different components.
At the previous practical lesson, we created a huge component whose code length approaches 150 lines. Here is the code we got then:
import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {
firstName: "",
lastName: "",
age: "",
gender: "",
destination: "",
isVegan: false,
isKosher: false,
isLactoseFree: false
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
const {name, value, type, checked} = event.target
type === "checkbox" ?
this.setState({
[name]: checked
})
:
this.setState({
[name]: value
})
}
render() {
return (
Entered information:
Your name: {this.state.firstName} {this.state.lastName}
Your age: {this.state.age}
Your gender: {this.state.gender}
Your destination: {this.state.destination}
Your dietary restrictions:
Vegan: {this.state.isVegan ? "Yes" : "No"}
Kosher: {this.state.isKosher ? "Yes" : "No"}
Lactose Free: {this.state.isLactoseFree ? "Yes" : "No"}
)
}
}
export default App
The first drawback of this code, which immediately catches your eye, is that when working with it, you constantly have to scroll it in the editor window.
You can notice that the bulk of this code is the logic of forming the application interface, the contents of the method
render()
. In addition, a certain amount of code is responsible for initializing the state of the component. The component also has what is called “business logic” (that is, what implements the logic of the application's functioning). This is the method code handleChange()
.According to the results of some studies, it is known that the ability of a programmer to perceive the code he is looking at is greatly impaired if the code is long enough, and the programmer has to use scrolling to view it in its entirety. I noticed this during the classes. When the code that I am talking about turns out to be quite long, and I constantly have to scroll through it, it becomes more difficult for students to perceive it.
It would be nice if we redesigned our code, sharing responsibility between the various components for the formation of the application interface (what is now described in the method
render()
) and for implementing the logic of the application’s functioning, that is, by determining what its interface should look like (the corresponding code is now represented by the constructor of the component in which the state is initialized and the event handler of the controls handleChange()
). When using this approach to application design, we, in fact, work with two types of components, and it should be noted that you may encounter different names for such components.We will use the Container / Component pattern here. When using it, applications are built by dividing the components into two types - component components (the word Container in the name of the pattern refers to them) and presentation components (this is Component in the name of the pattern). Sometimes container components are called “smart” components, or simply “containers,” and presentation components are called “dumb” components, or simply “components.” There are other names for these types of components, and, it should be noted, the meaning that is enclosed in these names may, from case to case, differ in certain features. In general, the general idea of this approach is that we have a container component responsible for storing state and containing methods for managing state, and the logic of forming the interface is transferred to another - presentation component. This component is only responsible for receiving properties from the container component and for the correct formation of the interface.
→ Here is Dan Abramov ’s material in which he explores this idea.
We transform the code of our application in accordance with the Container / Component pattern.
To begin with, let's pay attention to the fact that now everything in the application is assembled in a single component
App
. This application is designed in such a way as to simplify its structure as much as possible, but in real projects App
it hardly makes sense for the component to transfer the task of rendering the form and include code in it designed to organize the work of the internal mechanisms of this form. Add, to the same folder in which the file is located
App.js
, the file Form.js
in which the code of the new component will be located. We transfer all the code from the component to this file App
, and the componentApp
, which is now represented by a component that is based on a class, we transform into a functional component, the main task of which will be the output of the component Form
. Do not forget to import the component Form
into the component App
. As a result, the component code App
will look like this:import React, {Component} from "react"
import Form from "./Form"
function App() {
return (
Here is what the application displays on the screen at this stage of work.
Application in the browser
In previous classes, I told you that I prefer the component
App
to be something like a “table of contents” of the application, which indicates in which order its sections are displayed on the page, represented by other components to which large fragment rendering tasks are delegated applications. We have slightly improved the structure of the application, but the main problem, expressed in the fact that one component has too much responsibility, has not yet been resolved. We simply transferred everything that used to be in the component
App
to the component Form
. Therefore, now we are going to solve this problem. To do this, create, in the same folder in which the files are located Form.js
and App.js
, another file -FormComponent.js
. This file will represent the presentation component responsible for the visualization of the form. In fact, you can name it differently, you can structure component files differently, it all depends on the needs and scale of a particular project. The file Form.js
will contain the logic of the functioning of the form, that is, the code of the container component. Therefore, rename it to FormContainer.js
and change the import command in the component code App
, bringing it to this form:import Form from "./FormContainer"
You can also rename the component
Form
to FormContainer
, but we will not do this. Now transfer the code responsible for rendering the form from file FormContainer.js
to file FormComponent.js
. The component
FormComponent
will be functional. Here is how his code will look at this stage of work:function FormComponent(props) {
return (
Entered information:
Your name: {this.state.firstName} {this.state.lastName}
Your age: {this.state.age}
Your gender: {this.state.gender}
Your destination: {this.state.destination}
Your dietary restrictions:
Vegan: {this.state.isVegan ? "Yes" : "No"}
Kosher: {this.state.isKosher ? "Yes" : "No"}
Lactose Free: {this.state.isLactoseFree ? "Yes" : "No"}
)
}
If you look at this code, it becomes clear that we can’t limit ourselves to simply transferring it from file to file, since now there are links to the state (for example -
this.state.firstName
) and the event handler ( this.handleChange
) that were previously in the same component, based on the class in which this rendering code was located. Now, everything that was previously taken from the same class in which the rendering code was located will be taken from the properties passed to the component. There are some other problems. Now we will fix this code, but first, back to the component code Form
, which is now in the file FormContainer.js
. His method is
render()
now empty. We need a component to be output in this methodFormComponent
and you need to organize the transfer to him of the necessary properties. We import FormComponent
into a file Form
and display FormComponent
in the method render()
, passing it an event handler, and, in the form of an object, the state. Now the component code Form
will look like this:import React, {Component} from "react"
import FormComponent from "./FormComponent"
class Form extends Component {
constructor() {
super()
this.state = {
firstName: "",
lastName: "",
age: "",
gender: "",
destination: "",
isVegan: false,
isKosher: false,
isLactoseFree: false
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
const {name, value, type, checked} = event.target
type === "checkbox" ?
this.setState({
[name]: checked
})
:
this.setState({
[name]: value
})
}
render() {
return(
)
}
}
export default Form
Let's fix the component code
FormComponent
, bringing it to the following form:import React from "react"
function FormComponent(props) {
return (
Entered information:
Your name: {props.data.firstName} {props.data.lastName}
Your age: {props.data.age}
Your gender: {props.data.gender}
Your destination: {props.data.destination}
Your dietary restrictions:
Vegan: {props.data.isVegan ? "Yes" : "No"}
Kosher: {props.data.isKosher ? "Yes" : "No"}
Lactose Free: {props.data.isLactoseFree ? "Yes" : "No"}
)
}
export default FormComponent
Here we fixed the code taking into account the fact that the component now receives data and a link to the event handler via properties.
After all these transformations, neither the appearance of the form nor the way it works will change, but we have improved the structure of the project code, although the size of the component code
FormComponent
is still quite large. However, now this code solves only one problem, it is only responsible for the visualization of the form. Therefore, working with him is now much easier. As a result, we have achieved a separation of responsibilities between the components. The component
Form
from the file is FormContainer.js
now exclusively occupied with the logic of the application’s functioning, and the component FormComponent
from the file FormComponent.js
contains only the code that forms the application interface. ComponentApp
Now it is only responsible for assembling the page from large blocks. It is worth noting that, given the existence of libraries like the
Redux
recently released API Context
, the Container / Component pattern discussed here is no longer as relevant as before. For example, Redux can support the global state of an application that components can use.Summary
In this lesson, we examined the use of the Container / Component pattern, which is aimed at dividing the components into those that are responsible for the formation of the application interface and those that are responsible for storing the state and for the logic of the application. Applying this pattern helps to improve the code structure of React applications and facilitates the development process. Next time we will work on a course project.
Dear readers! What design patterns do you use when developing React applications?