A sustainable minimal react-app framework composition | Ready for production

Martin Horvath
7 min readFeb 15, 2021

I’m working with stone age applications to pay my bills and react is a technology/framework that allows me to speed up work, bring new flavors into those monolithic dinosaurs and separate front end and back end developers/work packages. But also react is changing (evolving) a lot and the pace is high. That’s why I was refactoring/rewriting one of my first apps to apply latest (stable!) methodologies and frameworks.

Photo by Polina Tankilevitch from Pexels

Management summary

It’s not that easy to create a sustainable react app template and there are many contradictory opinions. This selection is based on the following criteria sorted by importance to me:

  • Sustainability
  • Community support
  • Separation of concerns
  • Performance
  • Flexibility

During the refactoring/rewriting of a time tracking application, the following frameworks ended up in my final app:

  • Typescript for better JavaScript
  • React hooks for improved readability and less props-chaos
  • Redux for state management along with Redux-Toolkit for a reduced boilerplate and Redux-Thunk for async back end actions.
  • Immer for better deep-object-manipulation
  • React-i18next for internationalization
  • Webpack for the bundling and build process

You might want to replace one or the other, but you can’t go wrong with this baseline. That’s also gonna be my “minimum” for future react apps.

Motivation

Refactoring can be a lot of work and especially if it turns out to be more a rewrite than a refactoring. It all started with a recap of pain points that drove me crazy more than just a couple of times:

Let’s get started with the major downsides of my legacy app:

  • Communication between components using functions as props
  • Extensive passing of props between components
  • Single language
  • Back end calls in every component
  • No centralization
  • No type safety
  • A pain to debug

I guess those pain points don’t really need any explanation… More of interest is what a new react app needs to support nowadays:

  1. Centralized code for back end calls so it is flexible enough to deal with different (rest) services or APIs
  2. Separate business logic and presentation
  3. Ship as autonomous component for use in other applications
  4. Being able to deal with different time zones, date- and number-formats and languages
  5. Small footprint

The frameworks I’ve chosen

I took the hard way and tried many of nowadays frameworks and approaches. Many medium writers have done a great job and I really enjoyed following them, even if I ended up with a different opinion.

Typescript

What it is: An open source programming language built on JavaScript that brings type safety and is compiled to JavaScript using the TypeScript compiler or babel.

Why I use it: JavaScript allows me to work inefficiently on a try&error-basis. TypeScript forces me to handle undefined objects, wrong parameters etc. while writing code and the compiler stops me from testing broken code.

The following example is a plain JavaScript function that multiplies two values. This will print the results 8 and NaN to the console. NaN because I use strings instead of numbers for the invalid result.

var multiply = (val1,val2) => {
return val1 * val2;
}
var result = multiply(2,4);
console.log(result);
var invalid = multiply('2','asdf');
console.log(invalid);

In TypeScript, the required data type is part of the function declaration and the IDE and the compiler will warn during development and during the build process that there’s an error.

const multiply = (val1:number,val2:number) => {
return val1 * val2;
}
let x:number = myFunction(2,4);
console.log(result);
let invalid:number = multiply('2','asdf');
console.log(invalid);

If you think this is cool, check out the many useful features of TypeScript: https://www.typescriptlang.org/

React hooks

What it is: A new (recommended) way of writing react components. Instead of extending React.Component, it looks more like a traditional JavaScript function.

Why I use it: Components are smaller, come with improved readability and solve many problems with the old components.

The following is an example of a traditional component in react:

class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
setCount(c) {
this.setState({
count: c
});
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setCount(this.state.count + 1)}>Click me</button>
</div>
);
}
};
ReactDOM.render(<Counter />,document.getElementById('root'));

And the same using react hooks. What a beauty. No more this.state, no constructor props. Simple an attractive!

const Counter = () => {
[count, setCount] = React.useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
ReactDOM.render(<Counter />,document.getElementById('root'));

If you like this, check the article on the react dev page: https://reactjs.org/docs/hooks-intro.html

Redux & Redux-Toolkit & Redux-Thunk

What it is: Redux is a predictable state container for JavaScript apps that centralizes the apps state and logic. Redux-Toolkit is an official “booster” to reduce the redux boilerplate and make things easier. Redux is built for synchronous operations and Redux-Thunk is a redux middleware that allows us to write asynchronous code (e.g. rest request). In other words, something like: “Fetch data from http://myapi.com and then update the react state so that all components using it are re-rendered. And of course, don’t block the user interface while fetching”.

Why I use it: I like the idea of separating my components from the logic that is required to perform create/read/update/delete operations on some back end layer. And I like even more the automated flow of data so that concerned components are re-rendered when data has changed.

I’m aware of all the discussions around redux where someone is strongly recommending to get rid of redux because of this and that. I tried many approaches and at the end, I think redux is doing great for me. Especially the redux-toolkit flattens the learning curve and is a great addon.

A full example is out of scope for this article, but the screen shot below of my MetaData “store” should give you an idea. It is written in TypeScript and has two exposed methods, loadTags and loadFeelings which are both requesting a list of objects from a rest-interface. When successful, they are “dispatching” the results and the react components using tags (e.g. forms with select-options) are automatically updating by using this:

const tags = useSelector((state: RootState) => state.metadata.tags)

…instead of some tags via props etc.

Immer

What it is: Immer is a tiny package that allows you to work with immutable state in a more convenient way.

Why I use it: My state in react is not always flat and updating a nested property was always a pain. Immer makes that much easier.

The following shows a working but inefficient and “dirty” way of updating a state object with some deeper levels. The “clone” is created by first serializing the object to json and then deserializing it. There are better ways, sure, but this is just for demo purpose.

var o = JSON.parse(JSON.stringify(this.state.entry));
o.foo.bar = 'my updated value';
this.setState({ entry: o });

With immer, I get a “draft” object that I can modify inside a function body without worrying about state, references etc.

//this is creating an object "entry" in my react state
const [entry, setEntry] = useImmer(props.entry);
const setFooBar = (newVal) => {
setEntry(draft => {
draft.foo.bar = newVal;
});
};

React-I18Next

What it is: react-i18next is a powerful internationalization framework for React/React Native which is based on i18next.

Why I use it: Every serious application needs to support translations, formats etc. adopted to the users locale.

There are many ways how to solve internationalization and the more you think about it, the less you win with a custom implementation. I like the centralization, the re-rendering features upon change of language and I like that it “just works”.

import i18next from 'i18next';
...
return (
<div>{i18next.t('mylocalizedProperty')}</div>
);

It just needs a little setup, a translation file in json format and then it’s ready to be used like above. I currently prefer to import i18next directly instead of the “useTranslation” for react hooks. But that’s just my taste.

Webpack

What it is: A tool/framework that is bundling scripts, images, styles etc. so that your app can be packed and shipped.

Why I use it: Typescript needs to be compiled and I need to bundle all my stuff together in various forms: with libs included, without libs,…

Webpack has a bit of a learning curve but once you’ve got it running, it’s a joy. Here’s an example of a webpack config. It is keeping react and react-dom as external libraries, copies all translation files for i18next to the dist folder and compiles all ts, tsx, js, jsx files using the babel loader to javascript and saves them as my.bundle.js.

There are a bunch of environment scripts to tune the settings of babel, typescript etc. and it’s really crucial to get them right. But that’s for another post.

const appConfig = {
entry: './src/index.tsx',
externals: {
// Use external version of React
"react": "React",
"react-dom": "ReactDOM"
},
plugins: [
new webpack.IgnorePlugin(/react/,/react-dom/),
new copyPlugin({
patterns: [{
from: './locales/**/*.json',
to: './'
}]
})
],
module: {
rules: [
{
test: /\.(ts|js)x?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
},
output: {
filename: 'my.bundle.js',
path: path.resolve(__dirname, 'dist/app'),
},
devtool: "source-map"
};

--

--

Martin Horvath

I'm a consultant working on international projects in the field of geospatial data and customer experience, with a passion for technology and mountaineering.