Decoupling Software Design Patterns commonly used with React

Compose and test React with confidence and excellent developer experience

Marcel Mokos
ableneo Technology
7 min readOct 17, 2018

--

This article is used as the material for the hands-on lab at Openslava 2018 conference. Materials provided to participants before the hands-on lab are available here.

“low angle photo of gray clear glass building” by Reinaldo Kevin on Unsplash

Component

React approaches building user interfaces by breaking UI into components. This pattern is not mentioned in Gang of Four Design Patterns but has resemblance with Strategy pattern. Both patterns are talking part of an object’s behavior and delegating it to a separate subordinate object.

The difference is that with Strategy pattern, the “strategy” object is usually stateless it encapsulates an algorithm, but no data.

example of the strategy pattern in React, using stateless component

“strategy” defines how an object behaves but not what it is.

Components are a bit more self-important. They often hold state that describes the object and helps define its actual identity. You may have some components that don’t need any local state.

React team is preferring use of stateless functional components, that are more like a “strategies” since they are mostly static.

Presentational components

The base, styled components

  • Are concerned with only how things look.
  • commonly used for typography and layout
  • Often only use props.children
  • Examples: h1, section, div, span, Icon(may contain className, and accessibility attributes)

Presentational components

  • Are concerned with how things look.
  • Receive data and callbacks exclusively via props.
  • Rarely have own state other then UI-state
  • No lifecycle methods
  • Example: Avatar, Info, List

Container components

Container presenter components

  • Are concerned with how things look same as Presentational components.
  • Not connected to redux, or have own state other than UI-state
  • Can be tested without state or connection to the redux
  • Can have lifecycle methods
  • Often colocated in the same file as the container
  • Example: UserPagePresenter, UserListPresenter

Container components

  • Are concerned with how things work.
  • Not using styles
  • Often connected to the redux or have own state
  • Provide the data and behavior to presentational or other container components.
  • Example: UserPage, UserList

Assignment

We will be recreating youtube video comments discussion.

Analysis: Break The UI Into A Component Hierarchy

Let’s start with a comment component.

UI — Comment

Component Variations:

  • with/without an avatar
  • header with a link to user channel or to the comment and the commented video
  • relative time in a header or in a footer
An example can be seen in youtube comment history
  1. Comment (dark green): displays a row for each product

Base & styled components

Avatar (light green)

Header (yellow):

  • Heading(orange)
  • Subheading(red)

Text (purple)

Icon customizable icons

  • thumb up
  • thumb down,
  • arrow down
  • arrow up

Presentational components

Rating (blue): ignored for now or part of a different Jira issue 😉

  • Upvote
  • Downvote
  • Reply

Button "view comments”(light blue): all capital letters have an arrow icon

Common mistake — A component that knows too many details

It is tempting to create a specialized component that consumes data directly from one endpoint and outputs UI. This approach is coupling UI, data and business logic into one component.

Reusing the component with a slightly different layout will be complicated. You will be forced to create many props and if statements inside the component for every use case of slightly different layout or data.

Create a pure presentational component

At first, we need to display something. Focus only on the UI you want to output. Component should be highly composable and extendible.

🚧 ✍️🚧 Try to reorganize or change styles.

<Comment /> component just UI
<Comment /> component extended UI

Toggle for “view reply” button

Button "view comments”(light blue): all capital letters have an arrow icon

When closed:

  • =0 “No replies”
  • one=”view reply”
  • other=”view all {count} replies”

When open:

  • =0 {No replies}
  • one {Hide reply}
  • other {Hide all # replies}

We were able work with render props in a simple way adding components into static defaultProps. Now we will create component that will have internal ui-state.

Click on the Toggle isOpen button

We can now extend Toggle and create ViewCommentsButton. This particular component is using react-intl FormattedMessage for pluralized text. If you want to know more about internationalization in react apps you can read my article https://medium.com/@marcelmokos/internationalize-react-apps-done-right-using-react-intl-library-82978dbe175e.

Change count in default props to other numberic values and see the difference

<Discussion /> component with comments

Presenting collection of comments is now possible. Render props technique allows us to create specified <Comment /> component in defaultProps of a <Discussion /> component. There is no state used, and all data come from defaultProps.

<Discussion /> component

We have created all needed Presentational components. It is time to look at other decoupling patterns.

Command, Event Queue, Observer, Service Locator Pattern

Command

Command object “redux action” encapsulate a parameterized request as an object. Most well-known use of the command pattern is to change things using a command object, from where it is a small step to undo them. Redux dev tools use this pattern for time travel feature.

Event Queue

Decouple when a message or event is sent from when it is processed.

If you have not heard of “event queue” you probably have heard of “message queue,” or “event loop,” or “message pump.” Every time a user interacts with your application clicks a button, pulls down a menu or press any key the browser generates an event. Your job is to grab it and do something with it.

Notoriously used by DOM API, events are handled by React elements with some syntactic differences (SyntheticEvent, Handling Events). In Redux applications events in the form of actions are dispatched, and reducers produce state changes in response to actions sent to the store. Remember that actions only describe what happened, but don’t specify how the application’s state changes.

Observer

Redux uses Provider API that makes redux store available to the connect() calls in the component hierarchy below. If the mapStateToProps argument is specified, the new component will subscribe to Redux store updates.

Service Locator

Provide a global point of access to a service without coupling users to the concrete class that implements it.

React Context provides a way to pass data through the component tree without having to pass props down manually at every level.

The container component gets the store state calling redux connect(). From a parent in the hierarchy. It allows us to swap store implementation to test containers with a mocked store.

The Service Locator pattern is a sibling to Singleton in many ways. Redux store can be an example of a singleton since it is recommended to have just one store.

Some of the content was influenced and cited from a great book Game Programming Patterns by Robert Nystrom.

Fake data and normalization

In previous examples of <Discussion /> component, we were using fake data. Generating fake data can be very useful in some cases for visual or unit testing. We will not cover many details about generating fake data, but we would like to point out a different issue.

Redux state should be minimal. Data that can be derived should not be stored. For retrieving data from store, use selectors instead of data duplication. Redux store should be used as database. In our example, some channel(user) can comment in one discussion multiple times. If the user changes name or avatar, we can display inconsistent data.

Data should be normalized before storing in Redux state. In our example, we are using https://github.com/paularmstrong/normalizr. We can normalize and denormalize data using schema and provided functions.

Fake data for Channel, Comment and Discussion

<Fetch/> container

This component will allow us to load data from API. Since we want to keep it simple, we will expect that API will be promise provided by props. Some of the code will remind you redux since actions, reducers, and selectors are used.

If you want to see a more complex implementation of a Fetch component you have a look at https://www.npmjs.com/package/react-fetch-component

Thinks to point out:

  • has a state but setState is not called directly
  • has actions like you would be using in redux
  • the selector allows us to have a minimal state object
  • upcoming react hooks will address this by providing useReducer hook which can reduce boilerplate we have to write here

Connecting <Discussion/> with <Fetch/> component

As was promised the approach with render props is highly composable. We are using <Fetch/> component to load and provide data for our <Discussion/> component.

Digging into Redux and connecting <Discussion/> component to Redux state would take us too much time. You can try to do it on your own or wait for a blog post from me (Marcel Mokos) on an ableneo technology blog.

Useful resources

My npm packages:

Discussing the Future

  • waiting for official Redux render props connect() in Redux v6 until then I am using our own in the project
  • React suspense out for public (note that the current API is not final and will more than likely change before public release)
  • Problematic React Lifecycle Methods are Going Away in React 17

--

--

Marcel Mokos
ableneo Technology

I'm fanatic to next generation Javascript lambda, yield, async/await everything. I admire typescript and flow types. Javascript will ultimately rule the world.