React Tutorial Part 26: Application Architecture, Container / Component Pattern

Original author: Bob Ziroll
  • Transfer
  • Tutorial
In this part of the translation of the React tutorial, we will talk about the architecture of React applications. In particular, we will discuss the popular Container / Component pattern. → Part 1: course overview, reasons for the popularity of React, ReactDOM and JSXPart 2: functional componentsPart 3: component files, project structurePart 4: parent and child componentsPart 5: start of work on a TODO application, basics of stylingPart 6: about some features of the course, JSX and JavaScriptPart 7: built-in stylesPart 8: continued work on the TODO application, familiarity with the properties of components

image









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 methodrender()) 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 Appit 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.jsin 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 Forminto the component App. As a result, the component code Appwill look like this:

import React, {Component} from "react"
import Form from "./Form"
function App() {
    return (
        
   ) } export default App

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 Appto 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 Appto 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.jsand 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.jswill contain the logic of the functioning of the form, that is, the code of the container component. Therefore, rename it to FormContainer.jsand change the import command in the component code App, bringing it to this form:

import Form from "./FormContainer"

You can also rename the component Formto FormContainer, but we will not do this. Now transfer the code responsible for rendering the form from file FormContainer.jsto file FormComponent.js.

The component FormComponentwill 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 methodFormComponentand you need to organize the transfer to him of the necessary properties. We import FormComponentinto a file Formand display FormComponentin the method render(), passing it an event handler, and, in the form of an object, the state. Now the component code Formwill 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 FormComponentis 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 Formfrom the file is FormContainer.jsnow exclusively occupied with the logic of the application’s functioning, and the component FormComponentfrom the file FormComponent.jscontains only the code that forms the application interface. ComponentAppNow it is only responsible for assembling the page from large blocks.

It is worth noting that, given the existence of libraries like the Reduxrecently 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?


Also popular now: