ReactJS – 9 Wonderful Useful Criteria Of A Reliable React Component

Hello Readers, CoolMonkTechie heartily welcomes you in this article.

In this article, We will learn about nine useful criteria of A React Component in ReactJS. We want to know how React embraces component-based architecture. We can compose complex user interfaces from smaller pieces, take advantage of components reusability and abstracted DOM manipulations.

We know about React : “Component-based development is productive: a complex system is built from specialized and easy to manage pieces. Yet only well designed components ensure composition and reusability benefits.

Despite the application complexity, we hurry to meet the deadlines and unexpectedly changing requirements, we must constantly walk on the thin line of architectural correctness. Make our components decoupled, focused on a single task, well tested.

Luckily, reliable components have common characteristics. We will discuss below 9 useful criterias with details.

  • Single responsibility
  • Encapsulated
  • Composable
  • Reusable
  • Pure or Almost Pure
  • Testable and Tested
  • Meaningful
  • Do Continuous Improvement
  • Reliability

A famous quote about learning is :

” Try to learn something about everything and everything about something.”


So Let’s begin.

When writing a React application, We need regularly ask questions ourself:

  • How to correctly structure the component?
  • At what point a big component should split into smaller components?
  • How to design a communication between components that prevents tight coupling?

Because Technical debt making progressively hard to modify existing or create new functionality. This will happen when write big components with many responsibilities, tightly couple components, forget about unit tests. These increase technical debt.

So we can solve this issue with the use of a reliable react component criterias. So we look these criterias one by one.


1. Single responsibility

A fundamental rule to consider when writing React components is the single responsibility principle.

” A component has a single responsibility when it has one reason to change. “

Single responsibility principle (abbreviated SRP) requires a component to have one reason to change.

A component has one reason to change when it implements one responsibility, or simpler when it does one thing.

A responsibility is either to render a list of items, or to show a date picker, or to make an HTTP request, or to draw a chart, or to lazy load an image, etc. Our component should pick only one responsibility and implement it. When we modify the way component implements its responsibility (e.g. a change to limit the number of items for render a list of items responsibility) – it has one reason to change.

Why is it important to have only one reason to change? Because component’s modification becomes isolated and under control.

Having one responsibility restricts the component size and makes it focused on one thing. A component focused on one thing is convenient to code, and later modify, reuse and test.

Let’s follow a few examples.

Example 1 :- A component fetches remote data, correspondingly it has one reason to change when fetch logic changes.

A reason to change happens when:

  • The server URL is modified
  • The response format is modified
  • You want to use a different HTTP requests library
  • Or any modification related to fetch logic only.

Example 2 :- A table component maps an array of data to a list of row components, as result having one reason to change when mapping logic changes.

A reason to change occurs when:

  • We have a task to limit the number of rendered row components (e.g. display up to 25 rows)
  • We’re asked to show a message “The list is empty” when there are no items to display
  • Or any modification related to mapping of array to row components only.

Does our component have many responsibilities? If the answer is yes, split the component into chunks by each individual responsibility.

An alternative reasoning about the single responsibility principle says to create the component around a clearly distinguishable axis of change. An axis of change attracts modifications of the same meaning.

In the previous 2 examples, the axis of change were fetch logic and mapping logic.

Units written at early project stage will change often until reaching the release stage. These change often components are required to be easily modifiable in isolation: a goal of the SRP.


Case study: make component have one responsibility

Imagine a component that makes an HTTP request to a specialized server to get the current weather. When data is successfully fetched, the same component uses the response to display the weather:

import axios from 'axios';
// Problem: A component with multiple responsibilities 
class Weather extends Component {
   constructor(props) {
     super(props);
     this.state = { temperature: 'N/A', windSpeed: 'N/A' };
   }
 
   render() {
     const { temperature, windSpeed } = this.state;
     return (
       <div className="weather">
         <div>Temperature: {temperature}°C</div>
         <div>Wind: {windSpeed}km/h</div>
       </div>
     );
   }
   
   componentDidMount() {
     axios.get('http://weather.com/api').then(function(response) {
       const { current } = response.data; 
       this.setState({
         temperature: current.temperature,
         windSpeed: current.windSpeed
       })
     });
   }
}

When dealing with alike situations, ask ourself: do we have to split the component into smaller pieces? The question is best answered by determining how component might change according to its responsibilities.

The weather component has 2 reasons to change:

  1. Fetch logic in componentDidMount(): server URL or response format can be modified
  2. Weather visualization in render(): the way component displays the weather can change several times

The solution is to divide <Weather> in 2 components: each having one responsibility. Let’s name the chunks <WeatherFetch> and <WeatherInfo>.

First component <WeatherFetch> is responsible for fetching the weather, extracting response data and saving it to state. It has one fetch logic reason to change:

import axios from 'axios';
// Solution: Make the component responsible only for fetching
class WeatherFetch extends Component {
   constructor(props) {
     super(props);
     this.state = { temperature: 'N/A', windSpeed: 'N/A' };
   }
 
   render() {
     const { temperature, windSpeed } = this.state;
     return (
       <WeatherInfo temperature={temperature} windSpeed={windSpeed} />
     );
   }
   
   componentDidMount() {
     axios.get('http://weather.com/api').then(function(response) {
       const { current } = response.data; 
       this.setState({
         temperature: current.temperature,
         windSpeed: current.windSpeed
       });
     });
   }
}

What benefits brings such structuring?

For instance, we would like to use async/await syntax instead of promises to get the response from server. This is a reason to change related to fetch logic:

// Reason to change: use async/await syntax
class WeatherFetch extends Component {
   // ..... //
   async componentDidMount() {
     const response = await axios.get('http://weather.com/api');
     const { current } = response.data; 
     this.setState({
       temperature: current.temperature,
       windSpeed: current.windSpeed
     });
   }
}

Because <WeatherFetch> has one fetch logic reason to change, any modification of this component happens in isolation. Using async/await does not affect directly the way weather is displayed.

Then <WeatherFetch> renders <WeatherInfo>. The latter is responsible only for displaying the weather, having one visual reason to change:

// Solution: Make the component responsible for displaying the weather
function WeatherInfo({ temperature, windSpeed }) {
   return (
     <div className="weather">
       <div>Temperature: {temperature}°C</div>
       <div>Wind: {windSpeed} km/h</div>
     </div>
   );
}

Let’s change <WeatherInfo> that instead of "Wind: 0 km/h" display "Wind: calm". That’s a reason to change related to visual display of weather:

// Reason to change: handle calm wind  
function WeatherInfo({ temperature, windSpeed }) {
   const windInfo = windSpeed === 0 ? 'calm' : `${windSpeed} km/h`;
   return (
     <div className="weather">
       <div>Temperature: {temperature}°C</div>
       <div>Wind: {windInfo}</div>
     </div>
   );
}

Again, this modification of <WeatherInfo> happens in isolation and does not affect <WeatherFetch> component.

<WeatherFetch> and <WeatherInfo> have their own one responsibility. A change of one component has small effect on the other one. That’s the power of single responsibility principle: modification in isolation that affects lightly and predictability other components of the system.


2. Encapsulated

An encapsulated component provides props to control its behavior while not exposing its internal structure. “

Coupling is a system characteristic that determines the degree of dependency between components.

Based on the degree of components dependence, 2 coupling types are distinguishable:

  • Loose coupling happens when the application components have little or no knowledge about other components.
  • Tight coupling happens when the application components know a lot of details about each other.

Loose coupling is the goal when designing application’s structure and the relationship between components.

Loose coupling leads to the following benefits:

  • Allow making changes in one area of the application without affecting others
  • Any component can be replaced with an alternative implementation
  • Enables components reusability across the application, thus favoring Don’t repeat yourself principle
  • Independent components are easier to test, increasing the application code coverage

Contrary, a tightly coupled system looses the benefits described above. The main drawback is the difficulty to modify a component that is highly dependent on other components. Even a single modification might lead to a cascade of dependency echo modifications.

Encapsulation, or Information Hiding, is a fundamental principle of how to design components, and is the key to loose coupling.

Information hiding

A well encapsulated component hides its internal structure and provides a set of props to control its behavior.

Hiding internal structure is essential. Other components are not allowed to know or rely on the component’s internal structure or implementation details.

A React component can be functional or class based, define instance methods, setup refs, have state or use lifecycle methods. These implementation details are encapsulated within the component itself, and other components shouldn’t know anything about these details.

Units that precisely hide their internal structure are less dependent on each other. Lowering the dependency degree brings the benefits of loose coupling.

Communication

Details hiding is a restriction that isolates the component. Nevertheless, we need a way to make components communicate. So welcome the props.

Props are meant to be plain, raw data that are component’s input.

A prop is recommended to be a primitive type (e.g. string, number, boolean):

<Message text="Hello world!" modal={false} />;

When necessary use a complex data structure like objects or arrays:

<MoviesList items={['Batman Begins', 'Blade Runner']} />

Prop as a function handles events and async behavior:

<input type="text" onChange={handleChange} />

A prop can be even a component constructor. A component can take care of other component’s instantiation:

function If({ component: Component, condition }) {
  return condition ? <Component /> : null;
}
<If condition={false} component={LazyComponent} /> 

To avoid breaking encapsulation, watch out the details passed through props. A parent component that sets child props should not expose any details about its internal structure. For example, it’s a bad decision to transmit using props the whole component instance or refs.

Accessing global variables is another problem that negatively affects encapsulation.


Case study: encapsulation restoration

Component’s instance and state object are implementation details encapsulated inside the component. Thus a certain way to break the encapsulation is to pass the parent instance for state management to a child component.

Let’s study such a situation.

A simple application shows a number and 2 buttons. First button increases and second button decreases the number. The application consists of two components: <App> and <Controls>.

<App> holds the state object that contains the modifiable number as a property, and renders this number:

// Problem: Broken encapsulation
class App extends Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }
  
  render() {
    return (
      <div className="app"> 
        <span className="number">{this.state.number}</span>
        <Controls parent={this} />
      </div>
    );
  }
}

<Controls> renders the buttons and attaches click event handlers to them. When user clicks a button, parent component state is updated (updateNumber() method) by increasing +1 or decreasing -1 the displayed number:

// Problem: Using internal structure of parent component
class Controls extends Component {
  render() {
    return (
      <div className="controls">
        <button onClick={() => this.updateNumber(+1)}>
          Increase
        </button> 
        <button onClick={() => this.updateNumber(-1)}>
          Decrease
        </button>
      </div>
    );
  }
  
  updateNumber(toAdd) {
    this.props.parent.setState(prevState => ({
      number: prevState.number + toAdd       
    }));
  }
}

What is wrong with the current implementation?

  • The first problem is <App>’s broken encapsulation, since its internal structure spreads across the application. <App> incorrectly permits <Controls> to update its state directly.
  • Consequently, the second problem is that <Controls> knows too many details about its parent <App>. It has access to parent instance, knows that parent is a stateful component, knows the state object structure (number property) and knows how to update the state.

The broken encapsulation couples <App> and <Controls> components.

A troublesome outcome is that <Controls> would be complicated to test and reuse. A slight modification to structure of <App> leads to cascade of modifications to <Controls> (and to alike coupled components in case of a bigger application).

The solution is to design a convenient communication interface that respects loose coupling and strong encapsulation. Let’s improve the structure and props of both components in order to restore the encapsulation.

Only the component itself should know its state structure. The state management of <App> should move from <Controls> (updateNumber() method) in the right place: <App> component.

Later, <App> is modified to provide <Controls> with props onIncrease and onDecrease. These are simple callbacks that update <App> state:

// Solution: Restore encapsulation
class App extends Component {  
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }

  render() {
    return (
      <div className="app"> 
        <span className="number">{this.state.number}</span>
        <Controls 
          onIncrease={() => this.updateNumber(+1)}
          onDecrease={() => this.updateNumber(-1)} 
        />
      </div>
    );
  }

  updateNumber(toAdd) {
    this.setState(prevState => ({
      number: prevState.number + toAdd       
    }));
  }
}

Now <Controls> receives callbacks for increasing and decreasing the number. Notice the decoupling and encapsulation restoration moment: <Controls> has no longer the need to access parent instance and modify <App> state directly.

Moreover <Controls> is transformed into a functional component:

// Solution: Use callbacks to update parent state
function Controls({ onIncrease, onDecrease }) {
  return (
    <div className="controls">
      <button onClick={onIncrease}>Increase</button> 
      <button onClick={onDecrease}>Decrease</button>
    </div>
  );
}

<App> encapsulation is now restored. The component manages its state by itself, as it should be.

Furthermore <Controls> no longer depends on <App> implementation details. onIncrease and onDecrease prop functions are called when corresponding button is clicked, and <Controls> does not know (and should not know) what happens inside those functions.

<Controls> reusability and testability significantly increased.

The reuse of <Controls> is convenient because it requires only callbacks, without any other dependencies. Testing is also handy: just verify whether callbacks are executed on buttons click.


3. Composable

“A composable component is created from the composition of smaller specialized components.”

Composition is a way to combine components to create a bigger (composed) component. Composition is the heart of React.

Fortunately, composition is easy to understand. Take a set of small pieces, combine them, and create a bigger thing.

Let’s look at a common frontend application composition pattern. The application is composed of a header at the top, footer at the bottom, sidebar on the left and payload content in the middle:

The application demonstrates how well composition builds the application. Such organization is expressive and open for understanding.

React composes components expressively and naturally. The library uses a declarative paradigm that doesn’t suppress the expressiveness of composition. The following components render the described application:

const app = (
  <Application>
    <Header />
    <Sidebar>
      <Menu />
    </Sidebar>
    <Content>
      <Article />
    </Content>
    <Footer />
  </Application>
);

<Application> is composed of <Header><Sidebar><Content> and <Footer>.
<Sidebar> has one component <Menu>, as well as <Content> has one <Article>.

How does composition relate with single responsibility and encapsulation?

Single responsibility principle describes how to split requirements into components, encapsulation describes how to organize these components, and composition describes how to glue the whole system back. “

Composition benefits


Single responsibility

An important aspect of composition is the ability to compose complex components from smaller specialized components. This divide and conquer approach helps an authority component conform to single responsibility principle.

Recall the previous code snippet. <Application> has the responsibility to render the header, footer, sidebar and main regions.

Makes sense to divide this responsibility into four sub-responsibilities, each of which is implemented by specialized components <Header><Sidebar><Content> and <Footer>. Later composition glues back <Application> from these specialized components.

Now comes up the benefit. Composition makes <Application> conform to single responsibility principle, by allowing its children to implement the sub-responsibilities.

Reusability

Components using composition can reuse common logic. This is the benefit of reusability.

For instance, components <Composed1> and <Composed2> share common code:

const instance1 = (
  <Composed1>
    /* Specific to Composed1 code... */
    /* Common code... */
  </Composed1>
);
const instance2 = (
  <Composed2>
    /* Common code... */
    /* Specific to Composed2 code... */
  </Composed2>
);

Since code duplication is a bad practice, how to make components reuse common code?

Firstly, encapsulate common code in a new component <Common>. Secondly, <Composed1> and <Composed2> should use composition to include <Common>, fixing code duplication:

const instance1 = (
  <Composed1>
    <Piece1 />
    <Common />
  </Composed1>
);
const instance2 = (
  <Composed2>
    <Common />
    <Piece2 />
  </Composed2>
);

Reusable components favor Don’t repeat yourself (DRY) principle. This beneficial practice saves efforts and time.

Flexibility

In React a composable component can control its children, usually through children prop. This leads to another benefit of flexibility.

For example, a component should render a message depending on user’s device. Use composition’s flexibility to implement this requirement:

function ByDevice({ children: { mobile, other } }) {
  return Utils.isMobile() ? mobile : other;
}

<ByDevice>{{
  mobile: <div>Mobile detected!</div>,
  other:  <div>Not a mobile device</div>
}}</ByDevice>

<ByDevice> composed component renders the message "Mobile detected!" for a mobile, and "Not a mobile device" for other devices.

Efficiency

User interfaces are composable hierarchical structures. Thus composition of components is an efficient way to construct user interfaces.


4. Reusable

“A reusable component is written once but used multiple times.”

Imagine a fantasy world where software development is mostly reinventing the wheel.

When coding, we can’t use any existing libraries or utilities. Even across the application we can’t use code that we already wrote.

In such environment, would it be possible to write an application in a reasonable amount of time? Definitely not.

Welcome reusability. Make things work, not reinvent how they work.

Reuse across application

According to Don’t repeat yourself (DRY) principle, every piece of knowledge must have a single, unambiguous, authoritative representation within a system. The principle advises to avoid repetition.

Code repetition increases complexity and maintenance efforts without adding significant value. An update of the logic forces you to modify all its clones within the application.

Repetition problem is solved with reusable components. Write once and use many times: efficient and time saving strategy.

However we don’t get reusability property for free. A component is reusable when it conforms to single responsibility principle and has correct encapsulation.

Conforming to single responsibility is essential:

Reuse of a component actually means the reuse of its responsibility implementation.”

Components that have only one responsibility are the easiest to reuse.

But when a component incorrectly has multiple responsibilities, its reusage adds a heavy overhead. We want to reuse only one responsibility implementation, but also we get the unneeded implementation of out of place responsibilities.

We want a banana, and we get a banana, plus all the jungle with it.

Correct encapsulation creates a component that doesn’t stuck with dependencies. Hidden internal structure and focused props enable the component to fit nicely in multiple places where it’s about to be reused.

Reuse of 3rd party libraries

A regular working day. We’ve just read the task to add a new feature to the application. Before firing up the text editor, hold on for a few minutes…

There’s a big chance that the problem we start working on is already solved. Due to React’s popularity and great open source community, it worth searching for an existing solution.

Good libraries positively affect architectural decisions and advocate best practices. In my experience, the top influencers are react-router and redux.

react-router uses declarative routing to structure a Single Page Application.
Associate a URL path with your component using <Route>. Then router will render the component for you when user visits the matched URL.

redux and react-redux HOC introduce unidirectional and predictable application state management. It extracts async and impure code (like HTTP requests) out of components, favoring single responsibility principle and creating pure or almost-pure components.

To be sure that a 3rd party library is worth using, here’s we need to verify checklist details:

  • Documentation: verify whether the library has meaningful readme.md file and detailed documentation
  • Tested: a sign of trustworthy library is high code coverage
  • Maintenance: see how often the library author creates new features, fixes bugs and generally maintains the library.


5. Pure or Almost-pure

“A pure component always renders same elements for same prop values.”

An almost-pure component always renders same elements for same prop values, and can produce a side effect.”

In functional programming terms, a pure function always returns the same output for given the same input. Let’s see a simple pure function:

function sum(a, b) {
  return a + b;
}
sum(5, 10); // => 15

For given two numbers, sum() function always returns the same sum.

A function becomes impure when it returns different output for same input. It can happen because the function relies on global state. For example:

let said = false;

function sayOnce(message) {
  if (said) {
    return null;
  }
  said = true;
  return message;
}

sayOnce('Hello World!'); // => 'Hello World!'
sayOnce('Hello World!'); // => null

sayOnce('Hello World!') on first call returns 'Hello World!'.

Even when using same argument 'Hello World!', on later invocations sayOnce() returns null. That’s the sign of an impure function that relies on a global state: said variable.

sayOnce() body has a statement said = true that modifies the global state. This produces a side effect, which is another sign of impure function.

Consequently, pure functions have no side effects and don’t rely on global state. Their single source of truth are parameters. Thus pure functions are predictable and determined, are reusable and straightforward to test.

React components should benefit from pure property. Given the same prop values, a pure component (not to be confused with React.PureComponent) always renders the same elements. Let’s take a look:

function Message({ text }) {
  return <div className="message">{text}</div>;
}

<Message text="Hello World!" /> 
// => <div class="message">Hello World</div>

You are guaranteed that <Message> for the same text prop value renders the same elements.

It’s not always possible to make a component pure. Sometimes we have to ask the environment for information, like in the following case:

class InputField extends Component {
  constructor(props) {
    super(props);
    this.state = { value: '' };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange({ target: { value } }) {
    this.setState({ value });
  }

  render() {
    return (
      <div>
         <input 
           type="text" 
           value={this.state.value} 
           onChange={this.handleChange} 
         />
         You typed: {this.state.value}
      </div>
    );
  }
}

<InputField> stateful component doesn’t accept any props, however renders different output depending on what user types into the input. <InputField> has to be impure, because it accesses the environment through input field.

Impure code is a necessary evil. Most of the applications require global state, network requests, local storage and alike. What we can do is isolate impure code from pure, a.k.a. apply purification on our components.

Isolated impure code explicitly shows it has side effects, or rely on global state. Being in isolation, impure code has less unpredictability effect on the rest of the system.

Let’s detail into purification examples.


Case study: purification from global variables

We don’t like global variables. They break encapsulation, create unpredictable behavior and make testing difficult.

Global variables can be used as mutable or immutable (read-only) objects.

Mutating global variables create uncontrolled behavior of components. Data is injected and modified at will, confusing reconciliation process. This is a mistake.

If we need a mutable global state, the solution is a predictable application state management. Consider using Redux.

An immutable (or read-only) usage of globals is often application’s configuration object. This object contains the site name, logged-in user name or any other configuration information.

The following statement defines a configuration object that holds the site name:

export const globalConfig = {
  siteName: 'Animals in Zoo'
};

Next, <Header> component renders the header of an application, including the display of site name "Animals in Zoo":

import { globalConfig } from './config';

export default function Header({ children }) {
  const heading = 
    globalConfig.siteName ? <h1>{globalConfig.siteName}</h1> : null;
  return (
     <div>
       {heading}
       {children}
     </div>
  );
}

<Header> component uses globalConfig.siteName to render site name inside a heading tag <h1>. When site name is not defined (i.e. null), the heading is not displayed.

The first to notice is that <Header> is impure. Given same value of children, the component returns different results because of globalConfig.siteName variations:

// globalConfig.siteName is 'Animals in Zoo'
<Header>Some content</Header>
// Renders:
<div>
  <h1>Animals in Zoo</h1>
  Some content
</div>

or

// globalConfig.siteName is `null`
<Header>Some content</Header>
// Renders:
<div>
  Some content
</div>

The second problem is testing difficulties. To test how component handles null site name, we have to modify the global variable globalConfig.siteName = null manually:

import assert from 'assert';
import { shallow } from 'enzyme';
import { globalConfig } from './config';
import Header from './Header';

describe('<Header />', function() {
  it('should render the heading', function() {
    const wrapper = shallow(
      <Header>Some content</Header>
    );
    assert(wrapper.contains(<h1>Animals in Zoo</h1>));
  });

  it('should not render the heading', function() {
    // Modification of global variable:
    globalConfig.siteName = null;
    const wrapper = shallow(
      <Header>Some content</Header>
    );
    assert(appWithHeading.find('h1').length === 0);
  });
});

The modification of global variable globalConfig.siteName = null for sake of testing is hacky and uncomfortable. It happens because <Heading> has a tight dependency on globals.

To solve such impurities, rather than injecting globals into component’s scope, make the global variable an input of the component.

Let’s modify <Header> to accept one more prop siteName. Then wrap the component with defaultProps() higher order component (HOC) from recompose library. defaultProps() ensures fulfilling the missing props with default values:

import { defaultProps } from 'recompose';
import { globalConfig } from './config';

export function Header({ children, siteName }) {
  const heading = siteName ? <h1>{siteName}</h1> : null;
  return (
     <div className="header">
       {heading}
       {children}
     </div>
  );
}

export default defaultProps({
  siteName: globalConfig.siteName
})(Header);

<Header> becomes a pure functional component, and does not depend directly on globalConfig variable. The pure version is a named export: export function Header() {...}, which is useful for testing.

At the same time, the wrapped component with defaultProps({...}) sets globalConfig.siteName when siteName prop is missing. That’s the place where impure code is separated and isolated.

Let’s test the pure version of <Header> (remember to use a named import):

import assert from 'assert';
import { shallow } from 'enzyme';
import { Header } from './Header'; // Import the pure Header

describe('<Header />', function() {
  it('should render the heading', function() {
    const wrapper = shallow(
      <Header siteName="Animals in Zoo">Some content</Header>
    );
    assert(wrapper.contains(<h1>Animals in Zoo</h1>));
  });

  it('should not render the heading', function() {
    const wrapper = shallow(
      <Header siteName={null}>Some content</Header>
    );
    assert(appWithHeading.find('h1').length === 0);
  });
});

This is great. Unit testing of pure <Header> is straightforward. The test does one thing: verify whether the component renders the expected elements for a given input. No need to import, access or modify global variables, no side effects magic.

Well designed components are easy to test , which is visible in case of pure components.


6. Testable and Tested

“A tested component is verified whether it renders the expected output for a given input.

“A testable component is easy to test. “

How to be sure that a component works as expected? We can say: “We manually verify how it works.”

If we plan to manually verify every component modification, sooner or later we’re going to skip this tedious task. Sooner or later small defects are going to make through.

That’s why is important to automate the verification of components: do unit testing. Unit tests make sure that our components are working correctly every time we make a modification.

Unit testing is not only about early bugs detection. Another important aspect is the ability to verify how well components are built architecturally.

The following statement I find especially important:

“A component that is untestable or hard to test is most likely badly designed. “

A component is hard to test because it has a lot of props, dependencies, requires mockups and access to global variables: that’s the sign of a bad design.

When the component has weak architectural design, it becomes untestable. When the component is untestable, we simply skip writing unit tests: as result it remains untested.

In conclusion, the reason why many applications are untested is incorrectly designed components. Even if we want to test such an application, we can’t.


Case study: testable means well designed

Let’s test 2 versions of <Controls> from the encapsulation point.

The following code tests <Controls> version that highly depends on the parent’s component structure:

import assert from 'assert';
import { shallow } from 'enzyme';

class Controls extends Component {
  render() {
    return (
      <div className="controls">
        <button onClick={() => this.updateNumber(+1)}>
          Increase
        </button> 
        <button onClick={() => this.updateNumber(-1)}>
          Decrease
        </button>
      </div>
    );
  }
  updateNumber(toAdd) {
    this.props.parent.setState(prevState => ({
      number: prevState.number + toAdd       
    }));
  }
}

class Temp extends Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }
  render() {
    return null;
  }
}

describe('<Controls />', function() {
  it('should update parent state', function() {
    const parent = shallow(<Temp/>);
    const wrapper = shallow(<Controls parent={parent} />);

    assert(parent.state('number') === 0);

    wrapper.find('button').at(0).simulate('click');
    assert(parent.state('number') === 1);

    wrapper.find('button').at(1).simulate('click');
    assert(parent.state('number') === 0); 
  });
});

<Controls> is complicated to test, since it relies on parent’s component implementation details.

The test scenario requires an additional component <Temp>, which emulates the parent. It permits to verify whether <Controls> modifies correctly parent’s state.

When <Controls> is independent of parent details, testing is easier. Let’s test the version with correct encapsulation:

import assert from 'assert';
import { shallow } from 'enzyme';
import { spy } from 'sinon';

function Controls({ onIncrease, onDecrease }) {
  return (
    <div className="controls">
      <button onClick={onIncrease}>Increase</button> 
      <button onClick={onDecrease}>Decrease</button>
    </div>
  );
}

describe('<Controls />', function() {
  it('should execute callback on buttons click', function() {
    const increase = sinon.spy();
    const descrease = sinon.spy();
    const wrapper = shallow(
      <Controls onIncrease={increase} onDecrease={descrease} />
    );

    wrapper.find('button').at(0).simulate('click');
    assert(increase.calledOnce);
    wrapper.find('button').at(1).simulate('click');
    assert(descrease.calledOnce);
  });
});

Strong encapsulation leads to easy and straightforward testing. And contrary a component with incorrect encapsulation is difficult to test.

Testability is a practical criteria to identify how well our components are structured.


7. Meaningful

meaningful component is easy to understand what it does.

It’s hard underestimate the importance of readable code. How many times did we stuck with obscured code? We see the characters, but don’t see the meaning.

Developer spends most of the time reading and understanding code, than actually writing it. Coding activity is 75% of time understanding code, 20% of time modifying existing code and only 5% writing new source.

A slight additional time spent on readability reduces the understanding time for teammates and ourself in the future. The naming practice becomes important when the application grows, because understanding efforts increase with volume of code.

Reading meaningful code is easy. Nevertheless writing meaningfully requires clean code practices and constant effort to express ourself clearly.

Component naming

Pascal case

Component name is a concatenation of one or more words (mostly nouns) in pascal case. For instance <DatePicker><GridItem><Application><Header>.

Specialization

The more specialized a component is, the more words its name might contain.

A component named <HeaderMenu> suggests a menu in the header. A name <SidebarMenuItem> indicates a menu item located in sidebar.

A component is easy to understand when the name meaningfully implies the intent. To make this happen, often we have to use verbose names. That’s fine: more verbose is better than less clear.

Suppose we navigate some project files and identify 2 components: <Authors> and <AuthorsList>. Based on names only, can we conclude the difference between them? Most likely not.

To get the details, you have to open <Authors> source file and explore the code. After doing that, we realize that <Authors> fetches authors list from server and renders <AuthorsList> presentational component.

A more specialized name instead of <Authors> doesn’t create this situation. Better names are <FetchAuthors><AuthorsContainer> or <AuthorsPage>.

One word – one concept

A word represents a concept. For example, a collection of rendered items concept is represented by list word.

Pick one word per concept, then keep the relation consistent within the whole application. The result is a predicable mental mapping of words – concepts that you get used to.

Readability suffers when the same concept is represented by many words. For example, we define a component that renders a list of orders <OrdersList>, and another that renders a list of expenses <ExpensesTable>.

The same concept of a collection of rendered items is represented by 2 different words: list and table. There’s no reason to use different words for the same concept. It adds confusion and breaks consistency in naming.

Name the components <OrdersList> and <ExpensesList> (using list word) or <OrdersTable> and <ExpensesTable> (using table word). Use whatever word we feel is better, just keep it consistent.

Code comments

Meaningful names for components, methods and variables are enough for making the code readable. Thus, comments are mostly redundant.


Case study: write self-explanatory code

Common misuse of comments is explanation of inexpressive and obscured naming. Let’s see such case:

// <Games> renders a list of games
// "data" prop contains a list of game data
function Games({ data }) {
  // display up to 10 first games
  const data1 = data.slice(0, 10);
  // Map data1 to <Game> component
  // "list" has an array of <Game> components
  const list = data1.map(function(v) {
    // "v" has game data
    return <Game key={v.id} name={v.name} />;
  });
  return <ul>{list}</ul>;
}

<Games 
   data=[{ id: 1, name: 'Mario' }, { id: 2, name: 'Doom' }] 
/>

Comments in the above example clarify obscured code. <Games>datadata1v, magic number 10 are inexpressive and difficult to understand.

If we refactor the component to have meaningful props and variables, the comments are easily omitted:

const GAMES_LIMIT = 10;

function GamesList({ items }) {
  const itemsSlice = items.slice(0, GAMES_LIMIT);
  const games = itemsSlice.map(function(gameItem) {
    return <Game key={gameItem.id} name={gameItem.name} />;
  });
  return <ul>{games}</ul>;
}

<GamesList 
  items=[{ id: 1, name: 'Mario' }, { id: 2, name: 'Doom' }]
/>

Don’t explain ourself with comments. Write code that is self-explanatory and self-documenting.

Expressiveness stairs

We distinguish 4 expressiveness stairs of a component. The lower we move on the stairs, the more effort we need to understand the component.

We can understand what the component does from:

  1. Reading name and props;
  2. Consulting documentation;
  3. Exploring the code;
  4. Asking the author.

If name and props give enough information to integrate the component into application, that’s a solid expressiveness. Try to keep this high quality level.

Some components have complex logic, and even a good name can’t give the necessary details. It’s fine to consult the documentation.

If documentation is missing or doesn’t answer all the questions, We have to explore the code. Not the best option because of additional time spent, but it’s acceptable.

When exploring code doesn’t help decipher the component, the next step is asking component’s author for details. That’s definitely bad naming, and avoid going down to this step. Better ask the author to refactor the code, or refactor it yourself.


8. Do continuous improvement

“We then said that rewriting is the essence of writing. We pointed out that professional writers rewrite their sentences over and over and then rewrite what they have rewritten.”

To produce a quality text, we have to rewrite our sentence multiple times. Read the written, simplify confusing places, use more synonyms, remove clutter words – then repeat until we have an enjoyable piece of text.

Interestingly that the same concept of rewriting applies to designing the components.

Sometimes it’s hardly possible to create the right components structure at the first attempt. It happens because:

  • A tight deadline doesn’t allow spending enough time on system design
  • The initially chosen approach appears to be wrong
  • We’ve just found an open source library that solves the problem better
  • or any other reason.

Finding the right organization is a series of trials and reviews. The more complex a component is, the more often it requires verification and refactoring.

Does the component implement a single responsibility, is it well encapsulated, is it enough tested? If we can’t answer a certain yes, determine the weak part (by comparing against presented above 9 attributes) and refactor the component.

Pragmatically, development is a never stopping process of reviewing previous decisions and making improvements.


9. Reliability

Taking care of component’s quality requires effort and periodical review. It worth investment, since correct components are the foundation of a well designed system. Such system is easy to maintain and grow with complexity that increases linearly.

As result, development is relatively convenient at any project stage.

On the other hand, as system size increases, we might forget to plan and regularly correct the structure, decrease coupling. A naive approach to just make it work.

But after an inevitable moment when system becomes enough tightly coupled, fulfilling new requirements becomes exponentially complicated. We can’t control the code, the weakness of system controls us. A bug fix creates new bugs, a code update requires a cascade of related modifications.

How does the sad story end? We might throw away the current system and rewrite the code from scratch, or most likely continue eating the cactus. We’ve eaten plenty of cactuses, we’ve probably too, and it’s not the best feeling.

The solution is simple, yet demanding: write reliable components.

That’s all about in this article.


Conclusion

In this article, We understood about nine useful criteria of A React Component in ReactJS. The presented 9 characteristics suggest the same idea from different angles :

reliable component implements one responsibility, hides its internal structure and provides effective set of props to control its behavior.”

Single responsibility and encapsulation are the base of a solid design. We conclude that :

  • Single responsibility suggests to create a component that implements only one responsibility and has one reason to change.
  • Encapsulated component hides its internal structure and implementation details, and defines props to control the behavior and output.
  • Composition structures big and authority components. Just split them into smaller chunks, then use composition to glue the whole back, making complex simple.
  • Reusable components are the result of a well designed system. Reuse the code whenever you can to avoid repetition.
  • Side effects like network requests or global variables make components depend on environment. Make them pure by returning same output for same prop values.
  • Meaningful component naming and expressive code are the key to readability. Your code must be understandable and welcoming to read.
  • Testing is not only an automated way of detecting bugs. If you find a component difficult to test, most likely it’s incorrectly designed.
  • A quality, extensible and maintainable, thus successful application stands on shoulders of reliable components.
  • What principles do you find useful when writing React components?

Thanks for reading ! I hope you enjoyed and learned about the nine useful criteria of A React Component in ReactJS. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe us on this blog and and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Loading

Leave a Comment