Containers and Presentationals: A More Modular SPA Architecture

Learn how to build your apps to optimize modularity and shareability

--

Bit is an awesome cloud platform for sharing components with other devs in the open-source community or with your team.

We are in the age of components. Our most popular SPAs are built with components — Angular, React and Vue. Components are meant to be re-usable. Whenever or wherever you need a particular view, you plug in the component easily, without re-writing code or views.

As we all know, a single component is composed of HTML elements that work together to achieve one goal. A might have a component that B loves and wants to use in his project. A wants to share his component so he pushes it to Bit’s cloud. Now, B can import the shared component from Bit and use it in his project. He may also sync changes, whenever A updates his component.

This post will talk about sharing Container and Presentational components with Bit. First, let’s look at what Container and Presentational components are.

Container Components

These are self-contained components, just like a real container. They generate the data, render and pass it to their child components. They are fondly called “Smart” components

They are mainly composed of Presentational components, which they pass data to. Inside Container components are were business logic methods are called. What is business logic? Business logic is the logic the app uses to generate and manipulate its data.

For example, a movie app that gets movies from a network and loads it to the app. Its business logic is the algorithm it uses to load the movies, the properties of the movies to load, what happens when there are no movies from the API endpoint, how to load a single movie. So you see they are where the function/methods that run them(business logic) are called.

Presentational components

They are components that only deals with the presentation of the UI, how a data is rendered on the browser. They are also called “Dumb” components, why? It is because the data they render are not generated by themselves. They are passed to them by the Container components.

See this follows the SRP in SOLID. The components have sole responsibility, one is responsible for generating data, the other is responsible for presenting data.

Presentational components are optimizable because they are pure and predictable. What do we mean by being predictable? P components do not side-effect meaning they don’t affect any external state of an app. They render/output what they are given, so we can memoize the input to render only if the inputs change referentially.

Bit, Components

Looking at the above explanation of C and P, we see that P are shareable, and because they are shareable they are re-usable. Yes, Bit will accept all your components but its good practice you share only your P components, cmon if you share your C components who is gonna use it? The business logic of A’s component might be different from business logic of B, even C or D, or any devs trying to use.

Have you considered this, all component UI libraries

  • Angular Material based on Material Design
  • ngBootstrap based on Bootstrap for Angular
  • Material UI based on Material Design for React
  • Tailwind CSS
  • Semantic

are all composed of P components. Check their Bit repo, their components are all P. All what their components do is to take in data and render based on the data.

Let’s take some for example

Chart in Primereact

Chart is a component in PrimeReact that draws a chart. Is it a C or P. Lets check.

To use Chart, we first install it:

// NPM option
npm i @bit/primefaces.primereact.chart
// yarn option
yarn add @bit/primefaces.primereact.chart
// Bit option used we developing the component
bit import primefaces.primereact/chart

Looking at the demo page in Bit, we see that the component accepts so many values in its props, should we call it settings? Yes, it accepts props that allow the user to set how to display data.

const data = {
labels: [
'Hulk',
'Captain America: The First Avenger',
'Iron Man',
'Thor',
'Captain America: Winter Soldier',
'Thor: Dark World',
'Iron Man II'
],
datasets: [
{
label: 'Marvel Movies Income',
backgroundColor: '#42A5F5',
data: [650, 590, 800, 810, 560, 550, 390]
}
]
};
<Chart type='bar' data={data} />

See the Chart component is passed values:

type: Tells the Chart how to display the data in a chart. We have a Bar chart, Flame chart, Spiral chart, pie chart. Here we are telling to display our data in bar form.

data: The data to display. Looking at the data object we see it contains the value the Chart will display, up close we are trying to display the chart of Marvel Movies with their grossing value.

From above, Chart is a Presentational component because it accepts inputs as props, these props tell it how to display its chart and what to render in the chart. So its deals with UI presentation and we can memoize(using React.memo, useMemo, ChangeDetectionStrategy.OnPush (Angular)) it because it depends on the data and type for data, if the state changes referentially then we re-render, if not there is no re-render to avoid wasted renders.

Table in MaterialUI: This component as the name implies is used to display data in tabular form using the Material Design CSS framework. This component is a P component because we feed it our data to display in tabular. It only deals with how to display data and nothing else. It has display logic. Its sole responsibility is to display data in a table.

Even if you share a C component in Bit, the consumer will have to refactor to their taste because some logic might interfere with the consumer which is not good. You can hardly see C components shared because they are more specific to projects.

Let’s say Big D is working on a movie app project in React and has this wonderful UI for rendering list of movies. It comes that his friend B stumbled across on his project and fell in love with the theme and style of display of Big D’s movies list.

Now if Big D’s project looks like this:

movie-app
src/
-App.js
-index.js
-App.css
-index.css
package.json
// ...
class App extends React.Component {
constructor() {
this.state = {
movies: []
}
}
componentDidMount() {
axios.get('api/movies').then((data)=> this.setState({movies: data.movies}))
}
render() {
return (
this.state.movies.map(movie=> (
<div className="movie-display">
<h3>Name: {movie.name}</h3>
<h3>Year: {movie.year}</h3>
<h3>Gross($): {movie.gross}</h3>
</div>
))
)
}
}

He can share the App component to Bit for B to import and use. First, he installs the Bit CLI and does the necessary:

npm i bit-bin -g
bit init
bit add src/app.js
bit export @bigdaddy/app

If B imports the component he will run into problems.

Loading of movies: B might not wnt to load movies from network, he might like to load it from the user’s memory storage. Even if he wants to load from the network he might not want to use the axios library. There are so many possibilities that B might refactor 90% of the code to suit his needs.

Network request: As I said, B might not like to load from network. He might not want to use axios, he might prefer to use his Observable-based network library.

All these are business logic that is specific to projects.

To make the UI shareable, Big D have to split the App.js component, one component will deal with presenting the movies array, the other holding the loading logic.

movie-app
src/
components/
- movielist/
- MovieList.js
- MovieList.css
- index.js
-App.js
-index.js
-App.css
-index.css
package.json
// ...
class MovieList extends Component {
render () {
return (
this.props.movies.map(movie=> (
<div className="movie-display">
<h3>Name: {movie.name}</h3>
<h3>Year: {movie.year}</h3>
<h3>Gross($): {movie.gross}</h3>
</div>
))
)
}
}
class App extends React.Component {
constructor() {
this.state = {
movies: []
}
}
componentDidMount() {
axios.get('api/movies').then((data)=> this.setState({movies: data.movies}))
}
render() {
return (
<div>
<MovieList movies={this.state.movies} />
</div>
)
}
}

See how we extracted the presentation of the movies to the MovieList component. It will deal with displaying movies from its movies props. With this Big D can seamlessly just share the MovieList to Bit for B to import and use:

bit add /components/movielist
bit export @bigdaddy

B can import the component from Big Daddy’s collection space in Bit:

bit import @bigdaddy/movielist --path=src/components

B can import it in his project(A movie sharing project):

movie-share
src/
App.js
index.js
index.css
App.css
package.json
|
v
movie-share
src/
components/
movielist/
index.js
MovieList.js
MovieList.css
App.js
index.js
index.css
App.css
package.json
class App extends Component {
constructor() {
this.state = {
movies: []
}
}
componentDidMount() {
this.setState({movies:localStorage.loadMovieFiles()})
}
render() {
return (
<div>
<h3>Share your movies to your Friends</h3>
<div className="movie-container">
<MovieList movies={this.state.movies} />
</div>
</div>
)
}
}

Developers around the world can import the MovieList component and use it in their projects.

Conclusion

What point I am trying to prove here? Bit is used to share reusable components. Presentational components are very much reusable other than Container components, so sharing your Container components will have little adoption and use because the business logic will be different from consumers even among your team.

When writing SPAs I follow this pattern (proposed by Jack Tomaszewski) so I can have as many reusable components in my app:

  1. Divide components into Smart and Dumb.
  2. Keep components as Dumb as possible.
  3. Decide when a component should be Smart instead of Dumb.

Thanks

Related Stories

Encapsulates components with Bit to run them anywhere across your projects and applications

Bit encapsulates components in your projects with all their files and dependencies, so they can run anywhere across your applications.

Build faster by making your components reusable out-of-the-box, and collaborate as a team to share and discover components. No refactoring or configurations needed, just share components and build truly modular apps.

LEARN MORE

--

--

JS | Blockchain dev | Author of “Understanding JavaScript” and “Array Methods in JavaScript” - https://app.gumroad.com/chidumennamdi 📕