<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Harsh Deep - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Harsh Deep - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 07 Jun 2026 16:46:49 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/harsh410/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Use RxStomp with React: Build A Chat App ]]>
                </title>
                <description>
                    <![CDATA[ STOMP is an amazingly simple yet powerful protocol for sending messages implemented by popular servers like RabbitMQ, ActiveMQ, and Apollo. Using STOMP over WebSocket is a straightforward protocol, making it a popular choice for sending messages from... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-chat-app-with-stomp-and-react/</link>
                <guid isPermaLink="false">671901e20aa5e141c2db418f</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ websocket ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Chat ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Harsh Deep ]]>
                </dc:creator>
                <pubDate>Wed, 23 Oct 2024 14:02:10 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729648105968/2926c346-0058-4a84-981e-2ff4bd6833df.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>STOMP is an amazingly simple yet powerful protocol for sending messages implemented by popular servers like RabbitMQ, ActiveMQ, and Apollo. Using STOMP over WebSocket is a straightforward protocol, making it a popular choice for sending messages from a web browser because protocols like AMQP are limited by major browsers blocking TCP connections.</p>
<p>To use STOMP over WebSocket, you can use <a target="_blank" href="https://www.npmjs.com/package/@stomp/stompjs">@stomp/stompjs</a>, but that has tricky callbacks and a complicated API that caters to more specialized use cases. Luckily, there’s also the lesser-known <a target="_blank" href="https://www.npmjs.com/package/@stomp/rx-stomp">@stompjs/rx-stomp</a> which provides a nice interface via <a target="_blank" href="https://www.npmjs.com/package/rxjs">RxJS</a> observables. Observables aren't exclusive to Angular, and they fit quite well with how React works. It's a neat interface when composing complex workflows and pipelines with many different message sources.</p>
<p>The tutorial follows a somewhat similar path as the initial version in <a target="_blank" href="https://stomp-js.github.io/guide/rx-stomp/rx-stomp-with-angular.html">Angular</a>, but the component structure and code style are tuned towards the functional style of React.</p>
<p><strong>Note:</strong> This tutorial is written with <code>strict</code> TypeScript, but the JavaScript code is almost identical since we only have 5 type declarations. For the JS version, you can skip the type imports and definitions.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-goals">Goals</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-starter-stomp-server-with-rabbitmq">Starter STOMP Server with RabbitMQ</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-starter-react-template">Starter React Template</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-rxstomp">How to Install RxStomp</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-connection-and-disconnection-with-the-stomp-server">How to Manage Connection and Disconnection with the STOMP Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-monitor-the-connection-status">How to Monitor the Connection Status</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-send-messages">How to Send Messages</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-receive-messages">How to Receive Messages</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-goals">Goals</h2>
<p>Here, we’ll build a simplified chatroom application that shows various aspects of RxStomp across different components. Overall, we want to have:</p>
<ul>
<li><p>A React frontend connected with RxStomp to a STOMP server.</p>
</li>
<li><p>A live connection status display based on our connection to the STOMP server.</p>
</li>
<li><p>Pub/Sub logic for any configurable topic.</p>
</li>
<li><p>Splitting RxStomp logic across multiple components to show how to separate logic and responsibility.</p>
</li>
<li><p>Aligning RxStomp connection/subscription lifecycles with React component lifecycles to ensure that there are no leaks or unclosed watchers.</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>You should have a STOMP server running so that the React application can connect to it. Here, we’ll use RabbitMQ with the <code>rabbitmq_web_stomp</code> extension.</p>
</li>
<li><p>Latest React version. This tutorial will use v18, although older versions will probably work as well.</p>
</li>
<li><p>Some familiarity with observables will also help.</p>
</li>
</ul>
<h2 id="heading-starter-stomp-server-with-rabbitmq">Starter STOMP Server with RabbitMQ</h2>
<p>If you’d like to use RabbitMQ too (not strictly required), here’s are <a target="_blank" href="https://www.rabbitmq.com/docs/download">installation guides for different operating systems</a>. To add the extension, you’ll need to run:</p>
<pre><code class="lang-bash">$ rabbitmq-plugins <span class="hljs-built_in">enable</span> rabbitmq_web_stomp
</code></pre>
<p>If you’re able to use <code>Docker</code>, a Docker file similar to <a target="_blank" href="https://github.com/harsh183/rabbitmq-intro/blob/master/code_examples/Dockerfile">this</a> will set everything needed for the tutorial:</p>
<pre><code class="lang-bash">FROM rabbitmq:3.8.8-alpine

run rabbitmq-plugins <span class="hljs-built_in">enable</span> --offline rabbitmq_web_stomp

EXPOSE 15674
</code></pre>
<h2 id="heading-starter-react-template">Starter React Template</h2>
<p>For this tutorial, we'll use <a target="_blank" href="https://vite.dev/guide/">Vite</a>'s <code>react-ts</code> template. The central part of our application will be in the <code>App</code> component, and we'll create child components for other specific STOMP functionality.</p>
<h2 id="heading-how-to-install-rxstomp">How to Install RxStomp</h2>
<p>We’ll use the <code>@stomp/rx-stomp</code> npm package:</p>
<pre><code class="lang-bash">$ npm i @stomp/rx-stomp rxjs
</code></pre>
<p>This will install version <code>2.0.0</code></p>
<p><strong>Note</strong>: This tutorial still works without explicitly specifying <code>rxjs</code> since it's a sister dependency, but it's good practice to be explicit about it.</p>
<h2 id="heading-how-to-manage-connection-and-disconnection-with-the-stomp-server">How to Manage Connection and Disconnection with the STOMP Server</h2>
<p>Now, let's open <strong>App.tsx</strong> and initialize our <code>RxStomp</code> client. Since the client isn't a state that will change for rendering, we’ll wrap it in the <code>useRef</code> Hook.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>
<span class="hljs-keyword">import</span> { useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> rxStompRef = useRef(<span class="hljs-keyword">new</span> RxStomp())
  <span class="hljs-keyword">const</span> rxStomp = rxStompRef.current

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h1&gt;Hello RxStomp!&lt;/h1&gt;
    &lt;/&gt;
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App
</code></pre>
<p>Assuming the default ports and authentication details, we’ll define some configuration for our connection next.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>

<span class="hljs-keyword">import</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStompConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>
...
<span class="hljs-keyword">const</span> rxStompConfig: RxStompConfig = {
  brokerURL: <span class="hljs-string">'ws://localhost:15674/ws'</span>,
  connectHeaders: {
    login: <span class="hljs-string">'guest'</span>,
    passcode: <span class="hljs-string">'guest'</span>,
  },
  debug: <span class="hljs-function">(<span class="hljs-params">msg</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(), msg)
  },
  heartbeatIncoming: <span class="hljs-number">0</span>,
  heartbeatOutgoing: <span class="hljs-number">20000</span>,
  reconnectDelay: <span class="hljs-number">200</span>,
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  ...
</code></pre>
<p>For a better dev experience, we logged all messages with timestamps to a local console and set low timer frequencies. Your configuration should be quite different for your production application, so check out the <a target="_blank" href="https://stomp-js.github.io/api-docs/latest/classes/RxStompConfig.html">RxStompConfig docs</a> for all the options available.</p>
<p>Next, we’ll pass the configuration to <code>rxStomp</code> inside a <code>useEffect</code> Hook. This manages the connection's activation alongside the component lifecycle.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>
...
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> rxStompRef = useRef(<span class="hljs-keyword">new</span> RxStomp())
  <span class="hljs-keyword">const</span> rxStomp = rxStompRef.current

  useEffect(<span class="hljs-function">() =&gt;</span> {
    rxStomp.configure(rxStompConfig)
    rxStomp.activate()

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> { 
      rxStomp.deactivate() 
    }
  })
  ...
</code></pre>
<p>While there's no visual change in our app, checking the logs should show connection and ping logs. Here's an example of what that should look like:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">Date</span> ... &gt;&gt;&gt; CONNECT
login:guest
passcode:guest
accept-version:<span class="hljs-number">1.2</span>,<span class="hljs-number">1.1</span>,<span class="hljs-number">1.0</span>
heart-beat:<span class="hljs-number">20000</span>,<span class="hljs-number">0</span>

<span class="hljs-built_in">Date</span> ... Received data 
<span class="hljs-built_in">Date</span> ... &lt;&lt;&lt; CONNECTED
version:<span class="hljs-number">1.2</span>
heart-beat:<span class="hljs-number">0</span>,<span class="hljs-number">20000</span>
session:session-EJqaGQijDXqlfc0eZomOqQ
server:RabbitMQ/<span class="hljs-number">4.0</span><span class="hljs-number">.2</span>
content-length:<span class="hljs-number">0</span>

<span class="hljs-built_in">Date</span> ... connected to server RabbitMQ/<span class="hljs-number">4.0</span><span class="hljs-number">.2</span> 
<span class="hljs-built_in">Date</span> ... send PING every <span class="hljs-number">20000</span>ms 
<span class="hljs-built_in">Date</span> ... &lt;&lt;&lt; PONG 
<span class="hljs-built_in">Date</span> ... &gt;&gt;&gt; PING
</code></pre>
<p><strong>Note:</strong> Generally, if you see duplicate logs, it may be a sign that a deactivation or unsubscribe functionality wasn't implemented correctly. React renders each component twice in a dev environment to help people catch these bugs via <code>React.StrictMode</code></p>
<h2 id="heading-how-to-monitor-the-connection-status">How to Monitor the Connection Status</h2>
<p>RxStomp has a <a target="_blank" href="https://stomp-js.github.io/api-docs/latest/miscellaneous/enumerations.html#RxStompState">RxStompState enum</a> that represents possible connection states with our broker. Our next goal is to display the connection status in our UI.</p>
<p>Let's create a new component for this called <code>Status.tsx</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Status.tsx</span>
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Status</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [connectionStatus, setConnectionStatus] = useState(<span class="hljs-string">''</span>)

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Connection Status: {connectionStatus}&lt;/h2&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>We can use the <code>rxStomp.connectionState$</code> observable to bind to our <code>connectionStatus</code> string. Similar to how we used <code>useEffect</code>, we’ll use the unmount action to <code>unsubscribe()</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Status.tsx</span>
<span class="hljs-keyword">import</span> { RxStompState } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Status</span>(<span class="hljs-params">props: { rxStomp: RxStomp }</span>) </span>{
  <span class="hljs-keyword">const</span> [connectionStatus, setConnectionStatus] = useState(<span class="hljs-string">''</span>)

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> statusSubscription = props.rxStomp.connectionState$.subscribe(<span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> {
      setConnectionStatus(RxStompState[state])
    })

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      statusSubscription.unsubscribe()
    }
  }, [])

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Connection Status: {connectionStatus}&lt;/h2&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>To view it, we include it in our app:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>
<span class="hljs-keyword">import</span> Status <span class="hljs-keyword">from</span> <span class="hljs-string">'./Status'</span>
...
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h1&gt;Hello RxStomp!&lt;/h1&gt;

      &lt;Status rxStomp={rxStomp}/&gt;
    &lt;/&gt;
  )
</code></pre>
<p>At this point, you should have a working visual indicator on the screen. Try playing around by taking the STOMP server down and see if the logs work as expected.</p>
<h2 id="heading-how-to-send-messages">How to Send Messages</h2>
<p>Let's create a simple chatroom to show a simplified end-to-end messaging flow with the broker.</p>
<p>We can place the functionality in a new <code>Chatroom</code> component. First, we can create the component with a custom <code>username</code> and <code>message</code> field that's bound to inputs.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Chatroom.tsx</span>
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Chatroom</span>(<span class="hljs-params">props: {rxStomp: RxStomp}</span>) </span>{
  <span class="hljs-keyword">const</span> [message, setMessage] = useState(<span class="hljs-string">''</span>)
  <span class="hljs-keyword">const</span> [userName, setUserName] = useState(<span class="hljs-string">`user<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">1000</span>)}</span>`</span>)

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Chatroom&lt;/h2&gt;

      &lt;label htmlFor=<span class="hljs-string">'username'</span>&gt;Username: &lt;/label&gt;
      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
        name=<span class="hljs-string">'username'</span>
        value={userName}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setUserName(e.target.value)}
      /&gt;

      &lt;label htmlFor=<span class="hljs-string">'message'</span>&gt;Message: &lt;/label&gt;

      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
        value={message}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setMessage(e.target.value)}
        name=<span class="hljs-string">'message'</span>
      /&gt;
    &lt;/&gt;
  )    
}
</code></pre>
<p>Let’s include this within our <strong>App</strong> with a toggle to join the chatroom:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/App.tsx</span>
<span class="hljs-keyword">import</span> { useEffect, useState, useRef } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> Chatroom <span class="hljs-keyword">from</span> <span class="hljs-string">'./Chatroom'</span>
...
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [joinedChatroom, setJoinedChatroom] = useState(<span class="hljs-literal">false</span>)
  ...
  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h1&gt;Hello RxStomp!&lt;/h1&gt;

      &lt;Status rxStomp={rxStomp}/&gt;

      {!joinedChatroom &amp;&amp; (
        &lt;button onClick={<span class="hljs-function">() =&gt;</span> setJoinedChatroom(<span class="hljs-literal">true</span>)}&gt;
          Join chatroom!
        &lt;/button&gt;
      )}

      {joinedChatroom &amp;&amp; (
        &lt;&gt;
          &lt;button onClick={<span class="hljs-function">() =&gt;</span> setJoinedChatroom(<span class="hljs-literal">false</span>)}&gt;
            Leave chatroom!
          &lt;/button&gt;

          &lt;Chatroom rxStomp={rxStomp}/&gt;
        &lt;/&gt;
      )}

    &lt;/&gt;
  )
</code></pre>
<p>Time to actually send messages. STOMP is best for sending text-based messages (binary data is also possible). We’ll define the structure of the data we're sending in a new <strong>types</strong> file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// types.ts</span>
<span class="hljs-keyword">interface</span> ChatMessage {
  userName: <span class="hljs-built_in">string</span>,
  message: <span class="hljs-built_in">string</span>
}
</code></pre>
<p><strong>Note:</strong> If you're not using TypeScript, you can skip adding this type definition.</p>
<p>Next, let's use JSON to serialize the message and send messages to our STOMP server using <code>.publish</code> with a destination topic and our JSON <code>body</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Chatroom.tsx</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ChatMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types'</span>
...
<span class="hljs-keyword">const</span> CHATROOM_NAME = <span class="hljs-string">'/topic/test'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Chatroom</span>(<span class="hljs-params">props: {rxStomp: RxStomp}</span>) </span>{
  ...
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendMessage</span>(<span class="hljs-params">chatMessage: ChatMessage</span>) </span>{
    <span class="hljs-keyword">const</span> body = <span class="hljs-built_in">JSON</span>.stringify({ ...chatMessage })
    props.rxStomp.publish({ destination: CHATROOM_NAME, body })
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Sent <span class="hljs-subst">${body}</span>`</span>)
    setMessage(<span class="hljs-string">''</span>)
  }

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Chatroom&lt;/h2&gt;

      &lt;label htmlFor=<span class="hljs-string">"username"</span>&gt;Username: &lt;/label&gt;
      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
        name=<span class="hljs-string">"username"</span>
        value={userName}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setUserName(e.target.value)}
      /&gt;

      &lt;label htmlFor=<span class="hljs-string">"message"</span>&gt;Message: &lt;/label&gt;

      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
        value={message}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setMessage(e.target.value)}
        name=<span class="hljs-string">"message"</span>
      /&gt;

      &lt;button onClick={<span class="hljs-function">() =&gt;</span> sendMessage({userName, message})}&gt;Send Message&lt;/button&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>To test it out, try clicking the <strong>Send Message</strong> button a few times and see if the serialization works fine. While you won't be able to see any visual changes yet, the console logs should show it:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">Date</span> ... &gt;&gt;&gt; SEND
destination:<span class="hljs-regexp">/topic/</span>test
content-length:<span class="hljs-number">45</span>

Sent {<span class="hljs-string">"userName"</span>:<span class="hljs-string">"user722"</span>,<span class="hljs-string">"message"</span>:<span class="hljs-string">"1234567890"</span>}
</code></pre>
<h2 id="heading-how-to-receive-messages">How to Receive Messages</h2>
<p>We’ll create a new component to show the list of messages from all the users. For now, we'll use the same type, pass the topic name as a prop, and display everything as a list. All this goes into a new component called <code>MessageList</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/MessageDisplay.tsx</span>
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ChatMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MessageDisplay</span>(<span class="hljs-params">props: {rxStomp: RxStomp, topic: <span class="hljs-built_in">string</span>}</span>) </span>{
  <span class="hljs-keyword">const</span> [chatMessages, setChatMessages] = useState&lt;ChatMessage[]&gt;([
    {userName: <span class="hljs-string">'admin'</span>, message: <span class="hljs-string">`Welcome to <span class="hljs-subst">${props.topic}</span> room!`</span>}
  ])

  <span class="hljs-keyword">return</span>(
  &lt;&gt;
  &lt;h2&gt;Chat Messages&lt;/h2&gt;
  &lt;ul&gt;
    {chatMessages.map(<span class="hljs-function">(<span class="hljs-params">chatMessage, index</span>) =&gt;</span> 
      &lt;li key={index}&gt;
        &lt;strong&gt;{chatMessage.userName}&lt;/strong&gt;: {chatMessage.message}
      &lt;/li&gt;
    )}
  &lt;/ul&gt;
  &lt;/&gt;
  )
}
</code></pre>
<p>Time to bring everything together!</p>
<p>We can display our messages to display within our <code>Chatroom</code> component by adding it to the bottom.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/Chatroom.tsx</span>
<span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ChatMessage } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { RxStomp } <span class="hljs-keyword">from</span> <span class="hljs-string">'@stomp/rx-stomp'</span>

<span class="hljs-keyword">import</span> MessageDisplay <span class="hljs-keyword">from</span> <span class="hljs-string">'./MessageDisplay'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CHATROOM_NAME = <span class="hljs-string">'/topic/test'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Chatroom</span>(<span class="hljs-params">props: {rxStomp: RxStomp}</span>) </span>{
  <span class="hljs-keyword">const</span> [message, setMessage] = useState(<span class="hljs-string">''</span>)
  <span class="hljs-keyword">const</span> [userName, setUserName] = useState(<span class="hljs-string">`user<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">1000</span>)}</span>`</span>)

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendMessage</span>(<span class="hljs-params">chatMessage: ChatMessage</span>) </span>{
    <span class="hljs-keyword">const</span> body = <span class="hljs-built_in">JSON</span>.stringify({ ...chatMessage })
    props.rxStomp.publish({ destination: CHATROOM_NAME, body })
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Sent <span class="hljs-subst">${body}</span>`</span>)
    setMessage(<span class="hljs-string">''</span>)
  }

  <span class="hljs-keyword">return</span> (
    &lt;&gt;
      &lt;h2&gt;Chatroom&lt;/h2&gt;

      &lt;label htmlFor=<span class="hljs-string">'username'</span>&gt;Username: &lt;/label&gt;
      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
        name=<span class="hljs-string">'username'</span>
        value={userName}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setUserName(e.target.value)}
      /&gt;

      &lt;label htmlFor=<span class="hljs-string">'message'</span>&gt;Message: &lt;/label&gt;

      &lt;input
        <span class="hljs-keyword">type</span>=<span class="hljs-string">'text'</span>
        value={message}
        onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setMessage(e.target.value)}
        name=<span class="hljs-string">'message'</span>
      /&gt;

      &lt;button onClick={<span class="hljs-function">() =&gt;</span> sendMessage({userName, message})}&gt;Send Message&lt;/button&gt;

      &lt;MessageDisplay rxStomp={props.rxStomp} topic={CHATROOM_NAME} /&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>And once you've verified the static display working locally, we can make this display dynamic using an RxJS Observable to receive our chat messages. </p>
<p>Similar to managing the subscription with the <code>Status</code> component, we set up the subscription on mount, and unsubscribe on unmount.</p>
<p>Using RxJS <code>pipe</code> and <code>map</code>, we can deserialize our JSON back to our <code>ChatMessage</code>. The modular design can let you set up a more complicated pipeline as needed using <code>RxJS</code> operators.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/MessageDisplay.tsx</span>
...
<span class="hljs-keyword">import</span> { map } <span class="hljs-keyword">from</span> <span class="hljs-string">'rxjs'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MessageDisplay</span>(<span class="hljs-params">props: {rxStomp: RxStomp, topic: <span class="hljs-built_in">string</span>}</span>) </span>{
  <span class="hljs-keyword">const</span> [chatMessages, setChatMessages] = useState&lt;ChatMessage[]&gt;([
    {userName: <span class="hljs-string">'admin'</span>, message: <span class="hljs-string">`Welcome to <span class="hljs-subst">${props.topic}</span> room!`</span>}
  ])

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> subscription = props.rxStomp
      .watch(props.topic)
      .pipe(map(<span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> <span class="hljs-built_in">JSON</span>.parse(message.body)))
      .subscribe(<span class="hljs-function">(<span class="hljs-params">message</span>) =&gt;</span> setChatMessages(<span class="hljs-function">(<span class="hljs-params">chatMessages</span>) =&gt;</span> [...chatMessages, message]))

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      subscription.unsubscribe()
    }
  }, [])

  ...
</code></pre>
<p>At this point, the chat GUI should show messages correctly, and you can experiment with opening multiple tabs as different users.</p>
<p>Another thing to try here is turning off the STOMP server, sending a few messages, and turning it back on. The messages should get queued locally and dispatched once the server is ready to go. Neat!</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, we:</p>
<ul>
<li><p>Installed <code>@stomp/rx-stomp</code> for a nice dev experience.</p>
</li>
<li><p>Set up <code>RxStompConfig</code> to configure our client with the connection details, debugger logging and timer settings.</p>
</li>
<li><p>Used <code>rxStomp.activate</code> and <code>rxStomp.deactivate</code> to manage the client’s main lifecycle.</p>
</li>
<li><p>Monitored the subscription state using <code>rxStomp.connectionState$</code> observable.</p>
</li>
<li><p>Published messages using <code>rxStomp.publish</code> with configurable destinations and message bodies.</p>
</li>
<li><p>Created an observable for a given topic using <code>rxStomp.watch</code>.</p>
</li>
<li><p>Used both console logs and React components to see the library in action, and verify functionality and fault tolerance.</p>
</li>
</ul>
<p>You can find the final code on Gitlab: <a target="_blank" href="https://gitlab.com/harsh183/rxstomp-react-tutorial">https://gitlab.com/harsh183/rxstomp-react-tutorial</a>. Feel free to use it as a starter template too and report any issues that may come up.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Upgrade from Node 16 and Jest 26 While Staying on React Scripts 4 ]]>
                </title>
                <description>
                    <![CDATA[ Recently, I was trying to upgrade some of my open source projects. They were made using create-react-app around 2019, and I wanted to upgrade to a newer version of NodeJS and Jest. This would let me take advantage of the security updates, bug fixes, ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-upgrade-node-and-jest-while-on-react-scripts-v4/</link>
                <guid isPermaLink="false">66b9e801748589d7de3c86ab</guid>
                
                    <category>
                        <![CDATA[ Jest ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Harsh Deep ]]>
                </dc:creator>
                <pubDate>Wed, 10 Jul 2024 19:35:36 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/image0.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Recently, I was trying to upgrade some of my open source <a target="_blank" href="https://github.com/classtranscribe/FrontEnd/">projects</a>. They were made using <a target="_blank" href="https://github.com/facebook/create-react-app">create-react-app</a> around 2019, and I wanted to upgrade to a newer version of NodeJS and Jest. This would let me take advantage of the security updates, bug fixes, speed improvements, and new features that the ecosystem has developed since then. </p>
<p>Unfortunately, it was not as simple as just running <code>$ nvm use 18</code> and sailing into the sunset. Luckily, if you follow all the proper steps, you'll get past many significant hurdles and upgrade successfully. In this guide, I will share all the knowledge I wish I had known going into the process. The goal is to get your React application using Node 18+ and Jest 29+ while not making the treacherous upgrade to React Scripts 5.</p>
<p>If you can upgrade to React Scripts 5 (which is impractical for most real-world applications), I highly recommend that path instead. This is because the newest version of CRA fixes many issues with older dependencies, like the <code>MD4 envelope</code> or Babel <code>process()</code> return shapes, that we'll manually tackle in this tutorial. If you can upgrade to v5, then Node versions 18+ should work out of the box.</p>
<p>Unfortunately, going up to React Scripts 5 introduces many breaking changes, mostly due to the upgrade to Webpack 5. While many small/tutorial-level applications can upgrade fairly easily, any real-world application faces a steep uphill journey to upgrade. </p>
<p>If the React Scripts 5 upgrade approach doesn't work for you, you can follow what I've written below on making the Node upgrade work while still staying on React Scripts 4. At the end of this page, I've written a small note about my journey trying the <code>v5</code> upgrade.</p>
<p>Everyone's upgrade journey will vary, especially considering the Jenga of <code>npm</code> dependencies and the relative lack of maintenance of Create React App's React Scripts in recent years.</p>
<p>These are the steps of the upgrade that I've tried with a few different React applications, but you may encounter issues I didn't encounter myself. Google is your best friend in these cases, and it will often lead you to Stackoverflow, GitHub issues, other tutorials, and maybe even source code. Don't be afraid; you'll be able to figure it out! </p>
<p>Note: In this tutorial, I'll refer to Create React App as CRA. React Scripts is the name of the installed package that abstracts all the configuration created by the Create React App command, and in most cases you'll see online resources use both interchangeably.</p>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#heading-how-to-validate-every-step">How to Validate Every Step</a></li>
<li><a class="post-section-overview" href="#heading-how-to-bump-to-react-scripts-v403">How to Bump to React Scripts v4.0.3</a></li>
<li><a class="post-section-overview" href="#heading-how-to-bump-node-version-to-18">How to Bump Node Version to 18</a><br>– <a class="post-section-overview" href="#heading-understanding-the-md4-issue">Understanding the MD4 Issue</a></li>
<li><a class="post-section-overview" href="#heading-how-to-eject-out-of-react-scripts">How to Eject Out of React Scripts</a><br>– <a class="post-section-overview" href="#heading-how-to-add-linter-ignores-for-ejected-files">How to Add Linter Ignores For Ejected Files</a><br>– <a class="post-section-overview" href="#heading-how-to-update-your-dockerfile-and-other-build-processes-with-the-ejected-folders-1">How to update your Dockerfile and Other Build Processes with the ejected folders</a><br>– <a class="post-section-overview" href="#heading-how-to-fix-absolute-paths-for-jest">How to Fix Absolute Paths for Jest</a><br>– <a class="post-section-overview" href="#heading-how-to-update-your-dockerfile-and-other-build-processes-with-the-ejected-folders-1">How to update your Dockerfile and Other Build Processes with the ejected folders</a></li>
<li><a class="post-section-overview" href="#heading-how-to-override-webpack-md4-to-sha256">How to Override Webpack MD4 to SHA256</a></li>
<li><a class="post-section-overview" href="#heading-how-to-upgrade-to-the-latest-version-of-jest">How to Upgrade to the Latest Version of Jest</a><br>– <a class="post-section-overview" href="#heading-how-to-bump-to-jest-28">How to Bump to Jest 28</a><br>– <a class="post-section-overview" href="#heading-how-to-explicitly-set-jsdom-as-the-test-environment">How to explicitly set jsdom as the test environment</a><br>– <a class="post-section-overview" href="#heading-how-to-fix-transformer-return-type-for-process-and-processasync">How to Fix Transformer Return Type for process() and processAsync()</a><br>– <a class="post-section-overview" href="#heading-how-to-bump-jest-to-29">How to Bump Jest to 29</a></li>
<li><a class="post-section-overview" href="#heading-how-far-should-i-upgrade-nodejs">How Far Should I Upgrade NodeJS?</a></li>
<li><a class="post-section-overview" href="#heading-should-you-still-use-create-react-scripts-what-alternatives-are-there">Should You Still Use Create React Scripts? What Alternatives Are There?</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
<li><a class="post-section-overview" href="#heading-alternatively-how-to-upgrade-to-react-scripts-501">Alternatively: How to Upgrade to React Scripts 5.0.1</a></li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along with this guide, you should have a React application that is:</p>
<ul>
<li>created with <code>create-react-app</code> v4 or upgraded to use <code>react-scripts</code> v4. I've tested this tutorial on both scenarios.</li>
<li>running on Node 16</li>
</ul>
<p>If you're running NodeJS behind 16, I highly suggest upgrading to version 16. The upgrade path to 16 isn't too bad, but the jump from 16 to 18 creates breaking issues with CRA 4 defaults. </p>
<p>Our application also ran <code>jest</code> (v26), the most common test framework in React and shipped by default in CRA v4. If you aren't running Jest, then you can skip the steps relevant to it.</p>
<p>We were also using <code>yarn</code>, but the process should be identical with different syntax if you use any other runner/package manager like <code>npm</code>.</p>
<p>Ideally, you have some test case coverage to ensure things don't break between versions, so it's well worth taking some time to write some broad integration and unit tests before any upgrade.</p>
<p>I recommend using version control like <code>git</code> for each stage while working on a branch. I started over three different times using different upgrade strategies until I had something that worked. Here's a quick <a target="_blank" href="https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell">intro</a> to <code>git</code> branches if you're unfamiliar with them.</p>
<p>I also recommend using <a target="_blank" href="https://github.com/nvm-sh/nvm">nvm</a> (Node Version Manager) to to swap versions quickly. You don't have to use it, and there are many other alternatives out there to manage versions, but it makes quickly switching very easy with just <code>nvm use</code>. I'll use <code>nvm</code> syntax in this tutorial, but it should be pretty similar for your tool.</p>
<h2 id="heading-how-to-validate-every-step">How to Validate Every Step</h2>
<p>Throughout the tutorial, to ensure things still work, you'll run the following:</p>
<ul>
<li><code>$ yarn build</code> – End-to-end build to catch a lot of library-level issues</li>
<li><code>$ yarn test</code> – Regression tests to catch breaks in functionality</li>
<li><code>$ yarn start</code> – The starter scripts to catch many initialization bugs.</li>
</ul>
<p>If you have any more validation steps (CI builds, Docker, staging environments, smoke tests), make sure they're working already and use them throughout the process to validate the upgrade worked correctly. For the rest of the tutorial, I'll refer to these as validation commands.</p>
<p>Before you start the upgrade, make sure all validation steps are working on your current Node 16 and CRA 4. At the end of the tutorial, all these validation steps should be working too. Ultimately, make sure to actually use your React application extensively as the final test once all the upgrade process is done.</p>
<p>Occasionally, you may need to <code>$ rm -rf node_modules</code> and <code>$ rm package.lock.json</code> / <code>$ rm yarn.lock</code> because some library changes may not propagate correctly. Ideally you won't need to do this, but it's reasonably safe since it just downloads all packages again.</p>
<h2 id="heading-how-to-bump-to-react-scripts-v403">How to Bump to React Scripts v4.0.3</h2>
<p>Depending on when you started your project from CRA, you'll likely be at different versions along v4. First, we upgrade to the latest minor version to smooth over the rest of the upgrade process.</p>
<p>There shouldn't be any major breaking changes between the minor versions, but make sure to upgrade it incrementally in your <code>package.json</code> going from <code>4.0.0</code> -&gt; <code>4.0.1</code> -&gt; <code>4.0.2</code> -&gt; <code>4.0.3</code>. Going to <code>4.0.3</code> will streamline your upgrade process since these minor updates have a lot of useful bug, library, and dependency fixes while not creating new work for now. </p>
<p>I ran <code>$ yarn install</code> after each step and then checked my validation commands to ensure everything was still working.</p>
<pre><code class="lang-json">... 
<span class="hljs-string">"dependencies"</span>: { 
    <span class="hljs-attr">"react-scripts"</span>: <span class="hljs-string">"4.0.1"</span>, 
    ... 
}, 
...
</code></pre>
<p>In my projects, I didn't encounter any issues, but your mileage may vary. The official <a target="_blank" href="https://github.com/facebook/create-react-app/blob/main/CHANGELOG-4.x.md">CRA v4 changelog documentation</a> has a list of small changes and upgrade steps between the versions, which will narrow down the causes.</p>
<h2 id="heading-how-to-bump-node-version-to-18">How to Bump Node Version to 18</h2>
<p>After making sure your validation commands are working on your current Node 16, set your version to 18. Then we work on fixing all the validation commands until all of them work. Occasionally you may switch back to 16 to make sure things still work in the older version. </p>
<p>In your command line, run the following:</p>
<pre><code class="lang-sh">$ nvm install 18
$ nvm use 18
</code></pre>
<blockquote>
<p>Note: If you have a <code>.nvmrc</code> file, you can skip the version numbers in the <code>nvm install</code> and <code>nvm use</code> commands. Update the file as you change node versions.</p>
</blockquote>
<p>Unfortunately, if you try <code>$ yarn start</code> or <code>$ yarn build</code>, you'll immediately run into the cryptography error that comes from <code>openssl</code>, which blocks all encryption using MD4. This is the main error blocking the upgrade to Node 18 while on CRA 4.</p>
<pre><code><span class="hljs-built_in">Error</span>: error:<span class="hljs-number">0308010</span>C:digital envelope routines::unsupported
</code></pre><h3 id="heading-understanding-the-md4-issue">Understanding the MD4 Issue</h3>
<p>MD4 is an old encryption algorithm from the 1990s and has been considered very insecure since 1995 (<a target="_blank" href="https://en.wikipedia.org/wiki/MD4">Wikipedia</a>). OpenSSL from version 3 onward changed MD4 to not be supported by default, but it can be enabled with an allow unsafe legacy <a target="_blank" href="https://github.com/openssl/openssl/issues/21247">flag</a> on your system <code>openssl</code> or <code>--openssl-legacy-provider</code> if adding it to your node/CRA script (see the Node <a target="_blank" href="https://nodejs.org/api/cli.html#--openssl-legacy-provider">docs</a>). </p>
<p>It's a seemingly simple fix to the solution, but this is more of a last resort since allowing unsafe cryptography is generally a bad idea, and OpenSSL has disabled the algorithm entirely for a reason.</p>
<blockquote>
<p>Note: If you're curious, Webpack has a 1000+ response <a target="_blank" href="https://github.com/webpack/webpack/issues/14532">discussion</a> on this topic that might have something useful. Later versions of Webpack also eventually <a target="_blank" href="https://github.com/webpack/webpack/pull/14306">allowed</a> a better algorithm called <a target="_blank" href="https://github.com/Cyan4973/xxHash">xxHash</a>, added a built-in MD4 <code>wasm</code> <a target="_blank" href="https://github.com/webpack/webpack/pull/14584">implementation</a>, and added a new config option called <a target="_blank" href="https://webpack.js.org/configuration/optimization/#optimizationmoduleids">deterministic</a> that sidesteps the issue. </p>
</blockquote>
<p>I highly recommend reading this StackOverflow <a target="_blank" href="https://stackoverflow.com/questions/69692842/error-message-error0308010cdigital-envelope-routinesunsupported">answer</a> for a quick overview of the major options if we aren't patching it ourselves. Since upgrading dependencies isn't possible here, and we don't want to stay on an old Node version or allow insecure algorithms, we need to dive into the internals of CRA to fix it.  </p>
<h2 id="heading-how-to-eject-out-of-react-scripts">How to Eject Out of React Scripts</h2>
<p>CRA is designed for a zero-configuration experience for React Apps that lets you focus on just working on your business logic. </p>
<p>When you want to start changing configuration, CRA doesn't have a built-in method to override any option. Instead, it offers a command called <code>eject</code> that copies over all the internals of CRA to your project while leaving your yarn/npm commands intact and then removing React scripts from your project entirely. It's a one-way action, so make sure you save the previous version in <code>git</code>. </p>
<pre><code class="lang-shell">$ yarn eject
</code></pre>
<p>This is a huge command that will change lots of files in the <code>config/</code> and <code>scripts/</code> directories as well as your list of packages in <code>package.json</code>. Once you rerun <code>yarn install</code>, make sure to run all your validation commands to make sure everything still works on Node 16 since nothing should have changed in terms of functionality.</p>
<p>Alternatively, if you don't want to try <code>eject</code>, there are also workarounds like:</p>
<ul>
<li>CRACO uses a clever override mechanism to allow you to still use React Scripts while customizing. Read <a target="_blank" href="https://craco.js.org/docs/getting-started/">Getting Started</a> and <a target="_blank" href="https://medium.com/workleap/why-i-built-craco-33ff39f4fc94">Why I built CRACO</a>. Start off with version <code>6.4.5</code> for CRA v4.</li>
<li><a target="_blank" href="https://github.com/ds300/patch-package">patch-package</a> applies specific npm package changes for your project and then you share the patch with your team/project. For this guide, you will patch <code>react-scripts</code> with the modified webpack and config setups.</li>
<li>Forking CRA with your own modifications. This way you can still keep the zero config CRA with no hacks to patch in new functionality, but this might get complicated. Here's a guide I saw online: <a target="_blank" href="https://auth0.com/blog/how-to-configure-create-react-app/">Customizing create-react-app: How to Make Your Own Template</a>.</li>
</ul>
<p>There's also <a target="_blank" href="https://github.com/timarney/react-app-rewired">react-app-rewired</a> for a similar purpose, but it's mostly unmaintained right now and intended for older versions of CRA behind v4.</p>
<h3 id="heading-how-to-add-linter-ignores-for-ejected-files">How to Add Linter Ignores for Ejected Files</h3>
<p>A lot of the new files from the ejected configuration might not follow your existing project's linter rules. Until you're done with the upgrade, I recommend just adding new ignores on the top of the failing files like:</p>
<pre><code class="lang-js"><span class="hljs-comment">/* eslint-disable import/order */</span>

<span class="hljs-comment">// rest of file</span>
...
</code></pre>
<p>Once you're done with the entire tutorial, feel free to go back and try fixing some of the linter issues, but it might be okay to leave these files as-is since you'll rarely go in to change anything.</p>
<h3 id="heading-how-to-fix-absolute-paths-for-jest">How to Fix Absolute Paths for Jest</h3>
<p>In your <code>package.json</code>, the jest <code>"testRunner"</code> option might be encoded to the absolute path that only makes sense on your computer. So, you'll want to change it to a path based on your project's root directory. </p>
<p>While this might work fine for your local development, it will break for any collaborators or cloud computers.</p>
<pre><code class="lang-json">... 
<span class="hljs-string">"jest"</span>: { 
    ... 
    <span class="hljs-attr">"testRunner"</span>: <span class="hljs-string">"/my/computer/path/project_name/node_modules/jest-circus/runner.js"</span>, 
    ... 
}, 
...
</code></pre>
<p>We use the option <code>&lt;rootDir&gt;</code> that is provided by <a target="_blank" href="https://jestjs.io/docs/configuration#rootdir-string">Jest</a>:</p>
<pre><code class="lang-json">... 
<span class="hljs-string">"jest"</span>: { 
    ... 
    <span class="hljs-attr">"testRunner"</span>: <span class="hljs-string">"&lt;rootDir&gt;/node_modules/jest-circus/runner.js"</span>, 
    ... 
}, 
...
</code></pre>
<p>You might not have to do this on all projects, but <code>"modulePaths"</code> may need an update as well:</p>
<pre><code class="lang-json">...
<span class="hljs-string">"jest"</span>: { 
    ... 
    <span class="hljs-attr">"modulePaths"</span>: [ <span class="hljs-string">"/my/computer/path/project_name/src"</span> ] 
    ... 
}, 
...
</code></pre>
<p>Just remove the reference to your computer's absolute path:</p>
<pre><code class="lang-json">...
<span class="hljs-string">"jest"</span>: { 
    ... 
    <span class="hljs-attr">"modulePaths"</span>: [ <span class="hljs-string">"src"</span> ] 
    ... 
}, 
...
</code></pre>
<h3 id="heading-how-to-update-your-dockerfile-and-other-build-processes-with-the-ejected-folders">How to Update your Dockerfile and Other Build Processes with the Ejected Folders</h3>
<p>Make sure to include the new ejected folders, <code>scripts/</code> and  <code>config/</code>,  into your <code>Dockerfile</code> and other build processes you might be using that existed outside CRA. </p>
<p>For example, the Dockerfile will have the additions of new directories that CRA created that we also want to copy over.</p>
<pre><code class="lang-dockerfile">... 
<span class="hljs-keyword">COPY</span><span class="bash"> scripts scripts/</span>
<span class="hljs-keyword">COPY</span><span class="bash"> config config/ </span>
...
</code></pre>
<h2 id="heading-how-to-override-webpack-md4-to-sha256">How to Override Webpack MD4 to SHA256</h2>
<p>Based on this <a target="_blank" href="https://stackoverflow.com/a/78005686">StackOverflow answer</a>, we add to <code>webpack.config.js</code> right before we start defining <code>module.exports</code> to use the relatively more modern and secure SHA256 instead of MD4 that's also built into Webpack:</p>
<pre><code class="lang-js"><span class="hljs-comment">// ... </span>
<span class="hljs-comment">// https://stackoverflow.com/a/78005686 </span>
<span class="hljs-keyword">const</span> crypto = <span class="hljs-built_in">require</span>(<span class="hljs-string">"crypto"</span>); 
<span class="hljs-keyword">const</span> crypto_orig_createHash = crypto.createHash; crypto.createHash = <span class="hljs-function"><span class="hljs-params">algorithm</span> =&gt;</span> crypto_orig_createHash(algorithm == <span class="hljs-string">"md4"</span> ? <span class="hljs-string">"sha256"</span> : algorithm); 
<span class="hljs-comment">// This is the production and development configuration. </span>
<span class="hljs-comment">// It is focused on developer experience, fast rebuilds, and a minimal bundle. </span>
<span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">webpackEnv</span>) 
// ...</span>
</code></pre>
<p>Once you've changed this, the envelope errors should disappear and your validation commands should now work for Node 18.</p>
<h2 id="heading-how-to-upgrade-to-the-latest-version-of-jest">How to Upgrade to the Latest Version of Jest</h2>
<p>The <code>eject</code> also exposes the Babel configuration used for making more recent versions of Jest work correctly. This works great for version 26 but moving the CRA config to the latest version (v29 at the time of writing) has a few more steps. </p>
<p>You should go through <code>v26</code> -&gt; <code>v28</code> -&gt; <code>v29</code> (skipping v27) for all the Jest dependencies. This part is optional if you're happy with CRA v4's Jest 26, but until you eject, you're blocked from upgrading to a recent version of Jest.</p>
<p>I'm skipping Jest 27 because it'll require a change in <code>config/jest/babelTransform.js</code> where you'll have to change <code>module.exports = babelJest.default.createTransformer({</code> to <code>module.exports = babelJest.createTransformer({</code>. This was a bug <a target="_blank" href="https://github.com/jestjs/jest/pull/12399">fixed</a> in version 28. Still, if you want to go through Jest 27 as well, you'll be able to follow the rest of the steps with this change and then optionally reverting it on Jest 28.</p>
<p>I also highly recommend reading the introduction articles for each of the Jest version upgrades:</p>
<ul>
<li><a target="_blank" href="https://jestjs.io/blog/2021/05/25/jest-27">Jest 27: New Defaults for Jest, 2021 edition ⏩</a></li>
<li><a target="_blank" href="https://jestjs.io/blog/2022/04/25/jest-28">Jest 28: Shedding weight and improving compatibility 🫶</a></li>
<li><a target="_blank" href="https://jestjs.io/blog/2022/08/25/jest-29">Jest 29: Snapshot format changes</a></li>
</ul>
<p>Most of the issues come from Jest 28 having many breaking changes, but the rest of the upgrade path is fairly straightforward.</p>
<h3 id="heading-how-to-bump-to-jest-28">How to Bump to Jest 28</h3>
<p>For each upgrade, I recommend doing a find and replace for all the many Jest-related packages in your <code>package.json</code> since the version numbers are all synced. Once you update the numbers, just run <code>$ yarn install</code>:</p>
<pre><code class="lang-json">... 
<span class="hljs-string">"devDependencies"</span>: { 
    ...
    <span class="hljs-attr">"babel-jest"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    ...
    <span class="hljs-attr">"jest"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    <span class="hljs-attr">"jest-circus"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    <span class="hljs-attr">"jest-resolve"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    ...
} 
...
</code></pre>
<h3 id="heading-how-to-explicitly-set-jsdom-as-the-test-environment">How to Explicitly Set <code>jsdom</code> as the Test Environment</h3>
<p>If you try running your tests out of the box with <code>$ yarn test</code>. It'll give you this error:</p>
<pre><code>● Validation <span class="hljs-built_in">Error</span>: 
Test environment jest-environment-jsdom cannot be found. 
Make sure the testEnvironment configuration option points to an existing node <span class="hljs-built_in">module</span>. 
Configuration Documentation: https:<span class="hljs-comment">//jestjs.io/docs/configuration </span>
As <span class="hljs-keyword">of</span> Jest <span class="hljs-number">28</span> <span class="hljs-string">"jest-environment-jsdom"</span> is no longer shipped by <span class="hljs-keyword">default</span>, make sure to install it separately.
</code></pre><p>In Jest 27, Jest <a target="_blank" href="https://jestjs.io/blog/2021/05/25/jest-27">changed the default test environment</a> to be meant for a more lightweight NodeJS backend environment. However, we have a frontend application, so we still want to test with a simulated browser environment that older Jest versions were based off called <a target="_blank" href="https://github.com/jsdom/jsdom">jsdom</a>. </p>
<p>To fix this, add <code>"jest-environment-jsdom"</code> to your dependencies and then run <code>$ yarn install</code>.</p>
<pre><code class="lang-json">... 
<span class="hljs-string">"devDependencies"</span>: { 
    ...
    <span class="hljs-attr">"babel-jest"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    ...
    <span class="hljs-attr">"jest"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    <span class="hljs-attr">"jest-circus"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    <span class="hljs-attr">"jest-resolve"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    <span class="hljs-attr">"jest-environment-jsdom"</span>: <span class="hljs-string">"^28.1.3"</span>, 
    ...
} 
...
</code></pre>
<h3 id="heading-how-to-fix-transformer-return-type-for-process-and-processasync">How to Fix Transformer Return Type for <code>process()</code> and <code>processAsync()</code></h3>
<p>‌‌Now, if you run <code>yarn test</code>, you'll get this:</p>
<pre><code>FAIL  src/App.test.js 
● Test suite failed to run 
● Invalid <span class="hljs-keyword">return</span> value: <span class="hljs-string">`process()`</span> or/and <span class="hljs-string">`processAsync()`</span> method <span class="hljs-keyword">of</span> code transformer found at <span class="hljs-string">"path/in/my/computer"</span> 
should <span class="hljs-keyword">return</span> an object or a <span class="hljs-built_in">Promise</span> resolving to an object. The object must have <span class="hljs-string">`code`</span> property <span class="hljs-keyword">with</span> a string <span class="hljs-keyword">of</span> processed code. 
This error may be caused by a breaking change <span class="hljs-keyword">in</span> Jest <span class="hljs-number">28</span>: https:<span class="hljs-comment">//jestjs.io/docs/upgrading-to-jest28#transformer Code Transformation Documentation: https://jestjs.io/docs/code-transformation</span>
</code></pre><p>This is because the <code>process()</code> functions that used to return a string now expect an object in the format of <code>{ code:</code>old_string_here<code>}</code>. </p>
<p>To fix this, we go into our ejected <code>config/jest</code> folder, and we change the output shape for all our files. For CSS, it's a single line change:</p>
<pre><code class="lang-js"><span class="hljs-comment">// This is a custom Jest transformer turning style imports into empty objects. </span>
<span class="hljs-comment">// http://facebook.github.io/jest/docs/en/webpack.html </span>

<span class="hljs-built_in">module</span>.exports = { 
    process() { 
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">code</span>: <span class="hljs-string">'module.exports = {};'</span> }; 
    }, 
    getCacheKey() { 
        <span class="hljs-comment">// The output is always the same. </span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">'cssTransform'</span>; 
    }, 
};
</code></pre>
<p>and for files, you have to change both branch return statements:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>); 
<span class="hljs-keyword">const</span> camelcase = <span class="hljs-built_in">require</span>(<span class="hljs-string">'camelcase'</span>); 

<span class="hljs-comment">// This is a custom Jest transformer turning file imports into filenames. // http://facebook.github.io/jest/docs/en/webpack.html </span>
<span class="hljs-built_in">module</span>.exports = { 
    process(src, filename) { 
        <span class="hljs-keyword">const</span> assetFilename = <span class="hljs-built_in">JSON</span>.stringify(path.basename(filename)); 
        <span class="hljs-keyword">if</span> (filename.match(<span class="hljs-regexp">/\.svg$/</span>)) { 
            <span class="hljs-comment">// Based on how SVGR generates a component name: </span>
            <span class="hljs-comment">// https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 </span>
            <span class="hljs-keyword">const</span> pascalCaseFilename = camelcase(path.parse(filename).name, { <span class="hljs-attr">pascalCase</span>: <span class="hljs-literal">true</span>, }); 
            <span class="hljs-keyword">const</span> componentName = <span class="hljs-string">`Svg<span class="hljs-subst">${pascalCaseFilename}</span>`</span>; 
            <span class="hljs-keyword">return</span> { <span class="hljs-attr">code</span>: <span class="hljs-string">`const React = require('react')...`</span> <span class="hljs-comment">// pretty long string }; </span>
        }

        <span class="hljs-keyword">return</span> {<span class="hljs-attr">code</span>: <span class="hljs-string">`module.exports = <span class="hljs-subst">${assetFilename}</span>;`</span> }; 
    }, 
};
</code></pre>
<p>Note: As of the time of writing, the error message link to the upgrade guide tutorial <a target="_blank" href="https://github.com/jestjs/jest/issues/15112#issuecomment-2160883936">doesn't work</a>, but you can find the correct link at <a target="_blank" href="https://jest-archive-august-2023.netlify.app/docs/28.x/upgrading-to-jest28/">https://jest-archive-august-2023.netlify.app/docs/28.x/upgrading-to-jest28/</a>. There's also an older <a target="_blank" href="https://web.archive.org/web/20230330085721/https://jestjs.io/docs/28.x/upgrading-to-jest28#transformer">archive link</a> if that doesn't work.</p>
<h3 id="heading-how-to-bump-jest-to-29">How to Bump Jest to 29</h3>
<p>Once all the validation steps are working with Jest 28, the upgrade to 29 should be smoother. Just update your <code>package.json</code> and run <code>$ yarn install</code>:</p>
<pre><code class="lang-json">... 
<span class="hljs-string">"devDependencies"</span>: { ... 
    <span class="hljs-attr">"babel-jest"</span>: <span class="hljs-string">"^29.7.0"</span>, 
    <span class="hljs-attr">"jest"</span>: <span class="hljs-string">"^29.7.0"</span>, 
    <span class="hljs-attr">"jest-circus"</span>: <span class="hljs-string">"^29.7.0"</span>, 
    <span class="hljs-attr">"jest-resolve"</span>: <span class="hljs-string">"^29.7.0"</span>, 
    <span class="hljs-attr">"jest-environment-jsdom"</span>: <span class="hljs-string">"^29.7.0"</span> ... 
} 
...
</code></pre>
<p>At this point, <code>$ yarn test</code> should work correctly with your existing test suite. </p>
<h2 id="heading-how-far-should-i-upgrade-nodejs">How Far Should I Upgrade NodeJS?</h2>
<p>Trying to decide how far ahead to upgrade Node versions can be a tricky question. Following the above steps, I was able to get all the Node versions up until the most recent Node 22 working. </p>
<p>At the time of writing, 18 is a pretty good stopping point in terms of current support and recent ECMAScript support. But if you're looking to decide, then the following three factors are the most important:</p>
<ol>
<li>Library support: Look at all your critical libraries and see if they have a strong preference for a certain version or have breaking issues for more recent versions. Later Node versions are usually better, but sometimes old libraries didn't get the right patches and might block your upgrade.</li>
<li>Support windows: Different Node versions have a window where the maintainers consider it under "Maintenance", "Active", "Current" or "Unsupported", and over time the older versions lose maintenance. The even versions are also designated LTS (Long Term Support), giving support for a long time and what works for most people. The website has a helpful chart for this: <a target="_blank" href="https://nodejs.org/en/about/previous-releases">https://nodejs.org/en/about/previous-releases</a>.</li>
<li>Language feature support: ECMAScript's specification is always evolving with every year, and getting to use the newer syntax with nicer constructs is always a big quality of life upgrade. I love <a target="_blank" href="https://node.green/">https://node.green/</a> which has a table of Node versions against ECMAScript syntax features with code examples for each feature.</li>
</ol>
<p>Due to technologies like <a target="_blank" href="https://babeljs.io/">Babel</a> (bundled with Create React App), you don't need to worry too much about the end users of your website, as newer Node features will just get transpiled to browser-compliant ones.</p>
<h2 id="heading-should-you-still-use-create-react-scripts-what-alternatives-are-there">Should You Still Use Create React Scripts? What Alternatives Are There?</h2>
<p>In this tutorial, I decided to eject out of CRA to access the Webpack and Babel configuration, and many CRA projects have eventually come to do this as well. Maintenance of CRA has nearly stopped while the ecosystem keeps evolving. </p>
<p>Personally, I recommend someone creating a React project today to try newer alternatives like <a target="_blank" href="https://vitejs.dev/guide/">Vite</a> or <a target="_blank" href="https://parceljs.org/recipes/react/">Parcel</a> which have a nice starter applications that are simple and easier to understand. Unfortunately, they might not have as many bells and whistles as what CRA gives, but it's good enough for almost all practical modern development. </p>
<p>In the context of education, my old tutorials used <code>create-react-app</code>, and it was such a major help, but my newer ones will use Vite.</p>
<p>Still, your application and development experience might be very different than mine. I recommend reading and learning from these resources to form your own perspective:</p>
<ul>
<li>GitHub <a target="_blank" href="https://github.com/reactjs/react.dev/pull/5487">issue</a> with 200+ responses and 1000s of reactions on if Create React App should be replaced with Vite on the official docs. It also has a <a target="_blank" href="https://github.com/reactjs/react.dev/pull/5487#issuecomment-1409720741">note</a> from the maintainer side of CRA explaining a lot of important context that is highly worth reading. Parcel's maintainer made a really good <a target="_blank" href="https://github.com/reactjs/react.dev/pull/5487#issuecomment-1399360209">comment</a> as well.</li>
<li>Some interesting comments (<a target="_blank" href="https://github.com/reactjs/react.dev/pull/5487#issuecomment-1423368130">one</a>, <a target="_blank" href="https://github.com/facebook/create-react-app/issues/13598">two</a>) on how CRA created a simple and easy to use React experience out of the box without worrying about setup hell and focusing on the actual application.</li>
<li><a target="_blank" href="https://medium.com/@vivekdwivedi/the-end-of-an-era-react-team-no-longer-recommends-create-react-app-f2fe6e842d13">News article</a> explaining that the React Team has chosen to stop recommending Create React App, along with some context behind this and future alternatives.</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>At this point, you should be able to run all your validation scripts and have an application that works with Node 18+ and Jest 29+.</p>
<p>In an ideal world, you'd run into the same hurdles as I did, and everything would be working. Realistically, everyone's application is different, and the internet is full of numerous developers who have gone through this upgrade process with various issues. </p>
<p>I highly suggest making Google, StackOverflow, GitHub, and official library documentation your best friends in the process, and I wish you good luck!</p>
<h3 id="heading-alternatively-how-to-upgrade-to-react-scripts-501">Alternatively: How to Upgrade to React Scripts 5.0.1</h3>
<p>This is beyond the scope of this tutorial, so I'll be briefer here – but here's a little information to get you started. </p>
<p>I suggest starting with the official docs changelog for CRA v5 that includes all the major changes as well as some version upgrade instructions: <a target="_blank" href="https://github.com/facebook/create-react-app/blob/main/CHANGELOG.md">https://github.com/facebook/create-react-app/blob/main/CHANGELOG.md</a>.</p>
<p>Bumping the version is fairly easy, setting <code>react-scripts</code> to <code>5.0.1</code> in your <code>package.json</code>, but then the hard part is all the breaking changes.</p>
<p>The most complicated part of the upgrade is the upgrade to Webpack 5 from Webpack 4. Read Webpack's official guide <a target="_blank" href="https://webpack.js.org/migrate/5/">To v5 from v4</a> which has a nice overview, and look around the internet for guides for this upgrade. A few more hurdles that you might come across:</p>
<ul>
<li>For <code>@babel/helper-compilation-targets: 'opera_mobile' is not a valid target</code> you can add <code>"not op_mob &gt;= 1"</code> to the <code>browserslist</code> array as suggested by this <a target="_blank" href="https://github.com/babel/babel/issues/16171#issuecomment-2015227043">comment</a> on the babel issue tracker. The other comments may also be helpful.</li>
<li>You'll probably have to access the CRA internals for many steps using either React Scripts <code>eject</code> or something like <a target="_blank" href="https://craco.js.org/docs/getting-started/">CRACO version 7</a>.</li>
<li>Webpack 5 has a breaking change which removes support for a lot of browser specific APIs like <code>os</code>, <code>http</code>, <code>util</code> that worked in Webpack 4 that your application may have been using. You can either add all of them back using a package like <a target="_blank" href="https://github.com/Richienb/node-polyfill-webpack-plugin">node-polyfill-webpack-plugin</a> or add imports piecewise following this <a target="_blank" href="https://gist.github.com/ef4/d2cf5672a93cf241fd47c020b9b3066a">cheatsheet</a>.</li>
<li>For Babel eslint parser load errors like <code>Error: Failed to load parser 'babel-eslint' declared in '.eslintrc': Cannot find module 'babel-eslint'</code> , you might have to swap out <code>"parser": "babel-eslint"</code> with <code>"parser": "@babel/eslint-parser"</code> in your <code>.eslintrc</code> and install <code>"@babel/eslint-parser"</code> in your <code>package.json</code>. This might be caused by the move of <code>babel-eslint</code> to the <code>@babel</code> monorepo, see <a target="_blank" href="https://babeljs.io/blog/2020/07/13/the-state-of-babel-eslint">The State of babel-eslint</a> for more info.</li>
<li>Some filetype imports that used to work with Webpack 4 will start breaking with <code>Module build failed: UnhandledSchemeError</code> (the actual error took several screens in my Terminal). The solution here will be fixing the prefixes of the files you import, and for external files that were being included, see if you can find a npm package for it. For example, one of my projects stopped using <code>semantic-ui.min.css</code> downloaded from the internet, and instead I added <code>"semantic-ui-css": "^2.5.0"</code> to my <code>package.json</code>. Definitely read this <a target="_blank" href="https://github.com/webpack/webpack/issues/12792">issue</a> thread in the webpack repo for more information.</li>
</ul>
<p>After all of these I was able to get <code>yarn test</code> and <code>yarn build</code> to succeed, but <code>yarn start</code> still had too many issues and I pivoted to making CRA v4 work instead. Hopefully you might get further than I did.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
