Anatomy of a React application: the basics
Let's start with a quick overview of the framework.
React's core principles are:
- It's declarative: design simple UIs and let React handle updates and rendering based on your application state.
- It's component-based: build encapsulated components managing their own state and aggregate them to form complex UIs: this allows developers to pass rich data around the application and at the same time keep state out of the DOM.
- It supports web application as well as mobile applications (Native React).
React transparently takes care of partial page updates based on components whose state has changed in order to provide better page rendering performance and more fluent user experience.
By using React, you can create simple components which can be easily reused and aggregated to form complex UIs. The state of your application is decoupled from your design and views and components can be written in a declarative way.
There are a lot of resources available online on React but the variety of approaches can make it difficult to understand its mechanics and parts. In particular, when I started diving more into React development, I faced the following challenges:
- The variety of examples and tutorials describing different approaches can make it difficult to understand how they differ and what best practices are.
- React is a fast moving and relatively young framework: examples are often pointing to older versions of React or other dependencies and given how rapidly the whole ecosystem moves, upgrading might involve fixing breaking changes and a fair amount of head-scratching.
- React is very valuable as a framework but you will almost certainly need additional tools such as Redux, Webpack, Babel, React Router.
The React ecosystem is full of interesting libraries. Redux is the indispensable tool I'm going to touch on this first post.
Redux offers a predictable state container for helping managing the data of your web application.
Redux main aim is to make state mutations predictable by imposing restrictions on how and when updates happen. Its core three principles are:
- Single source of truth: the state of the whole application is stored in a single object within a single store.
- The state is read-only: only actions can change it.
- Changes are made with pure functions called reducers.
Changes to the state in Redux are described as actions making it easier to understand what's happening. A reducer then ties an action with a state change and selectors are used to retrieve data from the state and pass it to components.
In my Redux template, the redux store is initially created on the server and its state returned as window.__initialState__ on the initial render. The client then also configure the store by reading it the state from the window object. Most of the action happens in conigureStore.js where the modules - each comprising of reducers, selectors, and actions - are combined together. Redux middleware is also defined here and it includes:
- Redux logger logs actions and state updates in your browser console. This is a very useful tool for development and staging environments but should be disabled in production.
- Action middleware (see middleware index): this can be used as a bridge between your application APIs and the application state.
- Thunk middleware: this allows you to write action creators that return a function instead of an action.
- Routing middleware (a more complex topic covered in the next chapter).
Reducers, selectors, and actions are organised in modules to provide a clear separation of the different data domains in the state (see the modules folder). Modules can then be imported in the components to provide a means for reading from the state (using selectors) and write to it (using actions). This is done by mapping state and dispatches to component properties (see the timer component for an example).
Other basic features of my Redux template includes:
- Yarn is used for managing npm dependencies and running tasks.
- Server-side rendering: in order to minimize initial page load time and make the application available to web crawlers, the first rendering happens on the server and the HTML is shipped to the client together with the application state. From that point onwards only partial view updates will be triggered based on state changes (e.g. user interactions, API calls).
- Feature toggles can be enabled / disabled with query string parameters as following: ?feature-toggles=some-feature-toggle&feature-toggles=another-feature-toggle. Once a feature toggle is enabled, that information will be stored in the cookies and used for subsequent requests. Re-add the feature-toggles query string parameter to override or disable feature toggles.
- The code is kept neat by using eslint: yarn lint.
- npm-check keeps the project dependencies up to date: yarn npm-check.
- Static resources: Express.static serves static resources such as JS and CSS files.
Over the next posts, I'll dive more into details of some other pieces of the React jigsaw and add more features to the template. Stay tuned!