Start a New React Project

If you want to build a new app or a new website fully with React, we recommend picking one of the React-powered frameworks popular in the community.

You can use React without a framework, however we’ve found that most apps and sites eventually build solutions to common problems such as code-splitting, routing, data fetching, and generating HTML. These problems are common to all UI libraries, not just React.

By starting with a framework, you can get started with React quickly, and avoid essentially building your own framework later.

Deep Dive

Can I use React without a framework?

You can definitely use React without a framework—that’s how you’d use React for a part of your page. However, if you’re building a new app or a site fully with React, we recommend using a framework.

Here’s why.

Even if you don’t need routing or data fetching at first, you’ll likely want to add some libraries for them. As your JavaScript bundle grows with every new feature, you might have to figure out how to split code for every route individually. As your data fetching needs get more complex, you are likely to encounter server-client network waterfalls that make your app feel very slow. As your audience includes more users with poor network conditions and low-end devices, you might need to generate HTML from your components to display content early—either on the server, or during the build time. Changing your setup to run some of your code on the server or during the build can be very tricky.

These problems are not React-specific. This is why Svelte has SvelteKit, Vue has Nuxt, and so on. To solve these problems on your own, you’ll need to integrate your bundler with your router and with your data fetching library. It’s not hard to get an initial setup working, but there are a lot of subtleties involved in making an app that loads quickly even as it grows over time. You’ll want to send down the minimal amount of app code but do so in a single client–server roundtrip, in parallel with any data required for the page. You’ll likely want the page to be interactive before your JavaScript code even runs, to support progressive enhancement. You may want to generate a folder of fully static HTML files for your marketing pages that can be hosted anywhere and still work with JavaScript disabled. Building these capabilities yourself takes real work.

React frameworks on this page solve problems like these by default, with no extra work from your side. They let you start very lean and then scale your app with your needs. Each React framework has a community, so finding answers to questions and upgrading tooling is easier. Frameworks also give structure to your code, helping you and others retain context and skills between different projects. Conversely, with a custom setup it’s easier to get stuck on unsupported dependency versions, and you’ll essentially end up creating your own framework—albeit one with no community or upgrade path (and if it’s anything like the ones we’ve made in the past, more haphazardly designed).

If your app has unusual constraints not served well by these frameworks, or you prefer to solve these problems yourself, you can roll your own custom setup with React. Grab react and react-dom from npm, set up your custom build process with a bundler like Vite or Parcel, and add other tools as you need them for routing, static generation or server-side rendering, and more.

Production-grade React frameworks

These frameworks support all the features you need to deploy and scale your app in production and are working towards supporting our full-stack architecture vision. All of the frameworks we recommend are open source with active communities for support, and can be deployed to your own server or a hosting provider. If you’re a framework author interested in being included on this list, please let us know.

Next.js

Next.js’ Pages Router is a full-stack React framework. It’s versatile and lets you create React apps of any size—from a mostly static blog to a complex dynamic application. To create a new Next.js project, run in your terminal:

Terminal
npx create-next-app@latest

If you’re new to Next.js, check out the learn Next.js course.

Next.js is maintained by Vercel. You can deploy a Next.js app to any Node.js or serverless hosting, or to your own server. Next.js also supports a static export which doesn’t require a server.

Remix

Remix is a full-stack React framework with nested routing. It lets you break your app into nested parts that can load data in parallel and refresh in response to the user actions. To create a new Remix project, run:

Terminal
npx create-remix

If you’re new to Remix, check out the Remix blog tutorial (short) and app tutorial (long).

Remix is maintained by Shopify. When you create a Remix project, you need to pick your deployment target. You can deploy a Remix app to any Node.js or serverless hosting by using or writing an adapter.

Gatsby

Gatsby is a React framework for fast CMS-backed websites. Its rich plugin ecosystem and its GraphQL data layer simplify integrating content, APIs, and services into one website. To create a new Gatsby project, run:

Terminal
npx create-gatsby

If you’re new to Gatsby, check out the Gatsby tutorial.

Gatsby is maintained by Netlify. You can deploy a fully static Gatsby site to any static hosting. If you opt into using server-only features, make sure your hosting provider supports them for Gatsby.

Expo (for native apps)

Expo is a React framework that lets you create universal Android, iOS, and web apps with truly native UIs. It provides an SDK for React Native that makes the native parts easier to use. To create a new Expo project, run:

Terminal
npx create-expo-app

If you’re new to Expo, check out the Expo tutorial.

Expo is maintained by Expo (the company). Building apps with Expo is free, and you can submit them to the Google and Apple app stores without restrictions. Expo additionally provides opt-in paid cloud services.

Bleeding-edge React frameworks

As we’ve explored how to continue improving React, we realized that integrating React more closely with frameworks (specifically, with routing, bundling, and server technologies) is our biggest opportunity to help React users build better apps. The Next.js team has agreed to collaborate with us in researching, developing, integrating, and testing framework-agnostic bleeding-edge React features like React Server Components.

These features are getting closer to being production-ready every day, and we’ve been in talks with other bundler and framework developers about integrating them. Our hope is that in a year or two, all frameworks listed on this page will have full support for these features. (If you’re a framework author interested in partnering with us to experiment with these features, please let us know!)

Next.js (App Router)

Next.js’s App Router is a redesign of the Next.js APIs aiming to fulfill the React team’s full-stack architecture vision. It lets you fetch data in asynchronous components that run on the server or even during the build.

Next.js is maintained by Vercel. You can deploy a Next.js app to any Node.js or serverless hosting, or to your own server. Next.js also supports static export which doesn’t require a server.

Deep Dive

Which features make up the React team’s full-stack architecture vision?

Next.js’s App Router bundler fully implements the official React Server Components specification. This lets you mix build-time, server-only, and interactive components in a single React tree.

For example, you can write a server-only React component as an async function that reads from a database or from a file. Then you can pass data down from it to your interactive components:

// This component runs *only* on the server (or during the build).
async function Talks({ confId }) {
// 1. You're on the server, so you can talk to your data layer. API endpoint not required.
const talks = await db.Talks.findAll({ confId });

// 2. Add any amount of rendering logic. It won't make your JavaScript bundle larger.
const videos = talks.map(talk => talk.video);

// 3. Pass the data down to the components that will run in the browser.
return <SearchableVideoList videos={videos} />;
}

Next.js’s App Router also integrates data fetching with Suspense. This lets you specify a loading state (like a skeleton placeholder) for different parts of your user interface directly in your React tree:

<Suspense fallback={<TalksLoading />}>
<Talks confId={conf.id} />
</Suspense>

Server Components and Suspense are React features rather than Next.js features. However, adopting them at the framework level requires buy-in and non-trivial implementation work. At the moment, the Next.js App Router is the most complete implementation. The React team is working with bundler developers to make these features easier to implement in the next generation of frameworks.

Create Monorepo from Scratch

There are a couple of hurdles to starting a React monorepo. The first is that node can’t process all of the syntax (such as import/export and JSX). The second is that we will either need to build our files or serve them somehow during development for our app to work - This is especially important in the latter situations. These issues with be handled by Babel and Webpack, which we cover below

Setup

To get started, create a new directory for our new React monorepo. Inside the monorepo directory, initialize a project with

Terminal
yarn init

Thinking ahead a little bit, we’ll eventually want to build our app and we’ll probably want to exclude the built version and our node modules from commits, so let’s go ahead and, at the root level of the monorepo, add a .gitignore file excluding (at least) thenode_modules, dist, etc:

dist
/build
.DS_Store
/coverage
node_modules

.env.

npm-debug.log*
yarn-debug.log*
yarn-error.log*

We need to install react now:

Terminal
yarn add react react-dom

Note that we do save those as regular dependencies, i.e. without --save-dev option.

Babel

Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments. For example, Babel transforms syntax:

// Babel Input: ES2015 arrow function
[1, 2, 3].map(n => n + 1);

// Babel Output: ES5 equivalent
[1, 2, 3].map(function(n) {
return n + 1;
});

To install Babel in our project, go to the top directory of our monorepo project and run

Terminal
yarn add -D @babel/core @babel/cli @babel/preset-env @babel/preset-react @babel/preset-typescript

@babel/core is the main babel package - We need this for babel to do any transformations on our code. @babel/cli allows us to compile files from the command line. preset-env and preset-react are both presets that transform specific flavors of code - in this case, the env preset allows us to transform ES6+ into more traditional javascript and the react preset does the same, but with JSX instead. @babel/preset-typescript is used by Jest we setup later, because we will need to transpile Jest into TypeScript via Babel

The following 2 links explains in details why the 4 dependencies above are needed in our react app:

In our monorepo project root, create a Babel configuration file called babel.config.json. Here, we’re telling babel that we’re using the env and react presets (and some typescript support for Jest testing which we discuss later):

{
"presets": ["@babel/preset-env", ["@babel/preset-react", { "runtime": "automatic" }], "@babel/preset-typescript"]
}

:::note

The { "runtime": "automatic" } is to prevent the ReferenceError: React is not defined error during Jest unit test

Please also note that with this config set, we should not need to use import React from 'react', which is a discouraged practice starting from React 17

:::

TypeScript

We integrate TypeScript by going to the top directory of our monorepo project and running

Terminal
yarn add -D typescript

Let’s set up a configuration to support JSX and compile TypeScript down to ES5 by creating a file called tsconfig.json in project root directory with the content of:

{
"compilerOptions": {
"target": "es6",
"module": "es6",

"strict": true,
"allowJs": true,
"jsx": "react-jsx",
"outDir": "./dist/",
"noImplicitAny": true,
"esModuleInterop": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
},
"include": ["packages"]
}

See TypeScript’s documentation to learn more about tsconfig.json configuration options. The thing we need to mention here is the "jsx": "react-jsx" option. In short, react-jsx is a more-modern option compared to other such as old react and we will use this newer feature.

Jest

Let’s jump into test setup with Jest. At the monorepo root directory run

Terminal
yarn add -D jest babel-jest @types/jest ts-jest react-test-renderer @testing-library/react @testing-library/jest-dom

Pitfall

There is a bug in Jest version 28 and we need to make sure to downgrade jest to some 27 version. At the time of writing, the following versions work:

"@types/jest": "^27.5.2",
"jest": "^27.4.3",
"ts-jest": "^27.1.4",
"babel-jest": "^27.4.2",

Jest transpiles TypeScripts before running test harness. We will need a configuration file to specify how TypeScript is going to be transpiled. The file name is jest.config.json:

{
"preset": "ts-jest",
"testEnvironment": "jsdom",
"setupFilesAfterEnv": ["<rootDir>/scripts/jest/setupTests.ts"],
"transform": {
"^.+\\.[t|j]sx?$": "babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"moduleNameMapper": {
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
}
}
  • "preset": "ts-jest" and "testEnvironment": "jsdom" are neede by ts-jest config

  • setupFilesAfterEnv: This is related to the @testing-library/jest-dom dependency. In terms of Jest configuration, rather than import it in every test file it is better to do it in the Jest config file via the setupFilesAfterEnv option and then we will have a setupTests.ts file located inside scripts/jest directory with the following content

    import "@testing-library/jest-dom";
  • "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",: This was taken from an ejected CRA that specifies how to mock out CSS imports. We should have a file called cssTransform.js under <rootDir>/config/jest/ directory with the following contents:

    Pitfall

    create-react-app abstracts a lot of what makes a React app work away from us - at least without ejecting it and having to tweak all of the options by hand. There are a number of reasons we may want to make our own implementation, or at least have some idea of what it’s doing under the hood. Most importantly,create-react-app is more like a “Spring Boot” in the Front End world. Those who dislike the Spring for offering quick startup but terrible customizability later might find create-react-app very frustrating.

    "use strict";

    module.exports = {
    process() {
    return "module.exports = {};";
    },
    getCacheKey() {
    // The output is always the same.
    return "cssTransform";
    },
    };

    Similarly, the next line specifies file mock (same location with file name of fileTransform.js):

    "use strict";

    const path = require("path");
    const camelcase = require("camelcase");

    // This is a custom Jest transformer turning file imports into filenames.
    // http://facebook.github.io/jest/docs/en/webpack.html

    module.exports = {
    process(src, filename) {
    const assetFilename = JSON.stringify(path.basename(filename));

    if (filename.match(/\.svg$/)) {
    // Based on how SVGR generates a component name:
    // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
    const pascalCaseFilename = camelcase(path.parse(filename).name, {
    pascalCase: true,
    });
    const componentName = `Svg${pascalCaseFilename}`;
    return `const React = require('react');
    module.exports = {
    __esModule: true,
    default: ${assetFilename},
    ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
    return {
    $$typeof: Symbol.for('react.element'),
    type: 'svg',
    ref: ref,
    key: null,
    props: Object.assign({}, props, {
    children: ${assetFilename}
    })
    };
    }),
    };`;
    }

    return `module.exports = ${assetFilename};`;
    },
    };
  • "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy": Mocking CSS Modules

Additional Jest References

Webpack

We configure Webpack now. We’ll need a few more packages as dev dependencies. Run the following commands at the root directory of our monorepo:

Terminal
yarn add -D webpack webpack-cli webpack-dev-server style-loader css-loader babel-loader

Setup Webpack Dev Server

We’ve mentioned previously the need to “build our files or serve them somehow during development for our app to work”. Essentially, we will need to achieve this by enabling yarn start command using Webpack Dev Server. First, we put a config file of it under config/webpack called webpack.config.js:

const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const imageInlineSizeLimit = parseInt(process.env.IMAGE_INLINE_SIZE_LIMIT || "10000");

module.exports = function (webpackEnv) {
const isProdEnvironment = webpackEnv === "production";

return {
entry: "./packages/app/src/index.tsx",
mode: isProdEnvironment ? "production" : "development",
output: {
publicPath: "/",
path: path.resolve(__dirname, "dist"),
filename: isProdEnvironment ? "static/js/[name].[contenthash:8].js" : "static/js/bundle.js",
},
module: {
rules: [
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
exclude: /(node_modules|bower_components)/,
loader: "babel-loader",
options: { presets: ["@babel/env"] },
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.webp$/],
type: "asset",
parser: {
dataUrlCondition: {
maxSize: imageInlineSizeLimit,
},
},
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: "./packages/app/public/index.html",
},
isProdEnvironment
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
],
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"],
},
};
};

entry tells Webpack where our application starts and where to start bundling our files. The following line lets webpack know whether we’re working in development mode or production build mode. This saves us from having to add a mode flag when we run the development server.

The module object helps define how our exported javascript modules are transformed and which ones are included according to the given array of rules.

Our first rule is all about transforming ES6 and JSX syntax. The test and exclude properties are conditions to match file against. In this case, it’ll match anything outside of the node_modules and bower_components directories. Since we’ll be transforming our .js and .jsx files as well, we’ll need to direct Webpack to use Babel. Finally, we specify that we want to use the env preset in options.

The next rule is for processing CSS. Since we’re not pre-or-post-processing our CSS, we just need to make sure to addstyle-loader and css-loader to the use property. css-loader requires style-loader in order to work.

We want to use Hot Module Replacement so that we don’t have to constantly refresh to see our changes. All we do for that in terms of this file is instantiate a new instance of the plugin in the plugins property, i.e. new webpack.HotModuleReplacementPlugin()

We need to add an index.html to our webpak config, so it can work with it; otherwise webpack-dev-server will simply get us a blank screen with yarn start1. We use html-webpack-plugin for this.

Terminal
yarn add -D html-webpack-plugin

The new HtmlWebpackPlugin(...) snippet above was taking from an ejected CRA.

The resolve property allows us to specify which extensions Webpack will resolve - this allows us to import modules without needing to add their extensions2. For example, we can safely put

import App from "./App"

when we have a file App.tsx. Without resolve above, import above will throw a runtime-error because only App.jscan be imported without specifying an extension.

We are going to use dev-server through the Node.js API. But before we proceed, it is worth mentioning that the webpack.config.js file above does not have the devServer field.

Because the field is ignored if we run dev server using Node.js API. We will, instead, pass the options as the first parameter: new WebpackDevServer({...}, compiler). For separation of concerns, the option is defined in yet another config file called webpackDevServer.config.js (located in the same directory as webpack.config.js) and this file will be imported in Node.js API file:

"use strict";

module.exports = function () {
return {
historyApiFallback: true,
};
};

Note

The historyApiFallback: true above combined with the publicPath: "/" from the webpack.config.js file shall, if we have router defined in our app, enable page refresh on the flight. Otherwise, a 404 error on sub-router page refresh will occur3.

Please checkout Server-side rendering vs Client-side rendering for more background discussion.

Finally, here is our Node.js API file:

"use strict";

const configFactory = require("../config/webpack/webpack.config");
const devServerConfig = require("../config/webpack/webpackDevServer.config");

const Webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
const webpackConfig = configFactory("development");

const compiler = Webpack(webpackConfig);
const devServerOptions = { ...devServerConfig(), open: true };
const server = new WebpackDevServer(devServerOptions, compiler);

server.startCallback(() => {
console.log("Starting server on http://localhost:3000");
});

We put this in scripts/start.js so that we will be able to call this script during yarn start by adding the following line to package.json:

"scripts": {
"start": "node scripts/start.js",
...
},

Creating a Production Build

We will use yarn build to create a build directory with a production build of our app. Inside the build/static directory will be our JavaScript and CSS files. Each filename inside of build/static will contain a unique hash of the file contents. This hash in the file name enables long term caching techniques, which allows us to use aggressive caching techniques to avoid the browser re-downloading our assets if the file contents haven’t changed. If the contents of a file changes in a subsequent build, the filename hash that is generated will be different.

"use strict";

const Webpack = require("webpack");
const configFactory = require("../config/webpack/webpack.config");
const webpackConfig = configFactory("production");

const compiler = Webpack(webpackConfig);

console.log("Creating an optimized production build...");

compiler.run();

We put this in scripts/build.js so that we will be able to call this script during yarn build by adding the following line to package.json:

"scripts": {
...
"build": "node scripts/build.js",
...
},

App Package

Next, in the new project folder, create the following directory:

mkdir -p packages/app/

Next, create the following structure inside packages/app/

.
+-- public
+-- src

Our public directory will handle any static assets, and most importantly houses our index.html file, which react will utilize to render our app. The following code is an example:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="./manifest.json" />
<link rel="shortcut icon" href="./favicon.ico" />
<title>My App</title>
</head>

<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<div id="root"></div>
</body>
</html>

The manifest.json and favidon.ico will be placed in the same directory as the index.html, i.e. the public directory.

The manifest.json provides metadata used when our web app is installed on a user’s mobile device or desktop.

packages/app/src/App.tsx

The TypeScript code in App.tsx creates our root component. In React, a root component is a tree of child components that represents the whole user interface:

import { BrowserRouter as Router, Route, Routes } from "react-router-dom";

import MyHomeComponent from "somePathTo/MyHomeComponent";
import MySettingsPageComponent from "somePathTo/MySettingsPageComponent";

export default function App(): JSX.Element {
return (
<Router>
<Routes>
<Route path="/" element={<MyHomeComponent />} />
<Route path="/settings" element={<MySettingsPageComponent />} />
</Routes>
</Router>
);
}

packages/app/src/index.tsx

index.tsx is the bridge between the root component and the web browser.

import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

packages/app/src/index.css

This file defines the styles for our React app. Here is an example:

body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
"Droid Sans", "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
}

Now that we’ve got our HTML page set up, we can start getting serious. We’re going to need to set up a few more things. First, we need to make sure the code we write can be compiled, so we’ll need Babel, which we discuss next.

Upgrade to Yarn 2

https://yarnpkg.com/migration/guide#migration-steps

Setup CI/CD

Project Configuration Management

This document describes Configuration Management for our monorepo project.

Context

Configuration management is essential for every application that will be deployed into multiple environments, which is pretty much the majority of Apps and APIs. Focusing on React Apps in this post, we will cover how to store configurations in Nexus Graph, how to configure them, and finally how to read them.

Configuration Types

There are different types of configurations in Nexus Graph

Environment Dependent

Those are configurations that change from one environment to the other. A good example would be FQDNs (Fully Qualified Domain Names). A URL in local dev environment, might point to https://localhost:6500, however the same URL in production, would point to https://theresa-api.com.

Storing Environment Dependent Configs

The way we manage these types of configurations is through env files. We maintain a separate env file for each environment.

We have:

  • .env: for local dev environment
  • .env.test: for test environment
  • .env.production: for production

When the application is packaged for each environment by WebPack, the right configuration file will be picked up. The content of such file is key-value pair, such as below:

HTTPS=true
PORT=8500
HOST=localhost
REACT_APP_API_URL=https://localhost:6011
REACT_APP_API_PORTAL_SUBSCRIPTION=NotRequiredForLocalUse
REACT_APP_INSTRUMENTATION_KEY=NotApplication
PUBLIC_URL=https://localhost:8500
EXTEND_ESLINT = true
REACT_APP_Environment=development

Pitfall

The content of .env files in any environment should be considered public knowledge, and there should not be any risk in exposing them to public.

For .env.(environment), we should have the same set of keys in each file, with different values specific to that environment.

Reading from .env File

We will use dotenv in our project

  1. Create .env file at the root of project

    API_URL=http://localhost:8000
  2. Install dotenv

    yarn add dotenv
  3. Config webpack to add env variables

    const webpack = require('webpack');
    const dotenv = require('dotenv');

    module.exports = () => {
    // call dotenv and it will return an Object with a parsed key
    const env = dotenv.config().parsed;

    // reduce it to a nice object, the same as before
    const envKeys = Object.keys(env).reduce((prev, next) => {
    prev[`process.env.${next}`] = JSON.stringify(env[next]);
    return prev;
    }, {});

    return {
    plugins: [
    new webpack.DefinePlugin(envKeys)
    ]
    };

Lastly, to access these config values, we simply use process.env.(key-name), such as process.env.API_URL

Note

In the world of TypeScript, reading the env variables requires us to enforce type-safety. So we might need to, in case env is a string, do

process.env.API_URL as string

In the case of number

Number(String(process.env.JWTEXPIRES))
Static Configurations

Static configurations are the ones that don’t change from one environment to the other. Examples would be telephone numbers, company names, messages and copies, etc. We simply store these values in a separate file, as they might be subject to change from time to time, and this makes it easier to find and change them.

Storing Static Configuration Values

One way to manage these configs, is to store them in simple json file, such as below:

{
"locations": {
"fetchCountriesUrl": "v1/countries"
},
"seo": {
"domain": "https://test.com",
"siteName": "Test",
"defaultTitle": "Test | Fast Engineering Eco-Systems with No Compromise",
"defaultDescription": "...",
"contact": {
"email": "info@test.com",
"phone": "+61 2 816238786"
},
"address": {
"streetAddress": "U7 678 Orouke Rd",
"addressLocality": "RedFern",
"addressRegion": "NSW",
"addressCountry": "Australia",
"postalCode": "2000"
}
}
}

JSON structure also enables us to store the values in a specific hierarchy which makes it easier to manage.

Reading Static Config Values

All we need to do to access such config values, is to import it in our .ts/.tsx files and access the values like any other json object:

import config from '../../config.json';

and then I can write:

config.locations.fetchCountriesUrl
Constants

The other alternative to manage static values in code, is through TS objects. We typically manage two types of values using TS objects:

  • To store/read those values that are highly unlikely to change from time to time, but we want to keep them separate from our code anyway
  • To store/read those values that won’t be stored in JSON files as they are (and not as strings), such as Regular Expressions (RegEx)

An example would look like below, and you can read them exactly like Option 2, as above:

export const Auth = {
PasswordRegEx: /^(?=.*[A-Z])(?=.*[\W])(?=.*[0-9])(?=.*[a-z]).{8,128}$/,
PasswordFailMessage: "Passwords must have at least 8 characters, 1 lowercase, 1 upper case, 1 number, and 1 special character."
}

Creating a new Package

In general, each sub-package should have 3 basic components to start with:

  1. src that contains the package source files

  2. package.json which hosts the package specific info and dependencies (and dev dependencies)

  3. index.ts

    Deep Dive

    What’s index.ts for?

    Inside my-package/index.ts we would simply do something like this:

    import MyComponent from './src/MyComponent.tsx';

    export default MyComponent;

    and that’s it. This is helpful because inside other components or containers we can do this:

    import MyComponent from '../my-package';

    because it tries to access the index.ts file by default thus not requiring any more info from us. It would import automatically the index.ts file which imports the actual component itself. If we did not have an index.ts file we would have had to do this:

    import MyComponent from '../my-package/src/MyComponent';

    which is kind of awkward. I

At the end of the day, a package fits into a monorepo with the following file structure:

.
└── monorepo/
├── packages/
└── my-package/
├── src
├── index.ts
└── package.json
├── tsconfig.json
├── package.json
└── ...

Troubleshooting

Some warnings pops up from some test files while running yarn test:

Console

console.error
Warning: An update to ToolbarPlugin inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
/* fire events that update state */
});
/* assert on the output */

This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act

We need to do 2 things:

  1. To prepare a component for assertions, we need to wrap the code rendering it and performing updates inside an act() call. This makes our test run closer to how React works in the browser.
  2. Someone out there points out using waitFor construct, although I have no idea why that works4

For example:

import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import { act } from 'react-dom/test-utils';
import MyComponent from "./MyComponent";

test("renders component properly", async () => {
act(() => render(<MyComponent />));

await waitFor(() => {
const linkElement = screen.getByText(/My Great App/i);
expect(linkElement).toBeInTheDocument();
});
});

Footnotes

  1. https://stackoverflow.com/a/44987447/14312712

  2. https://stackoverflow.com/a/63152687/14312712

  3. https://stackoverflow.com/a/72383988

  4. https://stackoverflow.com/a/65811603