MVC Implemented by React and Causality-Redux
Model–view–controller (MVC) is a software architectural pattern for implementing user interfaces on computers.
It divides a given application into three interconnected parts in order to separate internal representations of information from the ways that information is presented to and accepted from the user.
The MVC design pattern decouples these major components allowing for efficient code reuse and parallel development.
In particular with MVC, when a new UI implementation comes along, it is easy to swap out the old UI for the new since it is separated from the model and the controller logic. Otherwise, if the business logic and program state are tangled up with the UI then future UI technology replacements will require almost a complete rewrite of a web application.
Furthermore, when code is cleanly separated into simpler concerns it is easier to understand and maintain.
As such, this article will describe an MVC design pattern using react and an extension of redux named causality-redux that achieves the following objectives:
- Using the assignment operator, values can be set in the model that cause the UI to render those changes without having to define action creators, reducers or mapStateToProps functions.
- Through the props, causality-redux provides the react UI access to business code functions that can be either synchronous or asynchronous and these functions can be connected to event handlers like onClick.
- Supports hot reloading of both react UI components and business code while debugging.
- Ability to unit test UI components and business code independently or in combination.
- Modular data support using redux store partitions that allows each UI component to have its own separate independent store partition.
- A UI component and its corresponding business code can form a complete independent programming unit with the only dependency being perhaps props passed down from a parent in the react tree to customize the component.
- The business code of the component is reusable since it is not tangled up with the react UI but resides in a separate file.
To demonstrate MVC as implemented by causality-redux and react, a simple count by ten example will be described below.
Also, business code should not contain any program state so that it can be hot reloaded allowing fix and continue while debugging. If any non-UI program state is needed, it should be placed in a file separate from the UI and business code. Moreover, since the business code is an isolated module then it can be subjected to independent business code unit testing.
The prop keys of the component tenUp, tenDown and counter will be automatically supplied by the controller logic of causality-redux and this will be shown below.
Moreover, whenever counter is changed in the redux store, causality-redux will make this component re-render with the changed value of counter set correctly in the props.
Next, the controller logic is introduced and is implemented by causality-redux. Ideally, we would like to set some redux store values by assignment and this causes a render of those values in the react code. In fact, that is just what causality-redux can do. This contrasts with redux where changers and reducers must be written in order to change values in the redux store. Obviously, it is easier to change a value by simple assignment rather than implementing two functions to do the same thing.
Also, in redux there is only one store. In causality-redux, the redux store is separated into partitions, like the one defined below. This allows a separation of concerns by UI partitions and also makes it easier to target specific partitions in the redux store when debugging a component.
The purpose of the countTenPartition definition below is to disclose the shape of the redux store partition with the key defaultState, and to disclose the set of functions that can be made available to the react code by the controller logic of causality-redux. So, one can look at this definition and see all key/value pairs in the partition and their initial values.
By understanding the data structures involved in a program, one can easily understand its fundamental operation. However, in redux one has to look for every reducer in order to discover the key/value pairs and their initial values. Clearly, the simple task of understanding the shape of the redux store is easy under causality-redux and very difficult under just plain redux. In addition, one must add reducers to some global location if combineReducers is being used. These are all important considerations when evaluating the difficulty of maintaining the code.
The countTenPartition also includes a changerDefinitions entry that describes the set of functions that the UI may call. The keys in this object are the actual function names that will be made available in the props of the react component by causality-redux. These UI functions internally call the function at the controllerFunction entry. So for example, the UI will have tenUp available as a function in the props that can be connected to say the onClick handler. That function then internally calls controllerTenUp below. The function controllerTenUp then calls tenUp in the business logic to get the actual result. Finally, partitionState.counter is set to the result of tenUp.
Since counter has changed in the redux store, causality-redux detects that change and causes the component to re-render with the new value of counter passed into the component props. With redux, one would need write an action creator and reducer to change the value of counter and then write a mapStateToProps function for react-redux connect to cause the component to re-render with the changed value passed into the props. However, causality-redux performs all of this work for you.
Last, the causality-redux function establishControllerConnections is what makes all the above happen. It accepts module, the countTenPartition and the component CountTenForm as parameters. The module parameter allows causality-redux to correctly support hot re-loading for the react component, the business logic and the controller. Then, a mapStateToProps is internally built for use with the redux connect function that will supply all key/value pairs in defaultState to the props in the component. This can be overridden to include only a subset of key/value pairs in defaultState.
Likewise, an internal mapDispatchToProps is built that will provide all functions listed in the changerDefinition entry. Again, this can be overridden to include only a subset of the functions. Then react-redux connect is called with these internal built functions and the react-redux wrapped connect component is returned along with a proxy to the partition store.
The proxy partitionState is used to change redux store values by assignment and the returned uiComponent is used in place of CountTenForm and will support the proper re-rendering of the component whenever counter is change. It also supports the functions tenUp and tenDown that are sent into the props.
Note above that tenUp and tenDown could have also been asynchronous function calls, such that countrollerTenUp and countrollerTenDown used asynchronous business code operations with callbacks.
After the asynchronous operations completed, their respective callbacks would modify counter by assignment based on the results provided by the business code. Then the component CountTenForm would re-render with the new counter value. So, causality-redux makes it simple to call functions from the UI that translate to either synchronous or asynchronous business logic and no special treatment for either is necessary.
An additional positive feature of the declarative nature of defining a store partition as above is that future maintainers of the code can clearly see the precise interface provided by the controller logic between the business code and the UI. Therefore, it also serves as centralized documentation for how the two interact.
The component CountTenForCR can be used as below. Note that there is only one import from the component into the web application.
Finally, the hot reload support for the react app is demonstrated.
Therefore, values can be set in the controller code by assignment which cause react components to automatically render based on those changed values. There are no action creators, reducers and other code to be written in order to make the CountTenForm component render when required.
However, using assignment on partition store values must follow the same rules as with redux reducers. If the data type is basic like a number, then direct assignment is used. If the data type is an object like an array, then the proxy partitionState returns a shallow copy of the redux store partition object at the specified key. So you would make the change to the object copy and then you perform the assignment.
Below is an example on how this is done:
So with the above, the model is the business code plus the causality-redux store partition. The controller consists of the controller functions at the top of the file, the declarative countTenPartition definition and establishControllerConnections which connects business code operations and causality-redux store partition values to the UI.
Finally, the view is the stateless component CountTenForm. Therefore, causality-redux and react successfully implement MVC.
The diagram below clarifies this implementation:
Moreover, by separating the program concerns according to MVC, each part of the MVC pattern can be independently tested so that the testing code remains valid in the event some part is swapped out for something better.
I have put together a vscode/webpack template for developing with es6, react jsx and causality-redux at github. This template demonstrates various other features of causality-redux. So, all that needs to be done is to download the template and install it. Then you can set breakpoints in the sample code in order to analyze the causality-redux.
The template supports the following features:
- Total separation of react UI components from program state and business code.
- Major extensions and simplifications to redux.
- Ability to use the assignment operator on causality-redux store values to automatically update the react UI. No changers, reducers, etc are needed.
- Eslint support.
- Vscode debugging and hot reloading on a file save within react code or the business code.
- CSS modules.
- Support for sass, scss and less.
- Support for sass, scss, less and css injections into your react components.
- Legacy CSS code.
- Postcss-loader so you do not have to use vendor prefixes in your css code.
- Url-loader for assets such as images, fonts etc that can be imported into your react components.
- Mocha react/enzyme testing with examples.
- Mocha test vscode debugging that uses webpack watch to automatically compile changes made to the test code for faster debugging.
- Dll libraries for fast compiling while debugging and/or for production loading and browser caching.
- Non injected CSS across all components extracted into a single file and minified with cssnano.
- Production build of an html file using the HtmlWebpackPlugin that contains the minified js and css along with a minified dll library if you requested dll libraries.
- Ability to display production build before publishing.
- Many code samples that demonstrate the use of causality-redux with react stateless components.
You can download this template at the following github repo: react-causality-redux-vscode-template.