Let’s design React application

This text describes the initial setup I use to kickstart React application development. It emerged as a result of an idea, that an app should be able to scale well. And grow along with the business requirements with as little overhead as possible.

Overview

An app consists of “session controller”, multiple “controllers” and related “views” and “actions”. Controller represents a screen of content, view — its UI and actions — its business logic. Session controller contains application state, manages re-rendering of currently active controller’s view and able to swap controllers.

That was a high-level overview, let’s dive into details now.

Requirements for the app

Example folder structure

Maintainability. Let’s keep things isolated from each other so that it is easy to spin them off into NPM modules. In short: we are going to have lots of folders (screenshot on the left).

Single application state. This helps to keep UI changes of a screen of content in sync with each other.

Ability to develop UI components in isolation from the rest of the app (I prefer https://getstorybook.io). That means most of the components will be stateless and ready to be used with storybook.

Props propagation. As we are going to use single application state, root component will have to get all the props for its children. This children will have to propagate props to their children, and so on. It quickly becomes a burden as project grows. Let’s inject dependencies into components’ context to solve this problem.

CSS modules. Scoping css class names saves a lot of time, also helps to isolate UI components.

Splitting. Finally we are going to split bundle into chunks and keep currently unnecessary modules away.

Implementation details

Dependency injection. Context and actions

There is a module called react-simple-di. It splits dependencies into two types: context and actions. To be honest, this idea had the strongest impact on application design.

So, actions are functions that perform business logic and use context for side-effects. Context can contain references to application state storage, configurations, remote data solutions, etc.

react-simple-di has a very simple API:

  • injectDeps(context, actions)(Component) This method does two things. First one — it calls bind(null, context) on every function in actions. Second one — it returns a new component, that has childContextTypes and getChildContext set. Thus making context and modified actions list available to its children.
  • useDeps(mapper)(Component) It returns a new component that passes props to Component using mapper function. Mapper function takes context and actions as its arguments.

But it also has a drawback: actions list has to follow a predefined form. You cannot throw any structure at it.

I created a fork called components-di to solve this problem. It recursively goes through actions object and calls bind(null, context) on functions:

Application state

Now let’s choose a module to store local application state. A common choice would be Redux. But Redux might be an overkill for a small team. Too much hassle with action creators and reducers.

We are going to design our own state-storage with subscription mechanism of Redux. And API, that works like React component’s state.

API:

  • new Store(initialState) to create store;
  • store.setState(patch) to update state
  • store.resetState(newState) to completely replace current state with a new one
  • store.subscribe to subscribe to store updates.

The resulting module is called object-state-storage:

Rendering, views and controllers, session

On every store update React should re-render UI:

store.subscribe(() => { ReactDOM.render(
React.createElement(RootComponent),
mountPoint,
); });

We don’t need to provide props, components-di does it for us. Just remember to add store to context and inject dependencies into RootComponent.

Every screen of content has its own root component and a set of actions. They also might have differences in context. It makes sense to group actions, context and root component of a single screen of content in a single module:

When screen of content changes, controller changes. Because of single application state, we also might need to replace current state. To do so, let’s create a session controller. It will manage controller changes and re-rendering for us.

Splitting

It makes sense to keep screens of content as separate chunks. Webpack provides a nice mechanism to do so:

This is controllers list example, that we pass to SessionController when creating a new one.

Webpack configuration

This is what I currently use as my webpack.config.js :

I don’t use hot reloading or webpack dev server, just serving the “build” folder with http-server, that’s it.



This was yet another way of building React applications. What do you think? Was it useful? Will it affect the way you start working on new applications? Share your thoughts!