<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Paul Diggle</title>
    <description>The latest articles on Forem by Paul Diggle (@digglp).</description>
    <link>https://forem.com/digglp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1038559%2F0608ad9c-e69c-4bcb-9213-70762afef00f.png</url>
      <title>Forem: Paul Diggle</title>
      <link>https://forem.com/digglp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/digglp"/>
    <language>en</language>
    <item>
      <title>Reusable Loading component in React</title>
      <dc:creator>Paul Diggle</dc:creator>
      <pubDate>Fri, 17 Mar 2023 13:41:45 +0000</pubDate>
      <link>https://forem.com/digglp/reusable-loading-component-in-react-24fk</link>
      <guid>https://forem.com/digglp/reusable-loading-component-in-react-24fk</guid>
      <description>&lt;p&gt;If you're looking for a way to improve the user experience of your website or application, creating a loading component is a great place to start. In this blog post, I'll explore how to create a reusable loading component in Typescript with React. By leveraging Typescript interfaces, my loading component will be flexible enough to work with any data request in my project.&lt;/p&gt;

&lt;p&gt;I'll walk through the process step by step, covering the benefits of using Typescript with React and how it can help me write more robust and maintainable code. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The following steps only cover the main classes used and may not include all classes used in the project. For a fully working example, please refer to the GitHub repository at &lt;a href="https://github.com/digglp/reactloadingcomponent" rel="noopener noreferrer"&gt;https://github.com/digglp/reactloadingcomponent&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0ubsfcds3qx2zm9onef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu0ubsfcds3qx2zm9onef.png" alt="Image description" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkl3qiinvlaiwdos44e3l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkl3qiinvlaiwdos44e3l.png" alt="Image description" width="800" height="703"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Create a new project using the following command:
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;npx create-react-app reactloadingcomponent --template typescript&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will create a new React starter project.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: Install some dependencies
&lt;/h4&gt;

&lt;p&gt;For this project I will add bootstrap, react-bootstrap and axios.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install bootstrap&lt;/code&gt;&lt;br&gt;
&lt;code&gt;npm install react-bootstrap&lt;/code&gt;&lt;br&gt;
&lt;code&gt;npm install axios&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 3: Set up the folder structure. For my React projects, I typically create the following folders:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;src&lt;/code&gt;: The top-level folder for all code&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/domain&lt;/code&gt;: Contains all domain objects

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;src/domain/handlers&lt;/code&gt;: Handlers for handling tasks (e.g., getting data)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/domain/helpers&lt;/code&gt;: Helper objects (e.g., &lt;code&gt;DatesHelper&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/domain/models&lt;/code&gt;: Objects to represent data models (e.g., &lt;code&gt;Weather&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/infrastructure&lt;/code&gt;: Contains external dependencies

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;src/infrastructure/repositories&lt;/code&gt;: Repositories (e.g., &lt;code&gt;WeatherAPI&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/tests&lt;/code&gt;: Contains all test modules, with the same folder structure as the code

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;src/tests/domain&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/tests/handlers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/tests/helpers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/tests/models&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/tests/infrastructure/repositories&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;src/ui&lt;/code&gt;: Contains all React UI code, including components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fseaxofbjnpgkqfuk6fen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fseaxofbjnpgkqfuk6fen.png" alt="Image description" width="330" height="682"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 4: Create the data handler interface.
&lt;/h4&gt;

&lt;p&gt;Next, let's create the &lt;code&gt;iHandler&lt;/code&gt; interface that defines the template for all our data calls:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export interface IHandler {
  runAsync(request?: any): Promise&amp;lt;any&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Step 5: Create the Loader component.
&lt;/h4&gt;

&lt;p&gt;Now, let's create the &lt;code&gt;Loader&lt;/code&gt; component. The &lt;code&gt;Loader&lt;/code&gt; component takes in several props, including the &lt;code&gt;handler&lt;/code&gt; (a handler that the loader will call when loading takes place), &lt;code&gt;handlerData&lt;/code&gt; (any object that the handler function requires), &lt;code&gt;onComplete&lt;/code&gt; (a function to call after the data successfully completes), &lt;code&gt;onError&lt;/code&gt; (a function to call when the data fails), &lt;code&gt;failureMessage&lt;/code&gt; (a string that represents what message to show on failure), and &lt;code&gt;canRetry&lt;/code&gt; (a boolean value to determine whether to show the retry button).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Loader&lt;/code&gt; component is responsible for loading data, handling errors, and displaying a loading spinner or a retry button. It uses React's &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; hooks to manage state and run asynchronous code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
    import { useEffect, useState } from "react";
    import { Button, Spinner } from "react-bootstrap";
    import { IHandler } from "../../../../domain/handlers/IHandler";

    type Props = {
      handler: IHandler;
      handlerData?: any;
      onComplete: (data: any) =&amp;gt; void;
      onErrored: (error: Error) =&amp;gt; void;
      failureMessage?: string;
      canRetry: boolean;
    };

    export const Loader = (props: Props) =&amp;gt; {
      const [isLoading, setIsLoading] = useState(true);
      const [isErrored, setIsErrored] = useState(false);

      useEffect(() =&amp;gt; {
        const run = async () =&amp;gt; {
          if (isLoading) {
            try {
              props.onComplete(await props.handler.runAsync(props.handlerData));
              setIsLoading(false);
            } catch (error: any) {
              props.onErrored(error);
              setIsLoading(false);
              setIsErrored(true);
            }
          }
        };

        run();
      }, [isLoading, props]);

      const retryAsync = async () =&amp;gt; {
        setIsLoading(true);
        setIsErrored(false);
      };

      return (
        &amp;lt;&amp;gt;
          {isLoading &amp;amp;&amp;amp; (
            &amp;lt;Spinner animation="border" role="status"&amp;gt;
              &amp;lt;span className="visually-hidden" data-testid="loading"&amp;gt;
                Loading...
              &amp;lt;/span&amp;gt;
            &amp;lt;/Spinner&amp;gt;
          )}
          {isErrored &amp;amp;&amp;amp; (
            &amp;lt;div&amp;gt;
              {props.failureMessage ? props.failureMessage : &amp;lt;span&amp;gt;"Error loading data"&amp;lt;/span&amp;gt;}
              {props.canRetry &amp;amp;&amp;amp; (
                &amp;lt;Button className="mb-3" variant="secondary" onClick={() =&amp;gt; retryAsync()} data-testid="retryButton"&amp;gt;
                  Retry
                &amp;lt;/Button&amp;gt;
              )}
            &amp;lt;/div&amp;gt;
          )}
        &amp;lt;/&amp;gt;
      );
    };

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Some explanations.
&lt;/h5&gt;

&lt;h6&gt;
  
  
  State and useEffect Hook
&lt;/h6&gt;

&lt;p&gt;The initial state &lt;code&gt;isLoading&lt;/code&gt; is set to true. When the &lt;code&gt;useEffect&lt;/code&gt; hook runs and &lt;code&gt;isLoading&lt;/code&gt; is true, the asynchronous call to the network is executed using &lt;code&gt;props.handler.runAsync(props.handlerData)&lt;/code&gt;. If the call returns without errors, the &lt;code&gt;props.onComplete&lt;/code&gt; function is called and the data returned from the handler is passed back. In case of an error, the &lt;code&gt;try catch&lt;/code&gt; block handles it by calling the &lt;code&gt;props.onErrored&lt;/code&gt; function and setting the &lt;code&gt;isErrored&lt;/code&gt; state to true.&lt;/p&gt;

&lt;h6&gt;
  
  
  Retry
&lt;/h6&gt;

&lt;p&gt;If &lt;code&gt;props.canRetry&lt;/code&gt; is true and &lt;code&gt;isErrored&lt;/code&gt; state is true, the retry button will be displayed. Clicking the button will trigger the &lt;code&gt;retry&lt;/code&gt; function, which sets the &lt;code&gt;isLoading&lt;/code&gt; state to true. This will cause the &lt;code&gt;useEffect&lt;/code&gt; hook to execute and attempt loading again.&lt;/p&gt;

&lt;h6&gt;
  
  
  iHandler functionality
&lt;/h6&gt;

&lt;p&gt;The &lt;code&gt;iHandler&lt;/code&gt; interface allows any object that implements it to use this loading component, making it reusable across all data calls in your application.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 6: Lets use the loader
&lt;/h4&gt;

&lt;p&gt;To demonstrate how the loader component can be used, we'll create a new component and handler to load character data from the Rick And Morty API (&lt;a href="https://rickandmortyapi.com/" rel="noopener noreferrer"&gt;https://rickandmortyapi.com/&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For this, we'll need the following bits of code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository Code&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;IRickAndMortyCharacterRepository.ts&lt;/code&gt; - a repository interface&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;RickAndMortyCharacterRepository.ts&lt;/code&gt; - an implemented repository class&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;IReadRepository.ts&lt;/code&gt; - a base read repository interface&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;BaseReadRepository.ts&lt;/code&gt; - an implemented class that handles the Axios request/response&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;RepositoryConfigs.ts&lt;/code&gt; - a config class that handles environment variables, such as the URL for the API we want to call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Handler Code&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;IHandler.ts&lt;/code&gt; - an interface that works with the Loader component&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;CharacterListHandler.ts&lt;/code&gt; - a class that implements &lt;code&gt;IHandler.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The above structure enables straightforward unit testing by supplying a mock repository to the handler. See the testing section below for more details.&lt;/em&gt;&lt;/p&gt;

&lt;h6&gt;
  
  
  BaseReadRepository.ts
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios, { AxiosRequestConfig } from "axios";

import { IReadRepository } from "./IReadRepository";

export class BaseReadRepository&amp;lt;T&amp;gt; implements IReadRepository&amp;lt;T&amp;gt; {
  async getDataFromUrlAsync(url: string): Promise&amp;lt;T&amp;gt; {
    const requestConfig = {
      headers: {},
      timeout: 10000,
    } as AxiosRequestConfig;

    const response = await axios.get(url, requestConfig);
    const data = response.data;

    return data;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose of the BaseReadRepository class is to handle asynchronous read requests. This code uses the axios library to send a GET request to a specified URL and returns the response data. Other repository classes can utilize this implementation to handle read requests in a consistent manner.&lt;/p&gt;

&lt;h6&gt;
  
  
  RepositoryConfig.ts
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export class RepositoryConfigs {
  static configuration = {
    development: {
      characterUrl: "https://rickandmortyapi.com/api/character",
    },
    beta: {
      characterUrl: "https://rickandmortyapi.com/api/character",
    },
    production: {
      characterUrl: "https://rickandmortyapi.com/api/character",
    },
  } as any;

  static getCharacterUrl(environment: string) {
    return this.getUrl(environment, "characterUrl");
  }

  private static getUrl(environment: string, url: string) {
    if (environment) return RepositoryConfigs.configuration[environment][url];
    else throw new Error("No environment found");
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;RepositoryConfigs&lt;/code&gt; class provides a simple way to store environmental variables. An example of how to access a stored variable is by calling &lt;code&gt;RepositoryConfigs.getUrl('beta', 'characterUrl')&lt;/code&gt;.&lt;/p&gt;

&lt;h6&gt;
  
  
  RickAndMortyCharacterRepository.ts
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { IRickAndMortyCharacterRepository } from "./IRickAndMortyCharacterRepository";
import { RepositoryConfigs } from "./../RepositoryConfigs";
import { BaseReadRepository } from "../BaseReadRepository";
import { ResponseSchema } from "../../../domain/models/ResponseSchema";

export class RickAndMortyCharacterRepository
  extends BaseReadRepository&amp;lt;ResponseSchema&amp;gt;
  implements IRickAndMortyCharacterRepository
{
  url;

  constructor(environment: string) {
    super();
    this.url = RepositoryConfigs.getCharacterUrl(environment);
  }

  async getCharactersAsync(pageNumber: number): Promise&amp;lt;ResponseSchema&amp;gt; {
    const url = `${this.url}?page=${pageNumber}`;
    const characters = await this.getDataFromUrlAsync(url);

    return characters;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;RickAndMortyCharacterRepository&lt;/code&gt; class extends the base class &lt;code&gt;BaseReadRepository&lt;/code&gt; and uses its method &lt;code&gt;getDataFromUrlAsync(url)&lt;/code&gt; to make a request to the Rick and Morty character API. This class encapsulates the details of calling the API, such as constructing the URL with the specific endpoint and capturing the page number. An example usage of this class would be to call &lt;code&gt;const characters = await new RickAndMortyCharacterRepository().getCharacters(pageNumber);&lt;/code&gt; to retrieve a page of characters.&lt;/p&gt;

&lt;h6&gt;
  
  
  CharacterListHandler.ts
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { IRickAndMortyCharacterRepository } from "./../../../infrastructure/repositories/rickandmortyrepository/IRickAndMortyCharacterRepository";
import { IHandler } from "../IHandler";
import { CharacterListRequest } from "../../models/CharacterListRequest";

export class CharacterListHandler implements IHandler {
  constructor(private rickAndMortyCharacterRepository: IRickAndMortyCharacterRepository) {}
  async runAsync(characterRequest: CharacterListRequest): Promise&amp;lt;any&amp;gt; {
    try {
      const response = await this.rickAndMortyCharacterRepository.getCharactersAsync(characterRequest.pageNumber);
      if (characterRequest.simulatedDelayMs &amp;gt; 0) await this.wait(characterRequest.simulatedDelayMs);

      return response.results;
    } catch (error: any) {
      throw new Error("Error getting character data");
    }
  }

  wait = (ms: number) =&amp;gt; new Promise((resolve) =&amp;gt; setTimeout(resolve, ms));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CharacterListHandler&lt;/code&gt; class implements the &lt;code&gt;IHandler&lt;/code&gt; interface and takes an instance of the &lt;code&gt;IRickAndMortyCharacterRepository&lt;/code&gt; interface as a parameter. It defines a method &lt;code&gt;runAsync&lt;/code&gt; that takes a &lt;code&gt;CharacterListRequest&lt;/code&gt; object and returns a Promise. The &lt;code&gt;runAsync&lt;/code&gt; method calls the &lt;code&gt;getCharactersAsync&lt;/code&gt; method of the injected repository to retrieve a list of characters, with an optional simulated delay. If there is an error, it throws an error with a generic message. The class also contains a private &lt;code&gt;wait&lt;/code&gt; method to pause execution for a specified number of milliseconds.&lt;/p&gt;

&lt;h6&gt;
  
  
  CharacterList.tsx
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useEffect, useState } from "react";
import { Button, Card } from "react-bootstrap";
import { CharacterListHandler } from "../../../domain/handlers/RickAndMortyHandlers/CharacterListHandler";
import { Character } from "../../../domain/models/Character";
import { CharacterListRequest } from "../../../domain/models/CharacterListRequest";
import { RickAndMortyCharacterRepository } from "../../../infrastructure/repositories/rickandmortyrepository/RickAndMortyCharacterRepository";
import { useHelper } from "../../hooks/useHelper";
import { Loader } from "../utilities/loader/Loader";

export const CharacterList = () =&amp;gt; {
  const helper = useHelper();
  const [loading, setLoading] = useState(true);
  const [characterData, setCharacterData] = useState&amp;lt;Character[]&amp;gt;([]);
  const [pageNumber, setPageNumber] = useState(1);

  const characterListHandler = new CharacterListHandler(new RickAndMortyCharacterRepository(helper.getEnvironment()));

  const onLoadingComplete = (data: any) =&amp;gt; {
    // processDepots(data);
    console.log("Data loaded: ", data);
    setCharacterData(data);
    setLoading(false);
  };
  const onLoadingErrored = (error: Error) =&amp;gt; {
    console.log(error.message);
  };

  useEffect(() =&amp;gt; {
    setLoading(true);
  }, [pageNumber]);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;Card&amp;gt;
        &amp;lt;Card.Header&amp;gt;Character loader with 5 second delay&amp;lt;/Card.Header&amp;gt;
        &amp;lt;Card.Body&amp;gt;
          {loading &amp;amp;&amp;amp; (
            &amp;lt;Loader
              handler={characterListHandler}
              handlerData={new CharacterListRequest(500, pageNumber)}
              onComplete={onLoadingComplete}
              onErrored={onLoadingErrored}
              failureMessage="Error loading character data"
              canRetry={true}
            /&amp;gt;
          )}
          {!loading &amp;amp;&amp;amp; characterData &amp;amp;&amp;amp; (
            &amp;lt;&amp;gt;
              &amp;lt;Button className="me-3" onClick={() =&amp;gt; setPageNumber(pageNumber &amp;gt; 0 ? pageNumber - 1 : pageNumber)}&amp;gt;
                Load previous page
              &amp;lt;/Button&amp;gt;
              &amp;lt;Button onClick={() =&amp;gt; setPageNumber(pageNumber + 1)}&amp;gt;Load next page&amp;lt;/Button&amp;gt;
              &amp;lt;h3&amp;gt;Character Data Loaded&amp;lt;/h3&amp;gt;
              &amp;lt;ul&amp;gt;
                {characterData.map((character) =&amp;gt; (
                  &amp;lt;li key={character.id}&amp;gt;
                    {character.name} - {character.location.url}
                  &amp;lt;/li&amp;gt;
                ))}
              &amp;lt;/ul&amp;gt;
            &amp;lt;/&amp;gt;
          )}
        &amp;lt;/Card.Body&amp;gt;
      &amp;lt;/Card&amp;gt;
    &amp;lt;/&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CharacterList&lt;/code&gt; react component uses the &lt;code&gt;Loader&lt;/code&gt; component to load data from the Rick And Morty character api, and demonstrates how to manage loading state. It dynamically updates the &lt;code&gt;Loader&lt;/code&gt; component based on user interaction, by re-initializing with new parameters when the page number changes. The loaded data is displayed as a list of characters with their names and location URLs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 7: Writing some unit tests
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { RepositoryConfigs } from "../../infrastructure/repositories/RepositoryConfigs";
import { CharacterListHandler } from "../../domain/handlers/RickAndMortyHandlers/CharacterListHandler";
import { RickAndMortyCharacterRepository } from "../../infrastructure/repositories/rickandmortyrepository/RickAndMortyCharacterRepository";
import axios from "axios";
import {
  setupTestEnvironment,
  restoreEnvironment,
  mockResolvedValue,
  Verb,
  testEnvironment,
  getAxiosHeaders,
  mockRejectedValue,
} from "./TestBase";
import { CharacterListRequest } from "../../domain/models/CharacterListRequest";

jest.mock("axios");

describe("Character List API Test Suite", () =&amp;gt; {
  beforeEach(() =&amp;gt; {
    setupTestEnvironment();
  });

  afterEach(() =&amp;gt; {
    restoreEnvironment();
  });

  it("should return character data", async () =&amp;gt; {
    mockResolvedValue(getValidCharacterDataResponse(), Verb.GET);

    const rickAndMortyCharacterRepository = new RickAndMortyCharacterRepository(testEnvironment);
    const characterListHandler = new CharacterListHandler(rickAndMortyCharacterRepository);

    const characterList = await characterListHandler.runAsync(new CharacterListRequest(0, 0));

    expect(axios.get).toHaveBeenCalledWith(
      expect.stringContaining(RepositoryConfigs.getCharacterUrl(testEnvironment)),
      getAxiosHeaders()
    );
    expect(characterList).toEqual(getValidCharacterDataResponse().data.results);
  });
  it("should throw error when network fails", async () =&amp;gt; {
    mockRejectedValue(new Error("Test axios error"), Verb.GET);
    const rickAndMortyCharacterRepository = new RickAndMortyCharacterRepository(testEnvironment);
    const characterListHandler = new CharacterListHandler(rickAndMortyCharacterRepository);

    await expect(characterListHandler.runAsync(new CharacterListRequest(0, 0))).rejects.toThrow(
      "Error getting character data"
    );
  });
});

const getValidCharacterDataResponse = () =&amp;gt; {
  return {
    data: {
      info: {
        count: 671,
        pages: 34,
        next: "https://rickandmortyapi.com/api/character/?page=2",
        prev: "",
      },
      results: [
        {
          id: 1,
          name: "Rick Sanchez",
          status: "Alive",
          species: "Human",
          type: "",
        },
        {
          id: 2,
          name: "Morty Smith",
          status: "Alive",
          species: "Human",
          type: "",
        },
      ],
    },
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have created some basic unit tests to demonstrate how to test API calls. These tests are located in the integration folder because they test the interaction between the handler and the repository. The tests use mock axios responses. The positive test ensures that the handler is calling the correct URL and returns the mocked data, while the negative test tests error handling. Note that this is not a comprehensive test suite but rather serves as an example of how to mock repository interfaces.&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary
&lt;/h4&gt;

&lt;p&gt;The React Loading Component is a reusable React component designed to handle data loading from external APIs. It provides a loading indicator while data is being retrieved and displays an error message if the data retrieval fails. It also allows for easy retrying of data retrieval and supports custom error messages. The component can be easily configured and customized to fit various use cases. The code is open-source and available on GitHub.&lt;/p&gt;

&lt;p&gt;I am new to React and TypeScript and still learning. If you have any suggestions or improvements, please feel free to add them to the comments or contribute to the GitHub repository. If you made it this far, well done and thank you for reading.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>How to create a Launchpad Mini digital clock using Node.js</title>
      <dc:creator>Paul Diggle</dc:creator>
      <pubDate>Sat, 04 Mar 2023 20:32:44 +0000</pubDate>
      <link>https://forem.com/digglp/how-to-create-a-launchpad-mini-digital-clock-using-nodejs-2d59</link>
      <guid>https://forem.com/digglp/how-to-create-a-launchpad-mini-digital-clock-using-nodejs-2d59</guid>
      <description>&lt;p&gt;The Launchpad Mini is a MIDI controller with 8x8 buttons that can be used to trigger clips, play notes, and control various software applications. One fun project you can do with the Launchpad Mini is to turn it into a digital clock. This project uses NodeJS and the &lt;a href="https://www.npmjs.com/package/midi-stream" rel="noopener noreferrer"&gt;midi-stream npm package&lt;/a&gt; to communicate with the device.&lt;/p&gt;

&lt;p&gt;In this blog post, I will outline the steps I took and problems I encountered.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs74c9wamdxodoe14hz8d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs74c9wamdxodoe14hz8d.jpg" alt=" " width="480" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the clock for yourself
&lt;/h2&gt;

&lt;p&gt;Before we get started, make sure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Launchpad Mini device&lt;/li&gt;
&lt;li&gt;NodeJS installed on your computer&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Setting up the project
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clone the &lt;a href="https://github.com/digglp/LaunchpadClock" rel="noopener noreferrer"&gt;Launchpad Clock repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install the project with &lt;code&gt;npm install&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Execute the program
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Execute the project with &lt;code&gt;npx ts-node src/application/console.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftvz1s8u2b8rn6e3ida6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftvz1s8u2b8rn6e3ida6a.png" alt="Select a port (running on Raspberry Pi)" width="376" height="86"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the clock
&lt;/h2&gt;

&lt;p&gt;To start, I began by setting up a series of tests to ensure that I could successfully illuminate the individual buttons. From there, I developed a basic JavaScript application to explore concepts such as colour mapping and data mapping from the launchpad to a grid.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var MidiStream = require("midi-stream");
//https://www.youtube.com/watch?v=JatNuVsbsEQ

var duplex = MidiStream("Launchpad Mini");

duplex.write([176, 0, 0]); //clear all
//duplex.pipe(duplex);
duplex.on("data", function (data) {
  console.log(data);
  //console.log(mapToGrid(data[1]));

  //buttons across top 104 - 111
  //botton down right 8,24,40,56,72,88,104,120

  play(mapToGrid(data[1]).row, mapToGrid(data[1]).col, randomVelocity());
});

const play = (row, col, velocity, delay) =&amp;gt; {
  var note = mapToMidi(row, col);

  setTimeout(() =&amp;gt; {
    duplex.write([144, note, velocity]);
  }, delay || 0);
};

const mapToGrid = (data) =&amp;gt; {
  const row = Math.floor(data / 16);
  const col = data % 8;
  return { row, col };
};
const mapToMidi = (row, col) =&amp;gt; {
  return row * 16 + col;
};
const randomVelocity = () =&amp;gt; {
  return Math.floor(Math.random() * 100);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When configuring a button on the MIDI interface, an array of values is sent to specify the desired behavior. For instance, &lt;code&gt;stream.write([176,0,0])&lt;/code&gt; may be used to turn a button off, while &lt;code&gt;stream.write([144,0,3])&lt;/code&gt; would turn it on. The first value in the array indicates the desired state, with 176 indicating "switch off" and 144 indicating "switch on." The second value corresponds to the specific button being configured, and while it took a bit of experimentation to fully grasp the mapping between numbers and buttons, I eventually got the hang of it. Finally, the third value specifies the colour of the button, and again, some testing was necessary to understand the relationship between values and colours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ymhtyvdevb33siq3b0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ymhtyvdevb33siq3b0t.png" alt="Grid Layout" width="669" height="932"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The codebase was loosely designed with the hexagonal architecture in mind, featuring three primary components:&lt;/p&gt;

&lt;p&gt;Application - This component is responsible for starting the clock application.&lt;br&gt;
Domain - Here, the domain objects manage the bulk of the application's functionality.&lt;br&gt;
Infrastructure - Finally, the infrastructure component handles external entities, such as the low-level code for the Launchpad Mini.&lt;br&gt;
Of all the unit tests, the GridManager tests were the most vital. These tests validated that the grid correctly maps to the MIDI call, ensuring proper operation of the clock.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feydknty0q4nmc4orkvov.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feydknty0q4nmc4orkvov.PNG" alt=" " width="769" height="796"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In summary, this project was an enjoyable endeavour that allowed me to breathe new life into an old device. I've even gone so far as to install the application on a Raspberry Pi, where it runs 24/7 and serves as a stylish and functional decoration for my desk.&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
  </channel>
</rss>
