DEV Community

нιтєѕн кυмαя
нιтєѕн кυмαя

Posted on

Component Glossary 📖

Originally published on https://smellycode.com/component-glossary/

Components are basic building blocks of modern web applications. They help web developers to break complex user interfaces into independent smaller blocks or pieces which can be reused and plugged with other pieces or components as is. In general, a component is

"a part or element of a larger whole, especially a part of a machine or vehicle." ~ Google

This article explains various types of components with words and code.

Function Components

Function Components are JavaScript functions which take inputs known as props and returns a React Element as output. Here's a simple Greetings function component to greet.

function Greetings(props) {
  return <h1>Hello {props.name}</h1>;
}

// With arrow function
// const Greetings = props => <h1>Hello {props.name}</h1>;

People often mix up function components with "Functional Components". Every component is a functional component if it is functioning or working fine. 😀

React does not instantiate function components. It means they can not be accessed with the ref attribute. Lack of instantiation also makes the life-cycle hooks inaccessible to function components.

Function components don't have any state unless they are hooked.

Class Components

Components created with ES6 Classes are known as Class Components. Class components extend the base class React.Component. Unlike function components, class components can have state and access the life-cycle methods. Class Components define a render method which returns a react element as output. Here's a simple Clock component to display time.

class Clock extends React.Component {
  state = { now: new Date() };

  intervalId = null;

  updateTime = () => this.setState({ now: new Date() });

  componentDidMount() {
    this.intervalId = setInterval(() => this.updateTime(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.intervalId);
  }

  render() {
    return <p>{this.state.now.toLocaleTimeString({}, { hour12: true })}</p>;
  }
}

Instances of class components can be accessed with the ref attribute.

class App extends React.Component {
  clockRef = React.createRef();

  componentDidMount() {
    // instance of the clock component
    console.log(this.clockRef.current);
  }

  render() {
    return <Clock ref={this.clockRef} />;
  }
}

Pure Components

Let's discuss a simple Greetings React.Component first.

class Greetings extends React.Component {
  render() {
    console.count('Greetings --> render');
    return <p>Hello {this.props.name}!</p>;
  }
}

It greets with a name passed as props. An additional console.count statement is added to render method to count executions.

The App component below takes name from a form input control and passes it to the Greetings component.

class App extends React.Component {
  state = { name: 'Sheldon', text: '' };

  handleChange = event => {
    this.setState({ text: event.target.value });
  };

  handleSubmit = event => {
    event.preventDefault();
    this.setState({ text: '', name: this.state.text });
  };

  render() {
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <input
            type="text"
            value={this.state.text}
            required
            onChange={this.handleChange}
          />
          <input type="submit" value="Greet" />
        </form>
        <Greetings name={this.state.name} />
      </div>
    );
  }
}

When a user interacts with the input control, it updates the state of the App component. React invokes the render method--with the updated state and props--of the App component and its children to create a new React Element tree for diffing. Although, the state and props of the Greetings component are not changed, still React calls the render method of the Greetings component.

In large applications, such unnecessary executions of render methods create performance issues and bog down user interfaces. The shouldComponentUpdate life-cycle method is used to avoid these unnecessary re-renderings of the component. By default, shouldComponentUpdate return true, but its implementation can be easily overridden. Let's override shouldComponentUpdate for the Greetings component.

class Greetings extends React.Component {
  shouldComponentUpdate(nextProps) {
    // Re-render only when the `name` prop changes.
    return this.props.name !== nextProps.name;
  }

  render() {
    console.count('Greetings --> render');
    return <p>Hello {this.props.name}!</p>;
  }
}

After the very first render, Greetings component is re-rendered only when the name prop changes.

To solve the same problem, React introduces a variant of React.Component called React.PureComponent which implicitly implements shouldComponentUpdate. The implicit implementation compares props and state by reference(shallow comparison). Let's write the pure version of Greetings.

class PureGreetings extends React.PureComponent {
  render() {
    console.count('Pure Greetings --> render');
    return <span>Hello {this.props.name}!</span>;
  }
}

Here's the pen with full code.

Controlled/Uncontrolled Components

Working with form elements is a tad tedious. It requires a lot of malarky to get data from the form elements. That's because form elements maintain their own state internally. Developers have to throw a few lines to JavaScript to get the job done. Form elements in React are no exception. The way developers deal with a form element determines whether that element is a Controlled or Uncontrolled Element/Component. If the value of a form element is controlled by React then it's called a "Controlled Component" otherwise "Uncontrolled Component".

Controlled Components don't change their state on user interaction. State changes happen only when the parent component decides eg. the SubscriptionForm component below doesn't honor user inputs (Codepen).

class SubscriptionForm extends React.Component {
  handleSubmit = event => {
    event.preventDefault();
  };
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="email" value="smelly@smellycode.com" />
        <input type="submit" value="Subscribe" />
      </form>
    );
  }
}

Why changes are not honored? That's because the value attribute for the email input is set to smelly@smellycode.com. When React runs the diffing algorithm on the render tree. It always gets the email input as smelly@smellycode.com so it ends up rendering the same value regardless of inputs entered by the user. Let's fix it by setting up an event listener which will update the state on change event(Codepen).

class SubscriptionForm extends React.Component {
  state = { email: '' };

  handleSubmit = event => {
    event.preventDefault();
    console.log('Values --> ', this.state);
  };

  handleChange = event => this.setState({ email: event.target.value });

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input
          type="email"
          value={this.state.email}
          onChange={this.handleChange}
        />
        <input type="submit" value="Subscribe" />
      </form>
    );
  }
}

Everything that goes into the input form elements is controlled by React here. That's why it is called "Controlled Component".

For "Uncontrolled Component", form data is not handled by React. DOM takes care of them. Here's an uncontrolled version of the SubscriptionForm.

class SubscriptionForm extends React.Component {
  inputRef = React.createRef();

  handleSubmit = event => {
    event.preventDefault();
    console.log('Value -->', this.inputRef.current.value);
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="email" ref={this.inputRef} />
        <input type="submit" value="Subscribe" />
      </form>
    );
  }
}

For elaborative comparison please refer the article.

Higher Order Components

Suppose there's an application which has a few malformed components--components whose elements/children are invalid react elements. Rendering of these components breaks the user interface.

// A sample malformed component.
class MalformedComponent extends React.Component {
  render() {
    // {new Date()} is not a valid react element. Rendering it will throw an error.
    return <p>Now:{new Date()}</p>;
  }
}

We need to implement an error handling mechanism to avoid crashes. React provides error boundary apis to handle such errors. So we refactor MalformedComponent as:

class MalformedComponent extends React.Component {
  state = {
    error: null
  };

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { error };
  }

  render() {
    if (this.state.error) {
      return (
        <details>
          <summary>Ouch! Things are messed up. We are sorry. 👾</summary>
          <pre style={{ color: `red` }}>{this.state.error.stack}</pre>
        </details>
      );
    }
    return <WrappedComponent {...this.props} />;
  }
}

Adding error boundaries only fixes the MalformedComponent. We need to fix the other components too, means we need to add error boundaries to other components.

How do we do it? Hmm, One way is to add the error handling code in every malformed component the way we did above. But it will make our component a bit cumbersome to maintain and less DRY.

What if we write a function to fill in the error handling code? Well, we can write but we shouldn't because we'll be modifying the existing component which is not recommended and may lead to unexpected behavior.

What if we write a function which takes a malformed component and returns a new component which wraps the malformed component with error boundaries? Interesting! Only thing is, it will add a new wrapper component in our component tree, but we can live with it. Let's code it.

const withErrorBoundaries = WrappedComponent => props => {
  return class extends React.Component {
    state = {
      error: null
    };

    static getDerivedStateFromError(error) {
      // Update state so the next render will show the fallback UI.
      return { error };
    }

    render() {
      if (this.state.error) {
        // Fallback ui.
        return (
          <details>
            <summary>Ouch! Things are messed up. We are sorry. 👾</summary>
            <pre style={{ color: `red` }}>{this.state.error.stack}</pre>
          </details>
        );
      }
      return <WrappedComponent {...this.props} />;
    }
  };
};

withErrorBoundaries can be used with any malformed component.

const SafeComponent = withErrorBoundaries(MalformedComponent);

That's what precisely a higher order component is all about. It's a pattern which facilitates component logic reusability. You can think of a HOC as a function that takes a component and returns a new component. An in-depth explanation of HOCs is available here.

Dumb Components

Dumb components are also known as presentational or stateless components. They mostly contain HTML and styles. The purpose of dumb components is to render the DOM using props. Dumb Components don't load or mutate any data. Data required by dumb components are passed as input/props along with the actions. That's why dumb components don't have any state related to data. It makes them more reusable and manageable. Here is a very basic Greetings dumb component :

function Greetings(props) {
  return <h1>Hello {props.name}</h1>;
}

Smart/Container Components

Smart components are also known as Container Components. Smart Components know how to load and mutate data. Sometimes smart components mere act as a container and pass data to child components as props. Smart Components can also have state and logic to update the state. A simple Clock component with state and logic.

class Clock extends React.Component {
  state = { now: new Date() };

  intervalId = null;

  tick = () => this.setState({ now: new Date() });

  componentDidMount() {
    this.intervalId = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.intervalId);
  }

  render() {
    return <p>{this.state.now.toLocaleTimeString()}</p>;
  }
}

You can read more about Dumb Components and Smart components on Shade.codes.

Top comments (0)