Signal R and React

I haven’t touched Signal R in a while. I wanted to see how to work with Signal R in a React app.

Let’s start by creating a simple ASP.NET Core Web API server

  • Create an ASP.NET Core Web API application, I’m going to use minimal API
  • Add the NuGet package Microsoft.AspNetCore.SignalR.Client
  • Add the following to the Program.cs
    builder.Services.AddSignalR();
    builder.Services.AddCors();
    
  • We’ve added CORS support as we’re going to need tthis for testing locally, we’ll also need the following code
    app.UseCors(options =>
    {
      options.AllowAnyHeader()
        .AllowAnyMethod()
        .AllowCredentials()
        .SetIsOriginAllowed(origin => true);
    });
    

Before we can use Signal R we’ll need to add a hub, I’m going to add a file NotificationHub.cs with the following code

public class NotificationHub : Hub;

Now return to Program.cs and add the following

  • We need to map our hub into the application using
    app.MapHub<NotificationHub>("/notifications");
    
  • Finally let’s map an endpoint to allow us to send messages via Swagger to our clients. After the line above, add the following
    app.MapGet("/test", async (IHubContext<NotificationHub> hub, string message) =>
      await hub.Clients.All.SendAsync("NotifyMe",$"Message: {message}"));
    

Now we need a client, so create yourself a React application (I’m using TypeScript as usual with mine).

  • Add the package @microsoft/signalr, i.e. from yarn yarn add @microsoft/signalr
  • In the App.tsx we’re going to create the HubConnectionBuilder against out ASP.NET Core API server. We’ll then start the connection and finally watch for messages on the “NotifyMe” name as previously set up in the ASP.NET app, the code looks like this
    import { HubConnectionBuilder } from '@microsoft/signalr';
    
    function App() {
      const [message, setMessage] = useState("");
    
      useEffect(() => {
        const connection = new HubConnectionBuilder()
          .withUrl("http://localhost:5021/notifications")
          .build();
      
        connection.start();  
        connection.on("NotifyMe", data => {
          setMessage(data);
        });
      }, [])  
    
      return (
        <div className="App">
          {message}
        </div>
      );
    }
    

Make sure you start the ASP.NET server first, then start your React application. Now from the Swagger page we can send messages into the server and out to the React client’s connected to SignalR.

Messing around with MediatR

MediatR is an implementation of the Mediator pattern. It doesn’t match the pattern exactly, but as the creator, Jimmy Bogard states that “It matches the problem description (reducing chaotic dependencies), the implementation doesn’t exactly match…”. It’s worth reading his post You Probably Don’t Need to Worry About MediatR.

This pattern is aimed at decoupling the likes of business logic from a UI layer or request/response’s.

There are several ways we can already achieve this in our code, for example, using interfaces to decouple the business logic from the UI or API layers as “services” as we’ve probably all done for years. The only drawback of this approach is it requires the interfaces to be either passed around in our code or via DI and is a great way to do things. Another way to do this is, as used within UI, using WPF, Xamarin Forms, MAUI and others where we often use in-process message queues to send messages around our application tell it to undertake some task and this is essentially what MediatR is giving us.

Let’s have a look at using MediatR. I’m going to create an ASP.NET web API (obviously you could use MediatR in other types of solutions)

  • Create an ASP.NET Core Web API. I’m using Minimal API, so feel free to check that or stick with controllers as you prefer.
  • Add the nuget package MediatR
  • To the Program.cs file add
    builder.Services.AddMediatR(cfg => 
      cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
    

At this point we have MediatR registering services for us at startup. We can passing multiple assemblies to the RegisterServicesFromAssembly method, so if we have all our reqeust/response code in multiple assemblies we can supply just those assemblies. Obviously this makes our life simpler but at the cost of reflecting across our code at startup.

The ASP.NET Core Web API creates the WeatherForecast example, we’ll just use this for our sample code as well.

The first thing you’ll notice is that the route to the weatherforecast is tightly coupled to the sample code. Ofcourse it’s an example, so this is fine, but we’re going to clean things up here and move the implementation into a file named GetWeatherForecastHandler but before we do that…

Note: Ofcourse we could just move the weather forecast code into an WeatherForecastService, create an IWeatherForecastService interface and there’s no reason not to do that, MediatR just offers and alternative way of doing things.

MediatR will try to find a matching handler for your request. In this example we have no request parameters. This begs the question as to how MediatR will match against our GetWeatherForecastHandler. It needs a unique request type to map to our handler, in this case the simplest thing to do is create yourself the request type. Mine’s named GetWeatherForecast and looks like this

public record GetWeatherForecast : IRequest<WeatherForecast[]>
{
    public static GetWeatherForecast Default { get; } = new();
}

Note: I’ve created a static method so we’re not creating an instance for every call, however this is not required and obviously when you are passing parameters you will be creating an instance of a type each time – this does obviously concern me a little if we need high performance and are trying to write allocation free code, but then we’d do lots differently then including probably not using MediatR.

Now we’ll create the GetWeatherForecastHandler file and the code looks like this

public class GetWeatherForecastHandler : IRequestHandler<GetWeatherForecast, WeatherForecast[]>
{
  private static readonly string[] Summaries = new[]
  {
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
  };

  public Task<WeatherForecast[]> Handle(GetWeatherForecast request, CancellationToken cancellationToken)
  {
    var forecast = Enumerable.Range(1, 5).Select(index =>
      new WeatherForecast
      {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
      })
    .ToArray();

    return Task.FromResult(forecast);
  }
}

At this point we’ve created a way for MediatR to find the required handler (i.e. using the GetWeatherForecast type) and we’ve created a handler to create the response. In this example we’re not doing any async work, so we just wrap the result in a Task.FromResult.

Next go back to the Program.cs or if you’ve used controllers, go to your controller. If using controller you’ll need the constructor to take the parameters IMediator mediator and assign to a readonly field in the usually way.

For our minimal API example, go back to the Program.cs file remove the summaries variable/code and then change the route code to look like this

app.MapGet("/weatherforecast",  (IMediator mediator) => 
  mediator.Send(GetWeatherForecast.Default))
.WithName("GetWeatherForecast")
.WithOpenApi();

We’re not really playing too nice in the code above, in that we’re not returning results code, so let’s add some basic result handling

app.MapGet("/weatherforecast",  async (IMediator mediator) => 
  await mediator.Send(GetWeatherForecast.Default) is var results 
    ? Results.Ok(results) 
    : Results.NotFound())
  .WithName("GetWeatherForecast")
  .WithOpenApi();

Now for each new HTTP method call, we would create a request object and a handler object. In this case we send no parameters, but as you can no doubt see, for a request that takes (for example) a string for your location, we’d create a specific type for wrapping that parameter and the handler can then be mapped to that request type.

In our example we used the MediatR Send method. This sends a request to a single handler and expects a response of some type, but MediatR also has the ability to Publish to multiple handlers. These types of handlers are different, firstly they need to implement the INotificationHandler interface and secondly no response is expected when using Publish. These sorts of handlers are more like event broadcasts, so you might use then to send a message to an email service or database code which sends out an email upon request or updates a database.

Or WeatherForecast sample doesn’t give me any good ideas for using Publish in it’s current setup, so let’s just assume we have a way to set the current location. Like I said this example’s a little contrived as we’re going to essentially set the location for everyone connecting to this service, but you get the idea.

We’re going to add a SetLocation request type that looks like this

public record SetLocation(string Location) : INotification;

Notice that for publish our type is implementing the INotification interface. Our handles look like this (my file is named SetLocationHandler.cs but I’ll put both handlers in there just to be a little lazy)

public class UpdateHandler1 : INotificationHandler<SetLocation>
{
  public Task Handle(SetLocation notification, CancellationToken cancellationToken)
  {
    Console.WriteLine(nameof(UpdateHandler1));
    return Task.CompletedTask;
  }
}

public class UpdateHandler2 : INotificationHandler<SetLocation>
{
  public Task Handle(SetLocation notification, CancellationToken cancellationToken)
  {
    Console.WriteLine(nameof(UpdateHandler2));
    return Task.CompletedTask;
  }
}

As you can see, the handlers need to implement INotificationHandler with the correct request type. In this sample we’ll just write messages to console, but you might have a more interesting set of handlers in mind.

Finally let’s add the following to the Program.cs to publish a message

app.MapGet("/setlocation", (IMediator mediator, string location) =>
  mediator.Publish(new SetLocation(location)))
.WithName("SetLocation")
.WithOpenApi();

When you run up your server and use Swagger or call the setlocation method via it’s URL you’ll see that all your handlers that handle the request get called.

Ofcourse we can also Send and Post messages/request from our handlers, so maybe we get the weather forecast data then publish a message for some logging system to update the logs.

MediatR also includes the ability to stream from a requests where our request type implements the IStreamRequest and our handlers implement IStreamRequestHandler.

If we create a simple request type but this one implements IStreamRequest for example

public record GetWeatherStream : IStreamRequest<WeatherForecast>;

and now add a handler which implements IStreamRequestHandler, something like this (which delay’s to just give a feel of getting data from somewhere else)

public class GetWeatherStreamHandler : IStreamRequestHandler<GetWeatherStream, WeatherForecast>
{
  public async IAsyncEnumerable<WeatherForecast> Handle(GetWeatherStream request, 
    [EnumeratorCancellation] CancellationToken cancellationToken)
  {
    var index = 0;
    while (!cancellationToken.IsCancellationRequested)
    {
      await Task.Delay(500, cancellationToken);
      yield return new WeatherForecast
      {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Data.Summaries[Random.Shared.Next(Data.Summaries.Length)]
      };

      index++;
      if(index > 10)
        break;
    }
  }
}

Finally we can declare our streaming route using Minimal API very simply, for example

app.MapGet("/stream", (IMediator mediator) =>
  mediator.CreateStream(new GetWeatherStream()))
.WithName("Stream")
.WithOpenApi();

Using the Redux Toolkit

I’ve covered Redux on this blog in the past, but the “modern” approach is to use the Redux Toolkit which cleans up and simplifies some of the code. In some cases I found it more complicated, because it obviously does a lot and abstracts a lot.

Going over the basics

Let’s go over some of the basics of Redux first…

Redux is a global state management system. It’s made up of three keys parts, the store which as the name suggests, is where your state is stored. The shape of the data within the store is defined by your needs (i.e. no prerequisites). The store is used for global state, so to partition the store for ease of working with your data, we create slices. We’ll generally have slices based upon the domain, so maybe a slice for feature flags, another for component state, another for an API and so on.

Next we have actions, these are (as the name suggests) used to perform actions against our data. In Redux we have an object with a type and some (optional) payload. The type is the name of the action,

{type: "INCREMENT", payload: 3 }

Finally we have reducers which we use, based upon the type of actions, to carry out updates of state. It’s important to remember that Redux data should be immutable, hence the reducer doesn’t change the state itself, but instead copies the state updating the new version of the state before passing this new copy back

RTK

Let’s now create a bare minimum Redux toolkit store

Note: I’m using TypeScript in my examples

import { configureStore } from "@reduxjs/toolkit"

export const store = configureStore({
  reducer: {}.
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

The code above just creates a Redux store, there’s no reducers. This store is not too useful at this point, but bare with me, let’s now quickly pivot to look at how we might use our store in a React app

import { Provider } from "react-redux"
import { store } from "./state/store.ts"

<Provider store={store}>
   <App />
</Provider>

By using the Provider at the App level our store is now available to our entire app.

As mentioned, the store above is pretty useless at the moment, let’s use the example from Redux/RTK to add a simple counter to the store. This represents one slice of the store hence, we’ll name the file counter.slice.ts (although RTK tends to go with counterSlice.ts style names).

First create a folder for your slices within the store folder, i.e. store/slices. Ofcourse you can place these where best suit, but this is the folder structure I’m using here.

Create the file counter.slice.ts inside store/slices. This, as you probably realised, will contain the counter slice within the state manager, i.e. the data specific to the counter

import { createSlice } from "@reduxjs/toolkit";

interface CounterState {
  value: number;
}

const intialState: CounterState = {
  value: 0;
};

const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    }
  },
});

export const { increment, decrement } = counterSlice.actions;

export default counterSlive.reducer;

In this example we appear to be mutating state, but behind the scenes createSlice handles copying our state (RTK uses Immer to handle such immutability), if you prefer you can use the more obvious state.value + 1.

This file includes the CounterState interface (obviously not required for pure JavaScript) which is a representation of what our counter state will look like, in this case we just store a number, the current counter value. We also have these actions within the reducer, increment and decrement.

We create an initialState so our store has any defaults or starting points, for example in this case we set count initially to zero.

Next, we need to export our actions using export const { increment, decrement } = counterSlice.actions and finally for this section, we now need to register the slice with the store, this is accomplished by adding it to the store reducer, like this

import counterReducer from "./counter/counterSlive";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  }
});

At this point, everything is hooked up in the store, so let’s use our increment/decrement actions. We use the useSelector to get our current state and useDispatch to dispatch the actions to the the store

import { useSelector } from "react-redux";
import { RootState } from "../state/store";
import { decrement, increment } from "../state/counter/counterSlice";

export const Counter = () => {
  const count = useSelector((states: RootState) => state.counter.value);
  const dispatch = useDispatch(); // dispatch actions

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
    </div>

}

Passing parameters

These examples are using simple actions. Let’s extend then by adding a incrementBy

Add a new action to counterSlice like this

incrementBy: (states, action: PayloadAction<number>) => {
  state.value += action.payload;
}

<em>Note: Whilst the += makes it look like we're mutating state, this is just a reminder, in this instance code within RTK is handling this as an immutable update.</em>

The PayloadAction only handles a single parameter, hence if we want to handle multiple parameters, we need to create an interface (in TypeScript) to define them. Let's move from the counter example and assume our Router is accepting multiple query parameters which we want to store in our store. We'll use an example of the github URL where by you can access my blog post code via <a href="https://github.com/putridparrot/blog-projects" rel="noopener" target="_blank">https://github.com/putridparrot/blog-projects</a>, so the first parameter might be gitUser, in this case <em>putridparrot</em>, followed by the repo name - hence we'd create our queryParams.slice.ts as follows

[code]
import { PayloadAction, createSlice } from "@reduxjs/toolkit";

export interface IQueryParams {
  gitUser?: string;
  repoName?: string;
}

const initialState: IQueryParams = {
  gitUser: "",
  repoName: "",
}

const queryParamsSlice = createSlice({
  name: "queryParams",
  initialState: initialState,
  reducers: {
    setQueryParams: (_, action: PayloadAction<IQueryParams>) => 
      action.payload,
  },
});

export const { setQueryParams } = queryParamsSlice.actions;

export default queryParamsSlice.reducer;

Everything’s pretty much as our counter example apart from the PayloadAction<IQueryParams>. In this example we’re actually just passing everything into the store so just returning the payload via setQueryParams.

Async actions

RTK supplies a way to incorporate async actions into the store. If we have something like a network call or another process we know may take some time, we’ll want to make it async. Just for a simple example, we’ll assume increment is now handled via either a network call or some longer running piece of code. We create an async thunk like this

export const incrementAsync = createAsyncThunk(
  "counter/incrementAsync",
  async (amount: number) => {
    // simulate a network call
    await new Promise((resolve) => setTimeout(resolve, 1000));
    return amount;
  }
);

We’re supplying the name of our function counter/incrementAsync here. We now need to add this function to our slice, so after the reducers we add the following

extraReducers: (builder)  => {
  // we can accept state but for this example, we've no need
  builder.addCase(incrementAsync.pending, () => { 
    console.log("Pending");
  }).addCase(incrementAsync.fulfilled, (state, action: PayloadAction<number>) => {
    state.value += action.payload;
  });
}

Notice that each “case” is based upon some state which comes from createAsyncThunk. This is very cool as we now have the ability to update things based upon the current state of the async call, i.e. maybe we display a spinner for pending and remove when fulfilled or rejected occurs and in the case of rejected we now display an error toast message or the

Dispatch

To actually call into the store, i.e. call and action, we need to use code like the following

const dispatch = useDispatch<AppDispatch>();

Usually we’ll just wrap both the dispatch and selector into the following to make working with TypeScript simpler (just place this is some utility file such as hooks.ts or the classic helper.ts named file)

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

So now we can get the current count and update like this

import { decrement, increment } from '../store/slices/counter.slice'
import { useAppDispatch, useAppSelector } from '../store/hook'

export function Counter() {
  const count = useAppSelector((state) => state.counter.value)
  const dispatch = useAppDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}

Calling services or API’s

The example within the RTK documentation is a call to the pokeapi, so let’s start with that. RTK documentation seems to suggest such API’s go into a folder named services, so we’ll stick with that although they are essentially just other slices of global state (at least in my view).

In our services folder add pokemin.ts and here’s the contents of the file

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const pokemonApi = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
  tagTypes: [],
  endpoints: (builder) => ({
    getPokemonByName: builder.query({
      query: (name: string) => `pokemon/${name}`,
    }),
  }),
})

// Export hooks for usage in functional components
export const { useGetPokemonByNameQuery } = pokemonApi

We used createAsyncThunk earlier and ofcourse we can easily implement services/network/API calls via the same mechanism, but the createApi method, which extends createAsyncThunk, generates React hooks for us. We also have options to handle caching and more from here.

In this example we use the fetchBaseQuery defining the baseUrl. This is a lightweight method wrapping the fetch method.

We define our endpoints, i.e. the API to our service and then at the end of the code we export the code generation hook, useGetPokemonByNameQuery.

To use this code within our store we need to go back to store.ts and add the following to the rootReducer

api: pokemonApi.reducer,

and to the middleware we need to concat the API like this

getDefaultMiddleware()
  .concat(pokemonApi.middleware),

Finally we’ll want to use this API, and we simply use the hook like this

export const Pokemon = ({ name, pollingInterval }: { name: string, pollingInterval: number }) => {
  const { data, error, isLoading, isFetching } = useGetPokemonByNameQuery(
    name,
    {
      pollingInterval,
    },
  );

Notice that we now get those async thunk states, but named differently. So we have the data returned, a potential error. We also have isLoading and isFetching for handling progress info.

Multiple parameters and multiple API’s

As mentioned previously RTK accepts a single object as input and this is the same for createApi actions, so if you have a situation where you need to pass multiple parameters to your API, then you handle in the same way (although in this example I’m creating via an anonymous type), i.e.

getContract: builder.query<any, {contractId?: string, version?: string}>({
  query: (arg) => {
    const { contractId, version } = arg;        
    return {
      url: `someurl?contractId=${contractId}&version=${version}`,
   }
  },

With regards multiple API’s, let’s say we have a pokemon and contracts api (I admit an odd combination). Then to register them with the store, as seen with a single API we concat them with the middleware, but we don’t appear to concat each, instead we concat an array like this

getDefaultMiddleware()
  .concat([contractsApi.middleware,  pokemonApi.middleware]),

Dynamic baseUrl and/or setting headers etc.

In the pokemon API example we had a fixed URL for the service, but when developing the front and back end we’ll often have dev and/or test and then production environments. In this case we’ll want to change the baseUrl based upon the environment. In such cases and also when we want to apply our own headers, for example let’s assume our API requires an SSO token, then we’ll want to include that in the call in some way or other.

Here’s our dynamicBaseQuery which we’re actually using the store to store something called enviroment which we’re assume contained data such as the the current environment URL along with the current SSO token for the user for the environment. In the second line we set the baseUrl and then it’s used in the fetchBaseQuery. We also use this to set up the headers.

const dynamicBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (args, WebApi, extraOptions) => {
  const baseUrl = (WebApi.getState() as any).environment.appSettings.contractsApiUrl;
  const rawBaseQuery = fetchBaseQuery({
    baseUrl,
    prepareHeaders: async (headers, { getState }) => {
      const token = (getState() as any).environment.token;
      if (headers) {
        headers.set("Accept", "application/json");
        if(token) {
          headers.set("authorization", `Bearer ${token}`);
        }
      }

      return headers;
    },
  });
  return rawBaseQuery(args, WebApi, extraOptions);
};

To use this we simple replace the baseQuery value with dynamicBaseQuery, like this snippet

export const contractsApi = createApi({
  reducerPath: "contractsApi",
  baseQuery: dynamicBaseQuery,
  // rest of the code ommitted

Tooling

I highly recommend installing the Redux Dev Tools for your browser. These tools allow you to easily see the state of your Redux store (and as RTK is built on Redux we can see our RTK defined store) and best of all, RTK is automatically configured to work with the Redux Dev Tools, so now additional code is required.

Code

Code for this post is available in my blog-projects.

Note: Not all code is hooked up and being used, but hopefully there’s enough to show how things might look.

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.

The IAM User on AWS

When you signed up for AWS you created a Root user account. However we really should create another user (even if they have root like permissions) to run our cloud account.

Why do we need this user if they’re basically admin? Well we can reduce permissions but also delete the user without affecting the root user which we cannot do this on.

Let’s create an IAM (Identify and Access Management) user for our development use which will basically have admin permissions but would not be the user we use four out applications, this is essentially a developer account.

How do we set up our development user?

  • Log into your AWS account as Root user
  • In this search bar type IAM
  • First we want to create a new group, so select Access management | User groups
    • Click Create group
    • Enter a name for the group, usually we’d probably have this name match the application that the group represents, so I’m going to do this for my unit conversion API app, hence my group is UnitConversionApiUsers for my unit conversions API
    • In the Attach permissions policies let’s give this group AdministratorAccess permissions
    • Now click the Create group button
  • You should be placed back on the User groups screen and see out new group with zero users. So now click the Access management | Users option on the left of the screen
    • Click the Create user button
    • Enter a name for the user then click Next
    • Leave the default Add user to group
    • Check/tick the group you added then click the Next button
    • Finally click Create user
  • From the users screen on the Security credentials tab I have also clicked Enable console access, I also check the User must create new password at next sign-in, but you can autogenerate or create a custom password to suite
  • Download the .csv file for later use, but don’t worry if you don’t it will contain User name, Password and Console sign-in URL so you can copy these if you prefer from the UI

Note that the console URL contains the account number, we can change this using the alias option, from the IAM dashboard select the Dashboard option. On the right of the screen you’ll see AWS Account and am option to Create an Account Alias, clicking this we can enter a name to replace the account number in the URL. You’ll see the Sign-in URL change to suit.

Try signing into AWS by using the console URL or it’s alias if you changed that and in my case I was prompted to change the password, so did that and was able to log in.

You’ll also want to go the the user that you created and click Create access key. When completed download the .csv and or copy the access id and secret key so you can used from the AWS CLI or IDE integration.

Collection Expressions in C# 12

C# 12 includes something called Collection Expressions. These offer more generic way to create our collections from array-like syntax.

Let’s look first at the old style creation of an array of integers

var array = new [] { 1, 2, 3 };

This is simple enough array is of type int[]?. This way of creation arrays is not going away, but what if we want to change the array to a different collection then we end up using collection initializers like this

var list = new List<int> { 1, 2, 3 };

There’s nothing much wrong with this, but essentially we’re sort of doing the same thing, just with different syntax.

Collection expressions now allow us to use syntax such as (below) to create our collection regardless of type

int[] array = [1, 2, 3 ];
List<int> list = [1, 2, 3 ];

On the surface this may not seem a big deal, but imagine you’ve a class that accepts an int[] and maybe you change the type to a List, passing the values via the collection expression [] syntax means that part of your code remains unchanged, it just remains as [1, 2, 3].

Along with this we get to use the spread operator .. for example

List<int> list = [1, 2, 3 ];
int[] array = [.. list];

In this example we’ve created a list then basically copied the items to the array, but a spread operator can be used to concatenate values (or collections), such as

int[] array = [-3, -2, -1, 0, .. list];

Creating your own collections to use collection expressions

For many of the original types, such as List<T> the collection expression code is built in. But newer collections and, if we want, our own collection can take advantage of this syntax by following a minimal set of rules.

All we need to do is create our collection type and add the CollectionBuilderAttribute to it like this

[CollectionBuilder(typeof(MyCollection), nameof(MyCollection.Create))]
public class MyCollection<T>
{
   // our code
}

Now this is not going to work, the typeof expects a non-generic type, so we create a simple non-generic version of this class to handle the creation of the generic version. Also notice the CollectionBuilder expects the name of the method to call and expects a method that takes a single parameter of type ReadOnlySpan and returns the collection type, now initialized, like this

public class MyCollection
{
  public static MyCollection<T> Create<T>(ReadOnlySpan<T> items)
  {
     // returns a MyCollection<T>
  }
}

Let’s look at potential bare minimum implementation of this collection type which can be used with the collection expression syntax. Notice we will also need to implement IEnumerable and/or IEnumerable<T>

[CollectionBuilder(typeof(MyCollection), nameof(MyCollection.Create))]
public class MyCollection<T> : IEnumerable<T>
{
  public static readonly MyCollection<T> Empty = new(Array.Empty<T>());

  private readonly List<T> _innerCollection;

  internal MyCollection(T[]? items)
  {
    _innerCollection = items == null ? new List<T>() : [..items];
  }

  public T this[int index] => _innerCollection[index];
  public IEnumerator<T> GetEnumerator() => _innerCollection.GetEnumerator();
  IEnumerator IEnumerable.GetEnumerator() => _innerCollection.GetEnumerator();
}

public class MyCollection
{
  public static MyCollection<T> Create<T>(ReadOnlySpan<T> items)
  {
    return items.IsEmpty ? 
      MyCollection<T>.Empty : 
      new MyCollection<T>(items.ToArray());
  }
}

Ofcourse this is a silly example as we’re not adding anything that the inner List<T> cannot supply, but you get the idea. Now we can use the collection expression syntax on our new collection type

MyCollection<int> collection = [1, 2, 6, 7];