Functional Reactive Programming with React / Redux / Redux-Observable / TypeScript Strict Mode
App in React + Redux + TypeScript — core features
I have chosen Redux among other React-friendly state managers (Apollo Link State, Unstated, Mobx) and native hooks (useEffect, useMemo, useState, useReducer) because working with Angular got me used to NGRX and NGXS. I feel convenient with the way Redux organizes state management and defines store, actions and reducers separately from component logic. Moreover, Redux can be used on top of hooks (since react-redux v. 7.1.0), aside of Context API (which is by some people meant to store metadata rather than all the data). It can be also integrated with Apollo.
There are also Redux libraries (observable-hooks) and middleware (redux-thunk, redux-promise, redux-observable) for async state effects. I am particularly interested in RxJS-based ones (observable-hooks, redux-observable). RxJS can handle even the most convoluted events / data streams when it comes to handling large numbers of data sources in a decoupled application architecture.
Having said that, Sunday coding TypeScript may not be enough in a corporate environment. TypeScript has its own strict mode that reduces runtime errors to zero. And it is not enabled by default (to junior developers’ relief).
TypeScript strict mode vs non-strict
What is strict mode for TypeScript? And why does it even matter?
And the mission of TypeScript is to intercept any type errors in compile time, isn’t it?
A trivial example with implicitAny (one of TypeScript strict mode options)
The above code is perfectly fine without strict mode. The figure attribute of innerCircumference is not typed implicitly, being type any. The result for square will cause a runtime error.
With strict mode on, the innerCircumference figure argument will be highlighted as being implicitly type any and the compiler will throw a build error (before runtime).
Enabling TypeScript strict mode
At the time of writing this article, TypeScript’s version is 3.7 and strict master option has grown a little bit since - first being introduced in version 2.3. Bind, call and apply checking has been improved in the most recent releases. Today, strict mode combines the following options:
( http://www.typescriptlang.org/docs/handbook/compiler-options.html )
Note that strict option is false by default and you have to enable it in compilerOptions of your tsconfig.json.
Example tsconfig.json with strict mode enabled:
Optionally, add eslint. I would dare add two extra rules: explicit-function-return-type and no-explicit-any. Example: .eslintrc with strict mode enabled:
If you decided to add eslint, the package.json should look similar to this:
If using VSCode, do not forget to update .vscode/settings.json unless you are a die-hard linting from CLI:
Autofix if desired.
Hardcore: enabling TypeScript strict and functional mode
There is also a recipe for functional TypeScript. Although functional style checking is currently not entirely part of tsconfig, linters handle it abundantly:
- eslint as a set of the following rules:
and the eslint replacement of tslint-immutable functional ruleset:
Example tsconfig.json with functional strict mode enabled:
Example .eslintrc with functional strict mode enabled:
Remember to update VSCode settings as shown in the previous subchapter.
React in TypeScript strict mode
If you are unsure what type your React Component is — with eslint explicit-function-return-type enabled, it is ReactElement:
React Redux in TypeScript strict mode
Let’s start creating our app. I have organized the whole structure inside the app folder. For the purpose of the this simple example, I have created one centralized store.
Store is the place that will keep our data. For the purpose of the example application, we will use a single centralized store at the app level.
The simplest configuration (without middleware)
The last 2 types are enhancer’s State and Store extensions mixed into store type.
With middleware (logger, etc.)
I believe it is safe to widen the resulting State to “State” ignoring extensions details of logger-likes, because we will not be dismantling them to use their state interfaces. Therefore, in my app, logger is added like this:
Period. Actually that’s even too much — I would personally treat extensions such as logger as just unknown (who cares about logging module internals, huh? :-) )
Warning: Unknown is even worse than any, but I would dare make an exception from all this strictness just here:
Having said that, I strive to reduce casting / explicit any / unknown to minimum (it is wise to add eslint warn rules at least for these).
Linking the component
In order to connect a class component, I discovered that, unfortunately, some casting was needed at the connect function (unlike in Angular where Redux-like NGRX is taken for granted). Suppose you have the OwnProps of the component (passed from parent element), StateProps of Redux state and DispatchProps to tell the state what to update:
It would be beautiful if not all the casting required and the fact that class components are less common, although I would really hesitate to say, worse.
Redux Hook API
In Redux Hook API, connect is replaced by selectors. Either memoized ones (with reselect):
They can be used with useSelector hook from Redux Hook API.
Hardcore: React Redux in TypeScript strict and functional mode
Ignore html files
HTML files are not tsx. Add **/*.html to .eslintignore (to prevent index.html from being parsed)
There is no need to part with those sweet interfaces. Just change them into type literals. Add readonly to each attribute.
Change switches to ternary operators in reducers
Output relies only on input. No observables, local scope, side-effects, etc. affecting result. In practice, it basically means the following:
Function return types
All functions have to have return types, even in tsx.
All functions have to have inputs (you are not planning any side effects, are you?)
No expression statements
Missing left-hand assignments mean the function either does nothing or is an expression statement (returns garbage). Deceive eslint into thinking that ReactDOM is something useful:
Async React w/ Redux (react-redux) in strict mode
Never again worry about race conditions and start using precise stream management with RxJS operators!
Hardcore mode: Applying middleware
Although “no side effects” in pure functions is one of the functional programming paradigms, handling async data streams is a programmer’s daily routine. Reactive functional programming justifies using effects. Technically, Redux-like observables / subjects / etc. help to achieve immutability of data at a given application state. Therefore Redux effects should be contemplated on a different level of abstraction.
By the way, using only built-in React hooks without reactive extensions would be impossible with functional lint rules enabled:
useEffect must return void | undefined while fetch returns a Promise. Somewhat explainable contradiction if you know what I mean…
Redux has evolved to fit the React hooks design ( https://react-redux.js.org/next/api/hooks ). Connect is no longer required because class components get slowly replaced by function components and lifecycle methods give way to lifecycle hooks.
After a longer thought, I actually found some meaning in such an awkward change. Suppose one decides to use memoization (memoized selectors) excessively. The only thing that comes to my mind to make it safe is to write functional — this means pure functions, no mutations and … no classes, no scope variables polluting the space, observables used according to FRP best practises. This makes sense somehow. So on the one hand, I hope mankind will start writing functional sooner than later, but on the other I miss interfaces / classes and all that OOP vocabulary. But there has to be a golden mean out there somewhere.