What I learned from 2 years with JavaScript and React

Photo by Dan Gold on Unsplash

React

I come from a model view controller kind of development world. This also includes, that the development style was very object oriented. So completely different than JavaScript and React. I would describe React as a simple, easily to learn and extendible library for building User Interfaces in JavaScript. Very important to add here is that since React is not a framework, you may have to include additional modules for your use case. For example, adding navigation or state management to your project, or simply just a component library to have nice-looking buttons.

Project Setup

Before you do any project that should make it to a real product or you work with more than just yourself on it — set up your project correctly. This involves finding the right toolchain that has what you need. This could include a process for executing tests, building production/development builds and optimally also linter integration.

Setup Linter before you start in a team

That is something I definitely learned when I worked with multiple people in a JavaScript project. Besides proper documentation, a good setup for preventing access to undefined variables is a must in my opinion. If you use TypeScript rather than JavaScript, most of these things should already be setup.

Use Types when you work in a team

Even if you don’t work in a team, coming back to your code after months of not looking into it — do you still know what you did? Especially in JavaScript, I mostly don’t. That’s where Types may be very helpful.

Code Reviews > TypeScript

In general, adding types to JavaScript is a good thing, especially since it catches some errors upfront and does most of the documentation for your code (see typedoc). Interestingly, if you read the study about ‘To Type or Not to Type: Quantifying Detectable Bugs in JavaScript”, which I found in this awesome article, around 80% of bugs are not detectable by TypeScript. Type errors make up around 20% of bugs in your project (on average), the rest is mostly related to specification errors! So in conclusion, TypeScript (or other JavaScript type additions) will not be enough to catch all errors (or even half) in your code.

Project structure

It is really important that you decide how your project structure should look before you start the project (makes sense). Especially when you work in a team. In my opinion best practices for that should be documented somewhere, even if it’s just in the readme file. There are more things that I’ve questioned myself some time:

  • Have a logical file structuring like src/components src/container src/config or more behavioural, like src/usercontrols src/adminelements src/utils
  • When writing tests: E2E tests are usually placed in the projects root/e2e folder or similar. But what about unit- and integration test? Put them into a __test__ folder under src/ or in the hierarchy of the tested element?

Testing environment

In general, more testing gives you more confident that your system has less bugs. There are different levels of testing, starting from unit- and integration tests, up until End-To-End (E2E) and acceptance tests. I wrote an article about E2E testing in React Native. I would like to share some of my testing experience here anyway.

Continuous Integration and Continuous Delivery

Most of the time, developers have to do recurring tasks for a release. This often includes things like running tests/linters, bundling, acceptance tests and uploading. Without question this takes times and nerves, but can be minimized by using external CI/CD services. For sure you can get a DevOps Engineer who sets that up to you, but often this requires a lot of time and experience. An exception would be that you handle privacy sensitive data (your customers trust you) and thus must do it on your own.

Redux

When the project gets bigger, you could (should) add a library that enables unidirectional data flow for your application. One very famous pattern for that is Flux. Since it is just a pattern, you can chose from different library implementations like Redux or MobX. I personally only worked with Redux in the past and it is really great after you got used to the core concepts.

Redux middleware

Redux middlewares are configured ‘between’ your app and the redux store to do things. One thing could be to persist parts of your store so that your app is capable for offline usage. Another thing, which definitely makes sense is a logger middleware (redux-logger). With that you can see which action has which impact on your store, including timestamps and state differences before/after the action was dispatched.

Async/await

One thing many JavaScript developers like is the async/await syntax that highly improves readability of Promise.then() statements. Especially when you have nested then() statements, it can be quite helpful to use async/await. I don’t want to explain how async/await works, there are many great article out there that does that.

const fetchFood = new Promise((resolve, reject) => { 
setTimeout(() => resolve('food'), 1000);
});
const fetchWater = new Promise((resolve, reject) => {
setTimeout(() => resolve('water'), 300);
});
const fetchBread = new Promise((resolve, reject) => {
setTimeout(() => resolve('bread'), 500);
});
const main = async () => {
console.time('time');
const food = await fetchFood;
const water = await fetchWater;
const bread = await fetchBread;
console.timeEnd('time'); // ~1000ms

// equivalent
// const [food, water, bread] = await Promise.all([fetchFood, fetchWater, fetchBread]);
}
main();

Rest Spread Operator

The rest spread operator let you extend objects and arrays or simpler: concat them. The operation will create a new object or array:

const one = [1, 2];
const two = [3, 4];
console.log([...one, ...two]); // [ 1, 2, 3, 4 ]
console.log([...one, ...two, 5, 6]); // [ 1, 2, 3, 4, 5, 6 ]
console.log([...one] === one); // false
const someObject = {
magicValue: 42,
};
const hugeObject = {
...someObject,
someValue: 37,
deepObject: {
foo: [42, 43],
}
}
const overridenObject = {
...hugeObject,
someValue: 24,
deepObject: {
someArray: [91, 21], // foo[] will be overridden
}
}
const notOverridenObject = {
...hugeObject,
anotherValue: 37,
deepObject: {
...hugeObject.deepObject, // extend nested object
someArray: [42, 43],
}
}
const someSumFunction = (...args) => {
return args.reduce((sum, value) => sum + value)
}
console.log(someSumFunction(1, 2, 3));

React Performance

React has a component lifecycle, which is very important to understand if you want to get the maximum performance out of your application. React native has some special things to consider, because it obviously needs to communicate with the native side (iOS or Android) via a message-bridge, so everything is kind-of serialized from JavaScript into JavaScript Object Notation (JSON) and interpreted by the native side. But I’m not going into detail here.

const oneFunction = someInput => { return someInput; }
const theSameFunction = theSameInput => { return theSameInput; }
console.log(oneFunction === theSameFunction); // false
console.log([42, 43] === [42, 43]); // false

React Native and Performance

As I already mentioned, I will not go too detailed into React Native in this article. React Native in general is great and I’ve built many apps with it already. Many people complain about its errors that pop up when working with integrated modules or upgrading something. I’ve experienced that too, but after some time you know how to work with it and it gets easier. It helps when you’ve worked on multiple different native modules in react native.

React Native Mobile Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store