Author Archives: purpleblob

Dockerize your React application

You’ve created you React application and are now looking to create a docker image for it.

Before we look at the Dockerfile, create yourself a .dockerignore file that looks like this

.git
node_modules

We do not require the .git folder in our image and we’re going to install our node modules via npm install as you’ll find copying the node_modules folder(s) is slow.

Let’s jump straight in and look at a Dockerfile that will take our React code containerize it and set the container up to run nginx to host it.

FROM node:21-alpine3.18 as build

WORKDIR /usr/app
COPY . .
RUN npm install
RUN npm run build

FROM nginx:alpine
COPY --from=build /usr/app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

We’re using a node alpine image as our base. This is a lightweight image as we won’t our image to be lean. Next we create our WORKDIR, this can be set to your preferred location but just remember to reuse that location in the COPY command. We’re using .dockerignore to ignore .git and node_modules which allows us to then just copy everything to the image, where we then install then build our React application.

Finally we use nginx to serve our application, first copying the build folder to nginx and finally we set up the CMD to run nginx once the container is started, thus hosting our React application.

To build our image we can just use the following (change the tag name and version to suit your application)

docker build -t my-app:0.1.0 .

and to run, again set your application name any port redirects and use you tagged and version

docker run --rm --name my-app -p 8080:80 -d my-app:0.1.0

We could also extend this sample to include copying of nginx.conf to the image, for example if you want the supply a config like this

worker_processes 4;

events { worker_connections 1024; }

http {
  server {
    listen 4200;
    root  /usr/share/nginx/html;
    include /etc/nginx/mime.types;

  location / {
    root   /usr/share/nginx/html;
    index  index.html;
    try_files $uri $uri/ /index.html;
  }
}
}

If we assume we’re storing out nginx.conf file in a folder named .ngnix (this is not required it’s just for this example) then we could add the following to the Dockerfile, after the line FROM nginx:alpine add the following and whilst we’re at it let’s get rid of the default files that might be located in the images nginx/html folder

COPY ./.nginx/nginx.conf /etc/nginx/nginx.conf
RUN rm -rf /usr/share/nginx/html/*

Docker Compose

Whilst we’re here, let’s create a simple docker-compose.yaml file for our new image.

version: '3.8'
services:
  front-end:
    build:
      context: ./ui
      dockerfile: ./ui/Dockerfile
    ports:
      - 4200:4200
    image: putridparrot/my-app:0.1.0
    container_name: my-app

We might like to store configuration for this image on the hosting server, i.e. via a volume in which case we’d simply add to the bottom of this file the following

    volumes:
      - ./ui/public/appsettings.json:/usr/share/nginx/html/appsettings.json

In this example we’re using an appsettings.json file to configure the environment, or it might include feature flag settings or whatever and assuming it’s stored in the public folder of you React application.

Now we just docker-compose up.

Zustand state management

I’m used to using Redux and more recently Redux Toolkit for global state management in React, however along with state management libraries such as Mobx there’s another library of interest to me, named Zustand. Let’s see how to set up and project and use Zustand and take a very high level look at how to set-up a project with Zustand…

Create yourself a React application, as usual I’m using TypeScript.

  • Add Zustand using yarn add zustand
  • Our store is a hook, and to create the store we use the create method, for example
    import { create } from "zustand";
    
    interface CounterState {
        counter: number;
        increment: () => void;
        decrement: () => void;
    }
    
    export const useCounterStore = create<CounterState>(set => ({
        counter: 0,
        increment: () => set(state => ({ counter: state.counter + 1 })),
        decrement: () => set(state => ({ counter: state.counter - 1 })),
    }));
    

The above creates a simple store, with state and methods to interact with the state. As we’re using TypeScript, we’ve declared the interface matching our state.

To use this state we simply use the hook like this (change App.tsx to look like the following)

import './App.css';
import { useCounterStore } from "./store";

function App() {
  const { counter, increment, decrement } = useCounterStore();

  return (
    <div className="App">
      <button onClick={increment}>+</button>
      <div>{counter}</div>
      <button onClick={decrement}>-</button>
    </div>
  );
}

export default App;

We can also get slices of our state using the hook like this

const counter = useCounterStore(state => state.counter);

Before we move on, unlike RTK we need to enable redux devtools if we want to view the state in the Redux DevTools in our Browser, so to add the dev tool extensions do the following

  • yarn add @redux-devtools/extension
  • We need to import the devtools and change our store a little, so here’s the store with all the additions
    import { create } from "zustand";
    import { devtools } from "zustand/middleware"
    import type {} from "@redux-devtools/extension";
    
    interface CounterState {
      counter: number;
      increment: () => void;
      decrement: () => void;
    }
    
    export const useCounterStore = create<CounterState>()(
      devtools (
        set => ({
          counter: 0,
          increment: () => set(state => ({ counter: state.counter + 1 })),
          decrement: () => set(state => ({ counter: state.counter - 1 })),
        }),
        {
          name: "counter-store",
        }
      )
    );
    

Zustand also has the ability to wrap our global state within a persistence middleware. This allows us to save to various types of storage. We simply wrap our state in persist like this

import { devtools, persist } from "zustand/middleware"

export const useCounterStore = create<CounterState>()(
  devtools (
    persist (
      set => ({
        counter: 0,
        increment: () => set(state => ({ counter: state.counter + 1 })),
        decrement: () => set(state => ({ counter: state.counter - 1 })),
      }),
      {
        name: "counter-store",
      }
    )
  )
);

By default (as in the above code) this state will be persisted to localStorage.

Go check your Application | Local Storage in Edge or Chrome developer tools, for example for Local Storage I have a key counter-store with the value {“state”:{“counter”:4},”version”:0}.

Code

Code from this post is available on github.

React with Signals

Signals are another way of managing application state.

You might ask, “well great but we already have hooks like useState so what’s the point?”

In answer to the above perfectly valid question, Signals work on a in a more granular way – let’s compare to useState. If we create a simple little (and pretty standard) counter component we can immediately see the differences.

Create yourself a React application, I’m using yarn but use your preferred package manager.

yarn create react-app react-with-signals --template typescript

Add the signal package using

yarn add @preact/signals-react

Now we’ll create a folder named components and add a file named CounterState.tsx which looks like this

import { useState } from "react";

export const CounterState = () => {
  const [count, setCount] = useState(0);

  console.log("Render CounterState");

  return (
    <div>
      <div>Current Value {count}</div>
      <div>
        <button onClick={() => setCount(count - 1)}>Decrement</button>
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </div>
    </div>
  );
}

It’s not very pretty but it’s good enough for this demonstration.

Finally create a new file in the components folder named CounterSignals.tsx which should look like this

import React from 'react';
import { useSignal } from "@preact/signals-react";

export const CounterSignals = () => {
  const count = useSignal(0);

  console.log("Render CounterSignals");

  return (
    <div>
      <div>Current Value {count}</div>
      <div>
        <button onClick={() => count.value--}>Decrement</button>
        <button onClick={() => count.value++}>Increment</button>
      </div>
    </div>
  );
}

As you can see, the Signals code creates a Signals object instead of the destructuring way used with useState and the Signals object will not change, but when we change the value, the value within the object changes but does not re-rendering the entire component each time.

Let’s see this by watching the console output in our preferred browser, so just change App.tsx to look like this

import React from ‘react’;
import { CounterState } from ‘./components/CounterState’;
import { CounterSignals } from ‘./components/CounterSignals’;

function App() {
return (


);
}

export default App;
[/em]

Start the application. You might wish to disable React.StrictMode in the index.tsx as this will double up the console output whilst in DEV mode.

Click the Increment and Decrement buttons and you’ll see our useState implementation renders on each click whereas the counter changes for the Signals version but no re-rendering happens.

Code

Code for this example using Signals can be found on github.

i18n in React

i18n (internationalization) is the process of making an application work in different languages and cultures. This includes things like, translations of string resources through to to handling date formats as per the user’s language/culture settings, along with things like decimal separators and more.

There are several libraries available for helping with i18n coding but we’re going to focus on the react-i18next in this post, for no other reason that it’s by other teams in the company I’m working for at the moment.

Getting Started

The react-i18next website is a really good place to get started and frankly I’m very likely to cover much the same code here, so their website should be your first port of call.

If you’re want to instead follow thought the process here then, let’s create a simple React app with TypeScript (as is my way) using

npx create-react-app i18n-app --template typescript

Now add the packages react-i18next and i18next i.e.

npm install react-i18next i18next --save

In this Getting Started I’m going to also include the following libraries which will handle loading the translation files and more

npm install i18next-http-backend i18next-browser-languagedetector --save

Adding localized strings

We’re going to first show an example of embeding the string into a .ts file, however it’s much more likely we’ll want them in a separate file (or multiple files). Let’s get this started by creating a file named i18n1.ts.

In the file we’ll just write one string and here it is

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        translation: {
          welcome: "code: Hello string en World"
        },
      },
      fr: {
        translation: {
          welcome: "code: Bonjour string fr World"
        }
      }
    },
    debug: true,
    interpolation: {
      escapeValue: false, 
    },
  });

export default i18n;

As you can see we’re using the LanguageDetector to automatically set our resources. On my browser the language is set to en-GB so my expectation is to see the en strings as I’ve not listed en-GB specific strings.

IMPORTANT: Before we can use this, go to index.tsx and import this file import “./i18n1”; so the bundler includes it.

Display/using our localized strings

Now we’ll need to actually use our translation strings. react-i18next comes with hooks, HOC’s and standard JS type functions to interact with our translated strings. Let’s clear out most of the App.tsx and make it look like this

import React from "react";
import "./App.css";
import { useTranslation } from "react-i18next";

const lngs: any = {
  en: { nativeName: "English"},
  fr: { nativeName: "French"},
}

function App() {
  const { t, i18n } = useTranslation();

  return (
    <div className="App">
      <div className="App-header">
        {t("welcome")}
        <div>
          {Object.keys(lngs).map(lng => {
            return <button key={lng} style={{margin: "3px"}}
              onClick={() => i18n.changeLanguage(lng)} disabled={i18n.resolvedLanguage === lng}>{lngs[lng].nativeName}</button>
          })}
        </div>
      </div>
    </div>
  );
}

export default App;

This code will simply display the English/French buttons to allow us to change the language as we wish. Notice, however, that if we add a new language, such as de: { nativeName: “German”} and no strings exist for that language, you’ll end up seeing the “key” for the string. We can solve this later.

At this point if all is working, you can start the application up and switch between the languages. You’ll see that the strings will be prefixed with code: just to make it clear where the strings are coming from, i.e. our code file.

Moving to resource type files (part 1)

As I mentioned, we probably don’t want to embed our string in code. It’s preferable to move them into their own .JSON files.

Create a folder within the src folder named locales (the names of the folders and files doesn’t really matter but it’s good to be consistent) and within that we’ll have one folder name en-GB and another named fr. So the English strings are specific to GB but the French covers all French languages locales.

Now in en-GB create the file translations.json which will look like this

{
  "welcome": "src: Hello en-GB World"
}

For the French translations, add translations.json to the fr folder and it should look like this

{
 "welcome": "src: Bonjour fr World"
}

Note the src: prefix is again, just there to allow us to see where our resources are coming from in this demo.

We now need to change our i18n.ts file to look like this

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";

import enGB from "../src/locales/en-GB/translation.json";
import fr from "../src/locales/fr/translation.json";

const resources = {
  en: {
    translation: enGB
  },
  fr: {
    translation: fr
  }
};
i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources,
    debug: true,
    interpolation: {
      escapeValue: false, 
    },
  });

export default i18n;

So in this code we’reimporting the JSON and then assigning to the resources const. This is very similar to the other way we bought the resources into the i18n.ts file, just we’re importing via JSON files.

Moving to resource type files (part 2)

There’s another way to import the translated strings and that is to simply include the files in the public folder of our React application. So in the public folder add the same folders and files, i.e. locales folder with en-GB and fr folders with the same translation.json files as the last example. I’ve changed the src: prefix on the strings to public: again just so I can prove, for this demo, where the strings originate from.

In other words, here’s the en-GB translations.json file

{
  "welcome": "public: Hello en-GB World"
}

and the fr translations.json file

{
  "welcome": "public: Bonjour fr World"
}

Now back in our i18n.ts file change it to look like this

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import Backend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";

i18n
  .use(Backend)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: {
      "en" : ["en-GB"]
    },
    debug: true,
    interpolation: {
      escapeValue: false, 
    },
  });

export default i18n;

Fallback

My browser is setup as English (United Kingdom) which is en-GB and all works well. But what happens if we add a detect a language where we have no translation strings for? Well let’s try it by adding a German option to the lngs const in our App.tsx, so it looks like this

const lngs: any = {
  en: { nativeName: "English"},
  fr: { nativeName: "French"},
  de: { nativeName: "German"},
}

Now, what happens ? Well we’ve see the key for the string, which is probably not what we want in production, better to fall back to a known language. So we need to change the i18n.ts file and add a fallbackLng, I’ll include the whole i18n object so it’s obvious

i18n
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    resources,
    lng: "en-GB",
    fallbackLng: "en",
    debug: true,
    interpolation: {
      escapeValue: false, 
    },
  });

Now if our application encounters a locale it’s not setup for, it’ll default to the fallback language (in this case) English. We can also achieve this using

fallbackLng: {
  "default": ["en"]
},

Using this syntax we can also map different languages to specific fallback languages, so for example we might map Swiss locale to map to use French or Italian. This is achieved by passing an array of fallback languages (as taken from the <a href="https://www.i18next.com/principles/fallback" rel="noopener" target="_blank">Fallback documentation</a>

[code]
fallbackLng: { 
  "de-CH": ["fr", "it"], // French and Italian are also spoken in Switzerland
},

Finally with regards fallback languages, we can write code to determine the translation to use, again the Fallback documentation has a good example of this, so I’d suggest checking that link out.

Code

Code for this post is available on github which includes i18n files 1-3 for each of these options for creating translations. just change the index.tsx to import the one you wish to use.

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 key 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 counterSlice.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/counterSlice";

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.