As an approach React has proven itself since the beginning nearly 10 years ago. One of the best features is that it isn't a framework, but merely a library. This allows a lot of flexibility to be used in different ways.

With the flexibility comes a range of different project setups that all work well. Their differences are due to the organisation or use case. In this post I try to describe one approach that is very common, and still works well if it is tuned up by adjusting the details.

If your project uses this approach, my notes might help you identify needed improvements. I use this post to collect details for this approach. If you plan a new project I would consider a focus on additional libraries that help exchange data between the browser and server.


React with Hooks

An import revision was made with React 16.8 that gave the new Hooks approach. This means that your codebase should use at least React 16.14, and ideally React 18 to have the latest refinements and fixes.

Components should be writting in a functional style. It's fast, it supports all the normal use cases, and gives developers less to know and remember;

All developers on the team should know how to use the following hooks: `useState`, `useReducer`, `useEffect`, `useContext`, `useId` and `useRef`.

You should also promote the understanding of `useCallback` and `useMemo`. See difference described

State hooks allow components to hold and share state(internally!) in an easy way, when it is only locally relevant. This leaves Redux to hold actual application domain state information. It should also be noted that information that doesn't change shouldn't be held as state, but rather be passed as properties or in some cases context attributes.

Redux toolkit

Redux is by now the seen as the original approach to maintain application state. To keep people from shooting themselves in the foot by doing things wrong, the Redux Toolkit package offers a simplified API. You should use this.

Some projects got burned by Redux, but it is likely due to using it wrong. It should only hold what is truly business domain state that would potentially be saved somewhere and/or have an impact across the application.

One very nice aspect of Redux is that it implicitly creates a transaction history of what happened in the user interface. By recording this history it is possible to replay it to set up test cases, and identify specific issues in production.

During development you have the additional benefit of being able to view the current logical state of the application through the Redux DevTools browser plugin

React Router

Another important building block deals with what part of your Application to show based on the Web address URL was used to visit. The Web URL is a cornerstone that allow us to jump directly to a specific article or video to experience. It can be equally important to jump directly to a specific piece of information within your application.

This is not the only Router to use in React, but it is the canonical choice. Compare this to any alternative you're considering. When you implement React Router you should know that it supports different approaches, so read Picking a Router first.

React Router has moved to the configuration as an object similar to the Angular Router. Previously it was written in JSX syntax. As far as I can tell the difference is esthetic. The new implementation might be easier for the maintainers.

The router allows you to configure what needs to be done before rendering the component for a route. This is conceptually different from React components as it uses async functions to wait for data fetched from the server, whereas components would have to maintain a loading state. In practice you have to make a tradeoff here on what you want to show during loading of data, which should take the real world loading times into account. Some routes might only require data to load for a part of the component while other parts remain functional. In such a case you might want to manage the loading state inside the component for the route.

A nice shorthand supported is the Form Component. This comes with the option to configure a submit action once the form is completed. The tutorial nicely shows it off in the context of a [CRUD app](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete).

Be sure to use the Hooks that come with the router such as useRouteLoaderData.


Use modern browser features

It can be hard to keep with the constant changes, but once features make it to be built into the browser it is time to stop using

libraries for what comes built in.

If you are using Axios, you probably want to use the builtin `fetch()` function for GET, PUT, POST calls.

If you are using Lodash or Underscore, many functions can be done with builtin primitives. Functions that can be done without lodash are; difference, flatten, includes, some, every, filter, find, uniq, compact, omit, pick, values, assignIn.

If you do nothing else make sure to disallow importing `* as _ from 'lodash'` in eslint rules as it will pull in the whole library code whether you use it all or not.

If you are using Moment.js for fancy date/time handling, you should review if you actually need it. There are alternatives that take up much less.

We would previously combine all application sources into single bundles that would be loaded as one big JavaScript file. With support for Universal modules it is possible to split up the application in modules and combine them with standard 3rd party modules to make the application specific release much smaller.

If you put the 3rd party modules on a CDN, they will not be included in your build or release. This will lead to faster builds, which means faster iteration time and less waiting.

Modern browsers support ES6 quite well, and transpilers such as TypeScript cover over any missing pieces. This means that you should use ES6 syntax in your codebase to reduce the application size and reduce the room for errors. Go through the list of ES6 features to adopt.


On the topic of TypeScript, I can rant about it. Too many people put too much faith in types solving problems and resort to a very restrictive setup for TypeScript. I've come to appreciate TypeScript when inferred typing is used whenever possible. If you choose to just use ES6 as supported by browsers, you can get very far and avoid a bit of complexity.

It feels odd that JavaScript ever didn't have async as a construct, but there it is. It is essential that your team knows and uses `async await` wherever possible and avoids old alternatives like callback functions. Several libraries provides a graceful implementation building on async functions.


Other notable builtins that you should consider,

  • Intersection observer
  • File API for richer file download
  • Universal Modules
  • Async Script loading
  • CSS Modules
  • Systematic CSS Grid and Flexbox


Constructing the WebApp

Building Single Page webApps has gone through several cycles with Gulp, Brunch, WebPack, Rollup, Vite, and Esbuild. The basic premise was to take a whole structure of source code and create a single JS file that would be loaded and run in the browser when you load the Web page. This bundle file would do everything needed to start and run the application. However web technologies have changed enough that we can actually load individual source files explicitly without any bundling while maintaining a fast user experience. This allows modern build tools like [Vite](https://vitejs.dev) to be really fast. Rather than create a single bundle file, the application is loaded using EcmaScript modules.

The evolution isn't stopping because with this new structure we can create modules that can be run in a browser, but also on a server. This allows us to create environments on WebServers that can run parts of the Application as JavaScript as trusted code that isn't readable by users, or thereby potential hackers. A new approach is evolving that running individual functions in the Cloud whenever needed and not tied to a specific server. So expect that lambda JS midtier functions will see wider use (Vercel for example).

If you are looking to move more of the application away from the Browser and on the Cloud servers, you probably want to look at Svelte and SvelteKit to migrate away from React. It should be possible to run your React components on Svelte pages, but it would require a careful evaluation. So this topic is really a different story...

Beyond this there are of course many other topics that could be considered canonical around Continuous Integration, Deployment, Testing, Feature Switching etc. I might return to some of these in other posts.