Monthly Archives: January 2024

React Context API

The React Context API allows us to share data between components without the need for passing props to each child component. One of the biggest data problems with have in React is how to pass data down an element tree from parent to a child element. There’s several ways to achieve this including Redux and Mobx as well as the slightly more dreaded “prop drilling”.

Using the Context API we can essentially declare components which accept a specific context and in the parent we “provide” that context. This means child components will get context directly.

Let’s see how we implement a context…

We need a context (the data), a context provider and a context consumer.

  • Create a folder names contexts
  • We’ll create a simple string array for our data, so we create our context like this (mine’s in a file named NameContext.tsx
    import { createContext } from "react";
    
    export const NameContext = createContext<string[]>([]);
    

    In a real world application the NameContext file would have the types/interfaces for our application data, but we’re just making a simple example here.

  • Now we can wrap any part of our application in our NameContext, so in App.tsx I’ll have the following
    import { useState } from 'react';
    import './App.css';
    import { NameContextList } from './components/List';
    import { NameContext } from './contexts/NameContext';
    
    function App() {
      const [issues, _] = useState(["One", "Two", "Three"]);
    
      return (
        <NameContext.Provider value={issues}>
          <NameContextList />
        </NameContext.Provider>
      );
    }
    
    export default App;
    
  • How you handle this part is upto you, but I’m going to first create a List component which we can pass props to, then a context consumer – this will allow me to test the actual component without having to create a context provider, so my List.tsx looks like this
    import { NameContext } from "../contexts/NameContext";
    
    
    export const List = ({items}: {items : string[]}) => (
        <ul>
            {items.map(item => {
                return <li>{item}</li>    
            })}
        </ul>
    )
    
    
    export const NameContextList = () => (
        <NameContext.Consumer>
        {(ctx) => (
            <List items={ctx} />
        )}
      </NameContext.Consumer>
    )
    

    We can also use the hook useContext like this

    export const NameContextList = () => {
        const names = useContext(NameContext)
        return <List items={names} />
    }
    

When we run this application, the App.tsx creates some data via useState and supplies this to the provider. Obviously if you were fetching data from a service or API, facade or whatever, then you’ve ofcourse change this slightly, but the concept’s much the same. The context consumer will then receive the data and it’s not been passed down via the props.

Another addition to this might be to wrap the line

const names = useContext(NameContext);

in a method such as

export const useNameContext = () => {
  const context = useContext(NameContext);

  if(!context) {
     throw new Error("NameContext is in an invald state");
  }

  return context;
}

This then just allows us to write code (especially in Typescript) safe in the knowledge the context was created.

Multi-SPA using Next.js

With the next.config.js we can do some neat things, such as multi-SPA routing.

If you create yourself two Next.js apps, the “main” one is named home in my example and the second is a catalog and we will be able to navigate to.

Go to the next.config.js of the home app. and add the catalog changes listed below

const nextConfig = {
    reactStrictMode: true,
    async rewrites() {
        return [
            {
                source: '/:path*',
                destination: '/:path*'
            },
            {
                source: '/catalog',
                destination: 'http://localhost:3001/catalog'
            },
            {
                source: '/catalog/:path*',
                destination: 'http://localhost:3001/catalog/:path*'
            },
        ]
    }
}

in the catalog app go to it’s next.config.js and have it look like this

const nextConfig = {
    basePath:'/catalog'
}

then when you click a hyperlink to your second app it will actually appear as if it’s simply a page on the first app, with the same port.

Code is available on github as multiSpa.

Getting started with Next.js

I’ve been wanting to try Next.js for a while having seen many comments about it and podcasts talking about it. But what is Next.js?

Obviously your best source of information is to go and read the Next.js. website, but the basics are that it’s a React based framework for developing web applications or even full-stack web applications using React and features from React as the basis.

Then the next question you might have is why use Next.js over React?

Next.js builds upon React and give you a lot of tooling/libraries out of the box, such as routing, API calls etc. From this perspective you can get up and running very quickly. Next.js is a React framework, so we still write React code, but with Next.js lots of things just run out of the box. So why use Next.js over React, I guess the answer really is down to the developer experience, the speed and simplicity of getting things up and running. Life any framework that sits atop a library (or framework) such as React, we also need to be mindful that updates to React may not immediately appear in Next.js. Next.js supports SSR whereas, whilst (currently) React does not by default, but I know there’s lots going on with SSR on React.

As I’ve not used Next.js for anything other than learning I cannot say whether one should use Next.js over React or vice versa, but certainly getting up and running with Next.js is simple and as mentioned, some stuff such as routing is built in, so we don’t need to worry about that, but then once your React app has all those libraries what’s the gain in being with Next.js? I don’t have enough experience with Next.js to answer that, so let’s just get started and play…

Getting up and running

This is just a very short getting started (I’m using the current latest which is 14.0.4)

yarn create next-app

Note: Add @latest if you prefer to the command or the version you want to work with

You’ll be prompted for the name of your app as well as other configuration parameters. All I did was give the application a name and stick with all the default parameters, I am using TypeScript so ensure that one’s selected if you’re following through with the code here.

The app folder contains the page.tsx file which is essentially our main page.

Once create we can run the app using the following to run the development server

yarn dev

Bu default this will use port 3000, if you want to run on a different port change the dev script in package.json like this or when you run dev it will automatically pick the next port.

"dev": "next dev -p 3001",

Further configuration for Next.js takes place in the next.config.js file in the route folder of your app (i.e. where package.json is) and it’s a .js as it’s not passed to the Typescript (or Babel) transpiler.

With Next.js the app folder (as you’d probably expect) is where we find the entry point to our application, in the page.tsx file.

As mentioned, Next.js is built upon React, so we can start coding React components just like a CRA created application.

Suspense in React 18

Suspense is a JSX Element/Component which we use to wrap around child components such that whilst they are loading, we can display something else, like a progress page or other fallback page.

This has obvious uses when your React application starts up and potentially loads data from a remote source etc.

Suspense understands, what the React documentation calls “Suspense-enabled data sources”. These include data fetching frameworks such as Relay and Next.js. Also the lazy import keyword lazy and also can understand promises with the (currently experimental) use hook.

Before we use components within the Suspense boundary, they need to be lazy imported.

A simple example of usage would be

<Suspsense fallback={<Loading />}>
   <Header />
   <NavBar />
   <Content />
   <StatusBar />
</Suspsense>

In this example, the Suspense boundary will load the child components, Header, NavBar etc. whilst loading we’ll see the fallback component. Upon completion that will be removed and we’ll see our full application.

But as mentioned, we need to lazy import these components so we’re have imports like this

const Header = lazy(()=> import("./components/Header"));
const NavBar = lazy(()=> import("./components/NavBar"));
const Content = lazy(()=> import("./components/Content"));
const StatusBar = lazy(()=> import("./components/StatusBar"));

There’s a requirement for lazy imported code to exported as default. If you don’t want to or cannot change the export to default (for example if using micro frontends that you didn’t write) then we can get around this with slightly more verbose syntax.

const Header = React.lazy(() => import("header/Header")
   .then(module => ({ default: module.Header })));

lazy is returning a promise, we can use the continuation as shown above (the then clause), to assign the module’s export to the default: parameter.

Along with use of Suspense to load the children whilst display a fallback, lazy also allows components to be selectively loaded, i.e. not everything is loaded at once, which has obvious results in ensuring better performance when not all components need loading initially, so using Suspense we also now have a nice way to load parts of our application as and when required as distinct parts of an application, each with a fallback. With lazy we also have more options for code splitting to reduce bundle size etc.

Micro frontends using React and Module Federation

Module federation allows us to create micro frontends in a pretty simple way.

Start off by running

npx create-mf-app

You’ll be prompted for a few things (these may change in different versions of this app. so these are current settings)

  • Pick the name of your app. This is essentially the name of the shell/host or main application (however you prefer to think of it). So I’m used to Shell application in MAUI, Xamarin Forms, WPF etc. So mine’s going to be named shell.
  • Project Type. We’re going to choose Application (as this is our shell application)
  • Port number. The default is 8080, but I’m going to choose 3000 (as I already have servers running on 8080 for other things).
  • Framework. We’re going to be using react
  • Language. I’m a TypeScript person, so I’m choosing typescript
  • CSS Accept the default which is CSS

Now our shell will be created. So as the prompt will state

  • cd shell
  • npm install
  • npm start

If all worked you’ll be presented by some large text stating the name of the app, framework etc.

Let’s now create another app in exactly the same way, obviously give it a different name (mine’s about), this is one of your micro frontends, also give it a different port, i.e. 8081 if you stuck with the default 8080 for the shell or in my case 3001.

Now go to that application and run it up using the commands below (again)

  • cd about
  • npm install
  • npm start

If all went well we have an application (the shell) running and the about application running as well. So nothing too exciting to see here, but it just goes to show we’re developing “standard” React apps.

What we will now need to do, is create a component that we’re going to expose from this application. All I’m going to do is move the App.tsx code that shows the application name etc. into a component, so if you want to follow along, let’s add a components folder to the src folder and within that add a About.tsx file and moved the code from the App.tsx into it, so it looks like this

import React from "react";

export const About = () => 
(
    <div className="container">
    <div>Name: about</div>
    <div>Framework: react</div>
    <div>Language: TypeScript</div>
    <div>CSS: Empty CSS</div>
  </div>
);

Now the App.tsx looks like this

import { About } from "./components/About";

const App = () => (
  <About />
);

We need to make some changes to webpack.config.js, so locate the ModuleFederationPlugin section.

  • Locate the exposes section and change it to look like this
    exposes: {
      "./About": "./src/components/About"
    },
    

    We expose all the components we want via this section and the shell app can then access them.

  • In your shell/container application we also need to amend the webpack.config.js and locate the remotes section, as you’ve probably worked out, this is going to register the micro frontends to be used within the shell/container. So mine looks like this

    remotes: {
      about: "about@http://localhost:3001/remoteEntry.js"
    },
    

    Let’s see if this work, open the shell/container’s App.tsx and add/change it to look like the below

    import { About } from 'about/About';
    
    const App = () => (
      <div> 
      <div className="container">
        <div>Name: shell</div>
        <div>Framework: react</div>
        <div>Language: TypeScript</div>
        <div>CSS: Empty CSS</div>
      </div>
      <About />
      </div>
    );
    

    Run the about app and then the shell and if all went to plan you’ll see the “About” component in the shell.

    A little more in depth ModuleFederationPlugin

    This is really cool and we can see much of the work is done on the ModuleFederationPlugin, so let’s look a little more in depth into some of the key features

    • name is the name we’re giving to our application
    • library is used to determine how exposed code will be stored/retrieved
    • filename by default is remoteEntry.js, but we can name it whatever we want, for example remoteAbout.js for our about app
    • remotes, as we’ve seen these point to the “modules” we want to include and have the format
      "app-name": "name@remote-host/entryFilename.js"
      
    • exposes, as we’ve seen also the exposes is used to expose components etc. from our micro frontend, it has the format
      "name" : "location-within-app"
      
    • shared is used to share node libraries which the exposed module depends on.

    Code

    Checkout an example shell app with three Material UI/React micro frontend apps at microfrontend-react. Each app. uses Material UI and the shell app. brings them together to create the basics of an application with header, navigation bar and some content.

    Obviously there’s a lot more to do to have a truly interactive micro frontend based application, but it’s a starting point.