Functional Reactive Programming with React / Redux / Redux-Observable / TypeScript Strict Mode

Konrad Stobiecki
Dec 14, 2019 · 7 min read
Photo by NihoNorway graphy on Unsplash

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.

When it comes to decoupling, it is easy to confuse data from various sources. TypeScript helps by organizing data in recognizable structures (types, interfaces and classes) in contrast to plain JavaScript that allows for passing any data anywhere, risking runtime errors. Its use in stateful applications leaves no doubt.

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?

Without strict mode, TypeScript is just an addition of some types and interfaces to JavaScript. For a professional developer, this might be insufficient because typing in non-strict mode is like a caprice without obligation. If one plans to use a function’s return value or some newly created constant / variable in a function that accepts any values as parameter types, then they would actually be better off with plain JavaScript - still getting runtime errors, but being more descriptive. And what makes software from low-cost companies low quality, is the common practice of unskilled TypeScript devs: when you don’t know what type it is, type… nothing. Runtime error guaranteed.

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).

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:

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.

There is also a recipe for functional TypeScript. Although functional style checking is currently not entirely part of tsconfig, linters handle it abundantly:

and the eslint replacement of tslint-immutable functional ruleset:

Example tsconfig.json with functional strict mode enabled:

Example .eslintrc with functional strict mode enabled:

Example package.json:

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).

Connect (legacy)

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):

Or not:

They can be used with useSelector hook from Redux Hook API.

Turbo easy.

Hardcore: React Redux in TypeScript strict and functional mode

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

becomes

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.

Function params

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!

Note

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…

Conclusion

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.

Full code on GitHub

gft-engineering

GFT is driving the digital transformation of the world’s leading companies. On here, our tech communities from all around the globe share their tips, tricks & insights with other developers.

Konrad Stobiecki

Written by

gft-engineering

GFT is driving the digital transformation of the world’s leading companies. On here, our tech communities from all around the globe share their tips, tricks & insights with other developers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade