A powerful and proven way to include views on list rendering
Tags: webdev, react, javascript
Published: 2021-05-07 07:26:45


_[React]_ is a powerful javascript library that allows us to implement incredibly easy code. Hooks, use of JSX, easy components creation, and more features make developers create rich web experiences in few minutes. When complexity knocks on the door, we should combine _great technical designs_ along with the features provided by _[React]_ and give smart solutions to our problems.

This article shows how lists rendering can evolve from the simplest solution to solve easy requirements to a solution that follows the _[open/close principle]_ and uses the _[visitor pattern]_ for complex requirements.

> _Consider this article to be programming language agnostic although I use [React] for the examples provided._

**The standard way**

The standard way of rendering any list of items in _[React]_ is very simple and efficient. The following example has been taken from the _[React]_ official documentation. You may notice you can render any list of items just in 3 lines of code or 8 lines for readability.

```React
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);
```

This is the main way of rendering list of items and you should follow it in case of having very simple lists without any logic or simple logic involved in them.

**What about if we have several kind of visualizations**

But, what happens when you have to modify the way your items have to be shown depending on an external variable? 

Your logic can be adapted and create the proper components to render data in one way or another. For example, if we have a requirement to render the previous numbers in a table instead of a list we must change our code. Besides this requirement, we also have another one that lets the user set the way she wants to view the items. 

The following code is an improvement of the previous one setting the proper components to reach the requirements:

```React
const numbers = [1, 2, 3, 4, 5];

// View components
function ListView({ items }) {
  return <ul>
    {items && items.map(i => <li key={i}>{i}</li>)}
  </ul>;
}

function TableView({ items }) {
  return <table>
    <tbody>
    {items && items.map(i => <tr key={i}><td>{i}</td></tr>)}
    </tbody>
  </table>;
}

// View selector
function ViewSelector({ options, onSelect }) {
  return <div>
    {options && options.map(o => 
      <div key={o}><a href="#" onClick={() => onSelect(o)}>{o}</a></div>)
    }
  </div>;
}

// Application component
function App() {
  const options = ['list', 'table'];
  const [view, setView] = React.useState(options[0]);
  
  const onSelectHandler = (option) => {
    setView(option);
  };

  return <div>
    <ViewSelector options={options} onSelect={onSelectHandler} />
    {view === 'list' && <ListView items={numbers} />}
    {view === 'table' && <TableView items={numbers} />}
  </div>;
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
```

This code works fine and, visually, is really simple and easy to read. As a new developer in the team, you will be able to understand the previous code fastly and identify the responsibilities of each component. Hence, you will be able to evolve the code or to solve any issue that could appear in it.

As an example of evolution, a new requirement could be added to see the numbers inline, and would be easy to create a new `View` component and add it to the options to be selected. The new code could be something like the following:

```React
const numbers = [1, 2, 3, 4, 5];

// Notice the new view component
function InlineView({ items }) {
  return items && items.map(i => <span>{i}</span>);
}

function ListView({ items }) {
  return <ul>
    {items && items.map(i => <li key={i}>{i}</li>)}
  </ul>;
}

function TableView({ items }) {
  return <table>
    <tbody>
    {items && items.map(i => <tr key={i}><td>{i}</td></tr>)}
    </tbody>
  </table>;
}

function ViewSelector({ options, onSelect }) {
  return <div>
    {options && options.map(o => 
      <div key={o}><a href="#" onClick={() => onSelect(o)}>{o}</a></div>)
    }
  </div>;
}

function App() {
  // Notice the new option
  const options = ['list', 'table', 'inline'];
  const [view, setView] = React.useState(options[0]);
  
  const onSelectHandler = (option) => {
    setView(option);
  };

  // Notice how the new component has been added depending on `view` value
  return <div>
    <ViewSelector options={options} onSelect={onSelectHandler} />
    {view === 'list' && <ListView items={numbers} />}
    {view === 'table' && <TableView items={numbers} />}
    {view === 'inline' && <InlineView items={numbers} />}
  </div>;
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);
```

**We are breaking the [open/close principle]**

Imagine now that requirements are focused on providing more functionality to how items are shown in the app. In addition to this, if we want to apply more quality to our code and get greener lights on code review processes, we have to understand that the previous code is breaking the [open/close principle].

> _The [open/close principle] says: "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"_

Our `App` component has to be modified every time a new view is created or an existing one is replaced. Test -unit, integration, or any other kind- has to be modified as well in case we have to code them. All these factors add more uncertainty on how our code will work and this is something we need to avoid.

**How [visitor pattern] can be helpful**

Our objective here is to close our `App` component functionality to avoid any modification in the code. To reach this, we need to apply some changes that we will see in the paragraphs below.

Firstly, we need to create a new service with all the view types available and the `View` components related to each of these options.

```React
function ViewersService() {

  // service variable
  const views = {};

  return {
    // provide a copy of the views variable
    get() {
      return Object.assign({}, views);
    },

    // associate a view component to a type   
    register(type, viewComponent) {
      if(undefined === views[type]) {
        views[type] = [];
      }
      
      views[type].push(viewComponent);
    }
  };
}

// service instantiation
const viewers = new ViewersService();

// views registration
viewers.register('list', ListView);
viewers.register('table', TableView);
viewers.register('inline', InlineView);
``` 

Secondly, we have to provide this instance to our `App` component through parameters. And, then, we will use it for getting the available options and to render the proper view component depending on the user selection.

In the following code, we are using the option selected as the validator to determine if we need to _visit_ the view component. We assume that this value is the one to check for.

```React
// Notice viewers parameter
function App({ viewers }) {

  // Notice here that we get the views registrations from the instance
  const views = viewers.get();

  // Notice how options are obtained from the views keys
  const options = Object.keys(views);
  const [viewOption, setViewOption] = React.useState(options[0]);
  
  const onSelectHandler = (option) => {
    setViewOption(option);
  };

  // _views[viewOption]_ is the formula that determines the components to be visited  
  const viewsToVisit = views[viewOption];

  // Notice how we go through all views registered for the option selected and render altogether.
  const allViews = viewsToVisit.map(View => <View items={numbers} />);

  return <div>
    <ViewSelector options={options} onSelect={onSelectHandler} />
    {allViews}
  </div>;
}
```

At first sight, this code could be a bit challenging for a newbie because of the components and objects involved. I know this example is something relatively small but consider this solution to a  wider and larger application.

In the case of new requirements, a developer has to create the new `View` component and register it in the service. As an example, if we must only render the first item, the following code should be added:

```React
function FirstItemView({ items }) {
  return items && <span>{items[0]}</span>;
}

// this line to be added in the proper place
viewers.register('first', FirstItemView);
```

**Wrapping up**

This article tries to show a way to improve our code and its maintainability and readability by applying the [visitor pattern] widely used.

I think this is something challenging in the very first moment but will be useful when the increase in complexity and, therefore, the lines of code happens.

_What do you think about the exercise made in this article?
Hope this can be useful to you or just have fun reading it._

[open/close principle]: https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle
[visitor pattern]: https://en.wikipedia.org/wiki/Visitor_pattern
[React]: https://reactjs.org/