<?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>DEV Community: Alex Booker</title>
    <description>The latest articles on DEV Community by Alex Booker (@bookercodes).</description>
    <link>https://dev.to/bookercodes</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%2F40578%2Fd7dff48a-556d-4d6e-8ea8-65f6da33de23.png</url>
      <title>DEV Community: Alex Booker</title>
      <link>https://dev.to/bookercodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bookercodes"/>
    <language>en</language>
    <item>
      <title>Next.js form validation on the client and server with Zod</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Wed, 01 Jan 2025 14:33:47 +0000</pubDate>
      <link>https://dev.to/bookercodes/nextjs-form-validation-on-the-client-and-server-with-zod-lbc</link>
      <guid>https://dev.to/bookercodes/nextjs-form-validation-on-the-client-and-server-with-zod-lbc</guid>
      <description>&lt;p&gt;Building robust forms in Next.js is thirsty work! &lt;/p&gt;

&lt;p&gt;Not only must you validate forms on the server, you must validate them on the client as well. &lt;/p&gt;

&lt;p&gt;On the client, to ensure the user has a smooth experience, fields should be revalidated when their value changes, but only if the field has been "touched" or the form previously-submitted. &lt;/p&gt;

&lt;p&gt;If JavaScript is disabled, the form ought to regress gracefully. Dynamic form validation won't be possible, but errors should still render alongside their respective fields and preserve their values between requests to the server. &lt;/p&gt;

&lt;p&gt;You want to do all this without writing a bunch of duplicate code and, in this case, &lt;em&gt;without&lt;/em&gt; a form library like &lt;a href="https://react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's how a senior developer would do it utilising Zod ⬇️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt; allows you to define the shape of a valid for submission. Provided you do so in a separate file, you can reference the definition from either the server or a client component, eliminating the possibility of duplicate code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please enter a valid email.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Be at least 8 characters long&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contain at least one letter.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contain at least one number.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contain at least one special character.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SignUpActionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nl"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;To validate the form on the server, import and and validate against the schema when the sever action is submitted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SignUpActionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;signUpAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;_prev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SignUpActionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SignUpActionState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;fieldErrors&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the client, in a client component denoted with &lt;code&gt;"use client"&lt;/code&gt;, create your form:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ℹ️ Info&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;ValidatedInput /&amp;gt;&lt;/code&gt; isn't defined yet - take a moment to understand the form first&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useActionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signUpAction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ValidatedInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/ui/validated-input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SignUpForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;wasSubmitted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setWasSubmitted&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useActionState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signUpAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLFormElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setWasSubmitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;noValidate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidatedInput&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
          &lt;span class="na"&gt;wasSubmitted&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;wasSubmitted&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;fieldSchema&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Password:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidatedInput&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
          &lt;span class="na"&gt;fieldSchema&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;wasSubmitted&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;wasSubmitted&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isPending&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Continue
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the form is submitted, &lt;code&gt;onSubmit&lt;/code&gt; validates the form before indirectly invoking the server action. &lt;/p&gt;

&lt;p&gt;The form component above is not concerned about rendering errors - that is the responsibility of &lt;code&gt;&amp;lt;ValidatedInput /&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidatedInput&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
  &lt;span class="na"&gt;fieldSchema&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;wasSubmitted&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;wasSubmitted&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how we extract the &lt;code&gt;fieldSchema&lt;/code&gt; from &lt;code&gt;signUpFormSchema&lt;/code&gt; using &lt;code&gt;signUpFormSchema.shape&lt;/code&gt;. By passing the field schema in this way, &lt;code&gt;&amp;lt;ValidatedInput /&amp;gt;&lt;/code&gt; remains flexible and reusable across your different forms. &lt;/p&gt;

&lt;p&gt;Here's &lt;code&gt;&amp;lt;ValidatedInput /&amp;gt;&lt;/code&gt; in full:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ValidatedInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;wasSubmitted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fieldSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;touched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTouched&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fieldSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;formErrors&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fieldSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fieldErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;getErrors&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldRenderErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;wasSubmitted&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;touched&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleBlur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTouched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onBlur&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleBlur&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fieldErrors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;border-red-500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;shouldRenderErrors&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fieldErrors&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ValidatedInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's based on Kent's &lt;a href="https://www.epicreact.dev/improve-the-performance-of-your-react-forms" rel="noopener noreferrer"&gt;&lt;code&gt;FastInput&lt;/code&gt;&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Learn useActionState quickly with an example (Next.js 15)</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Sat, 16 Nov 2024 17:42:08 +0000</pubDate>
      <link>https://dev.to/bookercodes/learn-useactionstate-quickly-4jj7</link>
      <guid>https://dev.to/bookercodes/learn-useactionstate-quickly-4jj7</guid>
      <description>&lt;p&gt;When using a form, the &lt;a href="https://react.dev/reference/react/useActionState" rel="noopener noreferrer"&gt;&lt;code&gt;useActionState&lt;/code&gt;&lt;/a&gt; manages state by automatically updating a &lt;code&gt;state&lt;/code&gt; variable with the value returned from the server action. This is particularly helpful for rendering input field validation errors, as shown in the example below using &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;form.tsx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useActionState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signUp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../actions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SignUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useActionState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signUp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Username:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;
          &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Password:&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;
          &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Sign Up"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;actions.ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SignUpSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Be at least 8 characters long&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contain at least one letter.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contain at least one number.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contain at least one special character.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SignUpActionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;signUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;_prevState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SignUpActionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SignUpActionState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validatedFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SignUpSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validatedFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validatedFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;fieldErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// process validated form inputs here&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useActionState&lt;/code&gt; also returns an &lt;code&gt;isPending&lt;/code&gt; property (&lt;a href="https://react.dev/reference/react/useActionState#display-information-after-submitting-a-form" rel="noopener noreferrer"&gt;example&lt;/a&gt;) that indicates whether the server action's promise is still resolving.&lt;/p&gt;

&lt;p&gt;Reference &lt;code&gt;isPending&lt;/code&gt; to temporarily disable form elements, such as a submit button, to prevent users from clicking it multiple times in quick succession before the ongoing action has completed. &lt;/p&gt;

&lt;h2&gt;
  
  
  useActionState vs useFormAction and useFormStatus
&lt;/h2&gt;

&lt;p&gt;If you’re familiar with &lt;code&gt;useFormAction&lt;/code&gt; and &lt;code&gt;useFormStatus&lt;/code&gt;, you’ll find &lt;code&gt;useActionState&lt;/code&gt; quite similar.&lt;/p&gt;

&lt;p&gt;Essentially, it combines the functionality of both hooks and is renamed to reflect that server actions aren't just for forms (you can also use &lt;code&gt;useActionState&lt;/code&gt; with buttons and other elements.)&lt;/p&gt;

&lt;p&gt;Keep in mind that &lt;code&gt;useFormStatus&lt;/code&gt; is deprecated as of Next.js 15 and you should import &lt;code&gt;useActionState&lt;/code&gt; moving forward.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>5 Best Instant Messaging APIs</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Fri, 09 Feb 2024 12:57:54 +0000</pubDate>
      <link>https://dev.to/ably/5-best-instant-messaging-apis-4g7e</link>
      <guid>https://dev.to/ably/5-best-instant-messaging-apis-4g7e</guid>
      <description>&lt;p&gt;Looking for the perfect instant messaging solution can be a daunting task. &lt;/p&gt;

&lt;p&gt;You need a provider that not only supports all the necessary instant messaging features but also offers delivery and scaling guarantees to ensure a smooth chat operation as your business grows.&lt;/p&gt;

&lt;p&gt;While it might be tempting to start with a simple solution and see how your needs evolve, underestimating the importance of finding the right provider can be a costly mistake. Switching solutions later requires significant time, resources, and money, not to mention meticulous testing to avoid disruption to your business, which can lead to reputational damage and lost revenue. You need to get it right the first time, and this post is here to help you do exactly that. &lt;/p&gt;

&lt;p&gt;We've narrowed it down to just the 5 best instant messaging APIs and SDKs, taking into consideration factors like features, scalability, and price. This way, you can make an informed decision about which provider will work best for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are instant messaging APIs and SDKs?
&lt;/h2&gt;

&lt;p&gt;Fundamentally, instant messaging APIs and SDKs enable developers to build instant messaging more quickly by managing the infrastructure necessary to send and receive messages in realtime, plus &lt;a href="https://hubs.la/Q01XM4Xc0" rel="noopener noreferrer"&gt;must-have chat features&lt;/a&gt; like typing indicators, online indicators, and read receipts.&lt;/p&gt;

&lt;p&gt;If you’re wondering how an API and SDK differ in this context, it has to do with how developers interface with said infrastructure. &lt;/p&gt;

&lt;p&gt;An API gives developers direct access to the infrastructure through endpoints to send and receive messages, subscribe to typing indicators, and more. Equipped with the nuts and bolts, developers can now implement a chat UI with total flexibility.&lt;/p&gt;

&lt;p&gt;An SDK, on the other hand, offers a complete set of development tools, usually including prebuilt chat UI components. By assembling chat “building blocks”, developers can save time up front. &lt;/p&gt;

&lt;p&gt;Remember that APIs and SDKs aren’t mutually exclusive, and some providers offer both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should you look for in an instant messaging solution?
&lt;/h2&gt;

&lt;p&gt;Instant messaging apps like WhatsApp and Slack have set the bar for instant messaging experiences, and it’s important to find a provider that enables you to deliver the same quality experience. &lt;/p&gt;

&lt;p&gt;These are the ten foundational features you have to be able to deliver using your chosen solution, and that you should consider during your evaluation process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low Latency:&lt;/strong&gt; If messages take too long to send or load, users won’t have a smooth user experience and will take the conversation elsewhere. That could be another channel (for example, a customer might pick up the phone and start their support request from scratch) or, if the experience is consistently laggy, users might even switch to another app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The ability to scale:&lt;/strong&gt; As your user volume grows, the load on the server also increases. If the server can’t handle this increased load, messaging features can become unresponsive, causing a poor user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reliability:&lt;/strong&gt; Even a short downtime or an occasional error can be frustrating for users. This is unfortunate, but for businesses that rely on chat applications for customer support or sales, downtime can result in lost revenue or missed opportunities. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guaranteed delivery:&lt;/strong&gt; Messages should be delivered exactly once and in the correct order, even in the face of network issues. Otherwise, messages might come flying in out of order, and more than once, making it almost impossible to discern what is trying to be communicated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elasticity:&lt;/strong&gt; Instant messaging app usage can be unpredictable. Applications may experience sudden spikes in usage during peak hours or events, or during product launches and marketing campaigns. If the server can’t scale its resources up dynamically in response to changes in user demand, chat features may become unresponsive. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rich features:&lt;/strong&gt; Your users will expect typing indicators, online status indicators, read receipts, push notifications, and more. If users can’t accomplish what they need to with your chat, they might switch to another app where they can.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integrations:&lt;/strong&gt; Instant messaging solutions need to integrate with the other tools in your tech stack. Dedicated integrations between technologies can help keep engineering requirements to a minimum.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data governance:&lt;/strong&gt; The way data is stored, processed, and disposed of needs to meet regulatory requirements, wherever you operate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chat moderation:&lt;/strong&gt; Messaging experiences need to be safe for both end users and chat operators. The process of monitoring and controlling user-generated messages helps ensure compliance with your terms of use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontend UI:&lt;/strong&gt; Customizable themed components including colors and fonts help to keep your chat solution a cohesive part of your customer experience. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another key consideration when evaluating a live chat solution is, of course, cost.&lt;/p&gt;

&lt;p&gt;Most instant messaging API providers base their pricing on the number of &lt;a href="https://hubs.la/Q01XM4Mx0" rel="noopener noreferrer"&gt;monthly active users (MAUs)&lt;/a&gt;. Some also account for additional factors like the number of messages and concurrent users. &lt;/p&gt;

&lt;p&gt;While it might be hard to predict your precise usage up front, you should evaluate how a given provider’s pricing model might scale for your project. For example, some pricing models get gradually more expensive as you grow, while others hit an inflection point.&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%2Fae9t8tzos64qfka39mis.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%2Fae9t8tzos64qfka39mis.png" alt="chat-api-pricing-models-illustrated--1-" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we’ve covered the fundamentals, let’s look at the 5 best instant messaging APIs on the market.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5 best instant messaging APIs and SDKs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Ably
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hubs.la/Q01XM6GG0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; is a realtime experience infrastructure used by companies like HubSpot, I7Live, and Riff to build feature-rich and reliable chat.&lt;/p&gt;

&lt;p&gt;With the Ably API, you get the nuts and bolts needed to implement chat your way. For example, &lt;a href="https://hubs.la/Q01XM7Zz0" rel="noopener noreferrer"&gt;publish&lt;/a&gt; and &lt;a href="https://hubs.la/Q01XM7Zz0" rel="noopener noreferrer"&gt;subscribe&lt;/a&gt; functions to send and receive messages in a 1:1 or group setting, as well as additional data like which users are typing. Ably also provides other essential features, including &lt;a href="https://hubs.la/Q01XM8bK0" rel="noopener noreferrer"&gt;presence&lt;/a&gt; to see who’s online, &lt;a href="https://ably.com/tutorials/history" rel="noopener noreferrer"&gt;message history&lt;/a&gt;, and &lt;a href="https://hubs.la/Q01XM8qb0" rel="noopener noreferrer"&gt;push notifications&lt;/a&gt;, and they all work cross-platform, enabling you to deliver a consistent experience across the web, Android, and iOS.&lt;/p&gt;

&lt;p&gt;Whether it's a fundamental feature like threads, a contact book that needs to integrate with your database, or even live stream chat with donations, Ably's flexibility extends beyond customization options, allowing you to create engaging chat experiences beyond what's typically possible. Further, because you’re building on the &lt;a href="https://hubs.la/Q01XM8qn0" rel="noopener noreferrer"&gt;Ably platform&lt;/a&gt;, you’ll benefit from low latency, guaranteed message ordering, and an assuring 99.999% average uptime SLA.&lt;/p&gt;

&lt;p&gt;Although the nuts and bolts provided by Ably are perfect for chat, they’re not limited to chat alone. That’s great news because once you get started with Ably, you can use the same foundations to build &lt;a href="https://hubs.la/Q01XM8KD0" rel="noopener noreferrer"&gt;other realtime features&lt;/a&gt;, including notifications, and realtime collaboration.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Twilio
&lt;/h3&gt;

&lt;p&gt;Twilio is a communication platform with a suite of products designed to help developers code communication features like SMS, email, video, and - you guessed it - instant messaging. &lt;/p&gt;

&lt;p&gt;Their chat product is called &lt;a href="https://www.twilio.com/en-us/messaging/conversations-api" rel="noopener noreferrer"&gt;Conversations API&lt;/a&gt; and it’s unique because it’s specifically designed to enable omni-channel messaging. &lt;/p&gt;

&lt;p&gt;Suppose your project revolves around connecting members from your team with customers. With Conversations API, the customer can start a conversation in-app then continue it on WhatsApp, for example. Twilio helps connect the dots, creating a seamless omni-channel experience on both sides. &lt;/p&gt;

&lt;p&gt;Although Conversations API &lt;em&gt;could&lt;/em&gt; be adapted to enable your users to chat together, that isn’t really the intended use case. As a result, Conversations API offers a limited chat feature set compared to some alternative solutions. For example, you can’t use Twilio to &lt;a href="https://hubs.la/Q01XM8LX0" rel="noopener noreferrer"&gt;implement public group chat&lt;/a&gt;, and they don't support @user and @channel mentions or reactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Firebase
&lt;/h3&gt;

&lt;p&gt;Firebase is an app development platform backed by Google that famously replaces the need for a backend. It accomplishes this by providing developers with a variety of services, including authentication, push notifications, and a database called &lt;a href="https://firebase.google.com/docs/firestore" rel="noopener noreferrer"&gt;Cloud Firestore&lt;/a&gt; that can be adapted to store and broadcast chat messages in realtime.&lt;/p&gt;

&lt;p&gt;Firebase can be an attractive option because it manages all of your backend services in one place. These services are meant to gel well together, and it should therefore be smooth to implement user management, roles and permissions, chat history, and even push notifications under the roof of one platform. Of course, this might not be so compelling if you already have a backend and user management system. In that case, you’ll probably want to assess how you’ll keep Firebase and your systems in sync instead.&lt;/p&gt;

&lt;p&gt;Remember, since Firebase is a general-purpose platform, it doesn’t have a specific notion of instant messaging. While implementing basic messaging might be fairly straightforward, expect to invest a significant amount of upfront development to get features like &lt;a href="https://hubs.la/Q01XM9820" rel="noopener noreferrer"&gt;typing indicators&lt;/a&gt;, &lt;a href="https://hubs.la/Q01XM9zz0" rel="noopener noreferrer"&gt;read receipts&lt;/a&gt;, and presence indicators to work, especially without a hitch at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Sendbird
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://sendbird.com/" rel="noopener noreferrer"&gt;Sendbird Chat&lt;/a&gt; enables developers to implement chat and instant messaging by providing an SDK with ready-made chat UI components. They cover all the essential features you might expect, including voice and video, which makes it possible to deliver a comprehensive experience. &lt;/p&gt;

&lt;p&gt;This out-of-the-box experience enables developers to “turn on” chat quickly, albeit at a recurring monthly premium. A common concern about Sendbird is that, beyond a certain scale, their pricing becomes somewhat opaque. This can make it hard to calculate precisely how much you’ll pay, especially if you’re dealing with a high number of users and traffic spikes.  &lt;/p&gt;

&lt;p&gt;Under the hood, Sendbird manages the chat infrastructure necessary to send and receive messages in realtime while ensuring that messages are stored securely and compliant with relevant standards like SOC 2 and HIPAA. &lt;/p&gt;

&lt;p&gt;It should be noted that although Sendbird touts eight available data centers, each app must reside in a single data center, creating a single point of failure. Moreover, this can negatively impact latency, unless your users happen to be connecting from the same geographical region.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Stream (formerly GetStream)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://getstream.io/chat/" rel="noopener noreferrer"&gt;Stream&lt;/a&gt; provides APIs and chat UI components that developers can use to implement instant messaging into their web and mobile applications.&lt;/p&gt;

&lt;p&gt;They make it possible to build chat experiences with rich features like typing indicators, user presence, threaded conversations, push notifications, message &amp;amp; user moderation, reactions, plus @user and @channel mentions. In other words, everything you need for a modern chat experience.&lt;/p&gt;

&lt;p&gt;All the infrastructure is managed for you. However, all  traffic is processed and stored in a single data center. This can have a negative implication on latency and creates a single point of congestion and failure. This might explain why Stream only offers a 99.95% SLA unless you pay more for an enterprise plan.&lt;/p&gt;

&lt;p&gt;It’s worth noting that instant messaging often goes hand-in-hand with other realtime use cases, such as multiplayer collaboration (think of a product like Figma, where you can edit your design collaboratively and chat with other users in realtime). However, Stream is very much a chat-centric solution. If you’re looking to build other realtime experiences alongside your messaging functionality, Stream can’t help – you will have to use different technologies, which brings additional complexity and increased costs to your project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Which is the best instant messaging solution for your use case?
&lt;/h2&gt;

&lt;p&gt;Like everything in software, the answer is “it depends” - on the features you need, how you envision your users engaging with your app, and on if you want to save time up front with a turn key solution at the cost of flexibility later. It’s critical to try and answer these questions early on so you don’t end up locked-in with a vendor, or with a solution that is great for chat but forces you to source another solution to implement other realtime features like presence indicators, multiplayer collaboration (a la &lt;a href="https://ably.com/blog/buy-vs-build-adobe-acquires-figma" rel="noopener noreferrer"&gt;Figma&lt;/a&gt;), collaborative whiteboarding, and collaborative text editing. &lt;/p&gt;

&lt;p&gt;Although we might be biased, if you are looking for a solution that can support you now - as you integrate chat - and as you scale, and introduce new features, we recommend trying Ably for free (no credit card required). Our free plan includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6M monthly messages&lt;/li&gt;
&lt;li&gt;200 peak concurrent channels&lt;/li&gt;
&lt;li&gt;200 peak concurrent connections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a realtime Platform as a Service (PaaS), Ably gives you the infrastructure and tooling to build featureful in-app chat with no scale ceiling, low latency, predictable pricing, and 99.999% SLA. The best part? A new realtime feature doesn't require you to rebuild your entire back end or code against a brand-new API.&lt;/p&gt;

&lt;p&gt;Whether it's a fundamental shift in how your product works, such as implementing multiplayer collaboration, or you want to enhance the experience through a feature such as in-app messaging, Ably makes it easy to get started thanks to integrations with almost every popular programming language and &lt;a href="https://ably.com/sign-up" rel="noopener noreferrer"&gt;a free developer account&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Essential guide to WebSocket authentication</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Wed, 13 Dec 2023 14:15:46 +0000</pubDate>
      <link>https://dev.to/ably/essential-guide-to-websocket-authentication-564o</link>
      <guid>https://dev.to/ably/essential-guide-to-websocket-authentication-564o</guid>
      <description>&lt;p&gt;Authenticating &lt;a href="https://hubs.la/Q02cBJTh0" rel="noopener noreferrer"&gt;WebSocket&lt;/a&gt; connections from the browser is a lot trickier than it should be.&lt;/p&gt;

&lt;p&gt;Cookie authentication isn’t suitable for every app, and the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket" rel="noopener noreferrer"&gt;WebSocket browser API&lt;/a&gt; makes it impossible to set an &lt;code&gt;Authorization&lt;/code&gt; header with a token.&lt;/p&gt;

&lt;p&gt;It’s actually all a bit of a mess!&lt;/p&gt;

&lt;p&gt;That is the last thing you want to hear when it comes to security, so I’ve done the research to present this tidy list of methods to send credentials from the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge with WebSocket authentication
&lt;/h2&gt;

&lt;p&gt;Even though &lt;a href="https://ably.com/topic/websockets-vs-http" rel="noopener noreferrer"&gt;WebSocket and HTTP&lt;/a&gt; are completely separate protocols, every WebSocket connection begins with a HTTP handshake.&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%2Fzuznwnsh50cobv2g77bu.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%2Fzuznwnsh50cobv2g77bu.png" alt="HTTP handshake" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://datatracker.ietf.org/doc/html/rfc6455" rel="noopener noreferrer"&gt;WebSocket specification&lt;/a&gt; does not prescribe any particular way to authenticate a WebSocket connection once it’s established but suggests you authenticate the HTTP handshake before establishing the connection:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This protocol doesn’t prescribe any particular way that servers can authenticate clients during the WebSocket handshake. The WebSocket server can use any client authentication mechanism available to a generic HTTP server, such as cookies or HTTP authentication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HTTP has dedicated fields for authentication (like the &lt;code&gt;Authorization&lt;/code&gt; header), and you likely have a standard way to authenticate HTTP requests already so this makes a lot of sense on the surface!&lt;/p&gt;

&lt;p&gt;Surprisingly, though, the WebSocket browser API doesn’t allow you to set arbitrary headers with the HTTP handshake like &lt;code&gt;Authorization&lt;/code&gt;. 😱&lt;/p&gt;

&lt;p&gt;HTTP cookie authentication is an option, but, as you will see in this post, it’s not always suitable, and potentially even vulnerable to &lt;a href="https://owasp.org/www-community/attacks/csrf" rel="noopener noreferrer"&gt;CSRF&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I’ll outline your options to work around this remarkable limitation of modern browsers to securely and reliably send credentials to the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication methods for securing WebSocket connections
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Send access token in the query parameter
&lt;/h3&gt;

&lt;p&gt;One of the simplest methods to pass credentials from the client to a WebSocket server is to pass the access token via the URL like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;wss://website.com?token=your_token_here&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then, on the server, you can authenticate the request.&lt;/p&gt;

&lt;p&gt;Here’s an example with Node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebSocketServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// noSever: Tells WebSocketServer not to create an HTTP server &lt;/span&gt;
&lt;span class="c1"&gt;// but to instead handle upgrade requests from the existing &lt;/span&gt;
&lt;span class="c1"&gt;// server (above).&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocketServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;noServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: Actually authenticate token&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;upgrade&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;authed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// \r\n\r\n: These are control characters used in HTTP to&lt;/span&gt;
        &lt;span class="c1"&gt;// denote the end of the HTTP headers section.&lt;/span&gt;
        &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HTTP/1.1 401 Unauthorized&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;wsServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleUpgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Manually emit the 'connection' event on a WebSocket &lt;/span&gt;
        &lt;span class="c1"&gt;// server (we subscribe to this event below).&lt;/span&gt;
        &lt;span class="nx"&gt;wsServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;wsServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// %s: Convert the bytes (buffer) into a string using&lt;/span&gt;
        &lt;span class="c1"&gt;// utf-8 encoding.&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;received %s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server started on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the token is valid, you move ahead with the upgrade. Otherwise, send a standard 401 “Unauthorized” response code and close the underlying socket.&lt;/p&gt;

&lt;p&gt;This approach is easy to reason about and it only takes a few lines of code to implement on the client.&lt;/p&gt;

&lt;p&gt;The downside we need to explore is the security implication of encoding the token in the query string in this way.&lt;/p&gt;

&lt;p&gt;Some developers on public forums will argue this isn’t so bad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you use TLS (WSS), query strings are encrypted in transit.&lt;/li&gt;
&lt;li&gt;Compared to HTTP, WebSocket URLs aren’t really exposed to the user. Users can't bookmark or copy-and-paste them. This minimizes the risk of accidental sharing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, it is important to acknowledge query parameters will still show up in plaintext on the server where they will likely get logged.&lt;/p&gt;

&lt;p&gt;Even if your code doesn’t, the framework or cloud host &lt;a href="https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url" rel="noopener noreferrer"&gt;likely will&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is precarious because logs leak can error messages, for example (&lt;a href="https://owasp.org/www-project-mobile-top-10/2014-risks/m4-unintended-data-leakage" rel="noopener noreferrer"&gt;accidental information disclosure&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Should a malicious actor attain access to the logs, they would have access to all the data and functionality that the user behind the WebSocket connection has.&lt;/p&gt;

&lt;p&gt;In the next section, let’s explore an evolution of this method that’s more secure, albeit more work to implement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Send an ephemeral access token in the query parameter
&lt;/h3&gt;

&lt;p&gt;As we covered in the section above, sending your main access token in the query parameter is not sufficiently secure because it might be logged in plaintext on the server.&lt;/p&gt;

&lt;p&gt;To dramatically reduce the risk, we could use the main access token to request an ephemeral single-use token from an authentication service then send that short-lived token the query parameter.&lt;/p&gt;

&lt;p&gt;This way, by the time the token is logged on the server, it will likely be useless since it will either have already been used or expired.&lt;/p&gt;

&lt;p&gt;The basic flow can be illustrated like this:&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%2Fnqe7srkdgegsf822l9kj.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%2Fnqe7srkdgegsf822l9kj.png" alt="basic flow illustrated" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While inherently more secure than sending the main access token, you now need to implement a custom and stateful authentication service specifically for WebSockets, which is a pretty significant downside considering we first started exploring sending the token in the query parameter because of its convenience!&lt;/p&gt;

&lt;h3&gt;
  
  
  Send access token over WebSocket
&lt;/h3&gt;

&lt;p&gt;Another option to authenticate a WebSocket connection is to send credentials in the first message post-connection.&lt;/p&gt;

&lt;p&gt;The server must then validate the token before allowing the client to do anything else.&lt;/p&gt;

&lt;p&gt;Here’s a contrived server implementation I wrote with Node to illustrate the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebSocketServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;randomUUID&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocketServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: Actually authenticate token&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authenticate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authenticated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Process the message&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nx"&gt;wsServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;

  &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server started on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;If the token is invalid, the server terminates the connection. Otherwise, the it tracks the connection as “authenticated” and processes subsequent messages.&lt;/p&gt;

&lt;p&gt;When implemented correctly, this method is completely secure, however, it involves defining your own custom authentication mechanism protocol securely and correctly.&lt;/p&gt;

&lt;p&gt;Sending credentials over WebSockets in this way has some other downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementing a custom stateful protocol will increase complexity.&lt;/strong&gt; The example above looks simple but in reality you now need to manage session lifetimes, handle synchronization issues when you &lt;a href="https://hubs.la/Q02cBKFf0" rel="noopener noreferrer"&gt;scale&lt;/a&gt;, and deal with potential inconsistencies. Should something go wrong, your users might not be able to login or, worse yet, you might introduce a security vulnerability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You become vulnerable to DOS attacks.&lt;/strong&gt; With this method, anyone can open a WebSocket connection. An attacker might open a bunch of WebSocket connections and refuse to authenticate, tying up server resources like memory indefinitely, potentially overloading your server until it becomes sluggish. To counteract this, you’ll need to implement rigorous timeouts, further contributing to the complexity compared to HTTP-based authentication methods.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Send credentials in a HTTP cookie
&lt;/h3&gt;

&lt;p&gt;The WebSocket handshake is done with a standard HTTP request and response cycle, which supports cookies, allowing you to authenticate the request.&lt;/p&gt;

&lt;p&gt;Authentication using cookies has been widely adopted since the early days of the internet. It's a reliable method that offers security you can trust. However, there are some limitations you should be aware of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not suitable if your WebSocket server is hosted on a different domain.&lt;/strong&gt; If your WebSocket server is on a different domain than your web app, the browser will not send the authentication cookies to the WebSocket server, which makes the authentication fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Vulnerable to CSRF.&lt;/strong&gt; The browser does not enforce a Same-Origin Policy for the WebSocket handshake like it would an ordinary HTTP request. A malicious website badwebsite.com could open a connection to yourwebsite.com and the browser will happily send along the authentication cookie, creating an opportunity for badwebsite.com to send and receive messages on the user’s behalf, unbeknown to them.  To circumvent this, it’s pivotal the server checks the &lt;code&gt;Origin&lt;/code&gt; header of the request before allowing it. Alternatively, you may choose to implement a CSRF token.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Send credentials with the Sec-WebSocket-Protocol header
&lt;/h3&gt;

&lt;p&gt;While the WebSocket browser API doesn’t let you set arbitrary headers like &lt;code&gt;Authorization&lt;/code&gt;, it does allow you to set a value for the &lt;code&gt;Sec-WebSocket-Protocol&lt;/code&gt; header, creating an opportunity to smuggle the token in the request header!&lt;/p&gt;

&lt;p&gt;In vanilla JavaScript, the client WebSocket code might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://example.com/path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your_token_here&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with a library like &lt;a href="https://github.com/robtaussig/react-use-websocket" rel="noopener noreferrer"&gt;React useWebSocket&lt;/a&gt;, something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://example.com/path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your_token_here&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Sec-WebSocket-Protocol&lt;/code&gt; header is designed to negotiate a subprotocol not carry authentication information but some developers including me and &lt;a href="https://github.com/kubernetes/kubernetes/commit/714f97d7baf4975ad3aa47735a868a81a984d1f0" rel="noopener noreferrer"&gt;those behind Kubernetes&lt;/a&gt; are asking “why not?"&lt;/p&gt;

&lt;p&gt;You might be wondering what the downside of this neat workaround is. Every option in this list so far has a downside, and setting &lt;code&gt;Sec-WebSocket-Protocol&lt;/code&gt; is no exception:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The token might get logged in plaintext on the server. Because &lt;code&gt;Sec-WebSocket-Protocol&lt;/code&gt; is not designed to carry authentication tokens, they may end up in log files unintentionally as part of standard logging of WebSocket protocol negotiation, thus causing potential security risks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You might experience unexpected behavior. It’s also important to acknowledge that such use of &lt;code&gt;Sec-WebSocket-Protocol&lt;/code&gt; isn't standardized, meaning libraries, tooling, and middleware might not handle this kind of logic gracefully. For simple apps, this is unlikely to cause a problem, however, in a sufficiently complex system with multiple components, this could cause unexpected behavior including security issues.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Send credentials with basic access authentication
&lt;/h3&gt;

&lt;p&gt;Some posts out there suggest an outdated trick whereby you encode the username and password in the WebSocket URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://username:password@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, the browser will pull these out to add a basic auhtenticaiton access header.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ==&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apart from the fact that basic authentication is limited to a username and password (and you probably want to send a token), this method has always suffered from inconsistent browser support and is now &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#access_using_credentials_in_the_url" rel="noopener noreferrer"&gt;totally deprecated&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Don’t use it. I’m only including a brief note about it here for completeness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forget about WebSocket authentication (mostly) with Ably
&lt;/h3&gt;

&lt;p&gt;So far we’ve weighed the benefits and limitations of each approach however, you could just use a library that handles it all for you under the hood.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://hubs.la/Q02cBLtl0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt;, authentication is a solved problem.&lt;/p&gt;

&lt;p&gt;Ably is a realtime infrastructure API that makes it trivial to add realtime features to your apps compared to if you used WebSockets directly.&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%2Fh6f7mjdcgd6msyq51dw4.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%2Fh6f7mjdcgd6msyq51dw4.png" alt="Ably realtime infrastructure API" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apart from being easy to get started with, Ably handles authentication for you in a secure way, allowing you to focus on the features that really matter to your users.&lt;/p&gt;

&lt;p&gt;Instead of deliberating the best authentication mechanism and burdening all that responsibility even if you’re not a security expert, Ably provides you with “white spots” to fill in with code that connects to your database to authenticate the user. &lt;/p&gt;

&lt;p&gt;The token management part, including refresh tokens and permissions, is all handled for you.&lt;/p&gt;

&lt;p&gt;Learn more about how Ably can help you build impressive realtime features at scale &lt;a href="https://hubs.la/Q02cBLtl0" rel="noopener noreferrer"&gt;here&lt;/a&gt; or &lt;a href="https://hubs.la/Q02cBLHm0" rel="noopener noreferrer"&gt;create a free account&lt;/a&gt; and play around for yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, we explored several methods to send credentials from the web browser to a WebSocket server.&lt;/p&gt;

&lt;p&gt;It would have been nice if I could recommend one go-to method but, as will now be evident to you, each method has advantages and disadvantages that must be considered relative to your project.&lt;/p&gt;

&lt;p&gt;Here’s a quick summary for future reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query parameter:&lt;/strong&gt; Sending the credentials in a query parameter is dead easy, but it introduces a risk of the credentials being logged in plaintext on the server, and that could be risky! A homemade authentication service that issues ephemeral tokens for use in the query parameter would improve the security greatly, however, for many, that is a bridge too far.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;WebSocket connection:&lt;/strong&gt; Sending the credentials over the WebSocket connection is worth considering, however, you usually end up implementing your own authentication protocol that is finicky to maintain, potentially vulnerable to DOS attacks, and doesn’t play well with anything else.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cookies:&lt;/strong&gt; Cookie authentication is attractive due to its reliability and ease of implementation. However, it may not be compatible with your system design.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sec-WebSocket-Protocol:&lt;/strong&gt; Smuggling the token in the &lt;code&gt;Sec-WebSocket-Protocol&lt;/code&gt; is a stroke of genius, and, if it’s good enough for Kubernetes, it might be good enough for you! At the same time, misusing the header in this way might lead to unexpected behavior. User &lt;a href="https://stackoverflow.com/questions/4361173/http-headers-in-websockets-client-api#comment73692893_35890141" rel="noopener noreferrer"&gt;BatteryAcid&lt;/a&gt; on StackOverflow summed it up pretty well when they wrote - &lt;em&gt;"I implemented this and it works - just feels weird. thanks"&lt;/em&gt;  😂&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, if this all sounds like a headache, you might consider &lt;a href="https://ably.com" rel="noopener noreferrer"&gt;Ably&lt;/a&gt;. Apart from solving the authentication problem, Ably provides additional features you’d need to implement on top of WebSockets like &lt;a href="https://hubs.la/Q02cBLSw0" rel="noopener noreferrer"&gt;Presence&lt;/a&gt; and &lt;a href="https://hubs.la/Q02cBM6L0" rel="noopener noreferrer"&gt;message queues&lt;/a&gt;, and provides production guarantees that will be time-consuming or costly to achieve on your own like 99.999% uptime guarantee, exactly-once delivery, and guaranteed message ordering.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>learning</category>
      <category>security</category>
    </item>
    <item>
      <title>What is WebTransport and can it replace WebSockets?</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Mon, 27 Nov 2023 11:03:18 +0000</pubDate>
      <link>https://dev.to/ably/what-is-webtransport-and-can-it-replace-websockets-2io0</link>
      <guid>https://dev.to/ably/what-is-webtransport-and-can-it-replace-websockets-2io0</guid>
      <description>&lt;p&gt;&lt;a href="https://www.w3.org/TR/webtransport/" rel="noopener noreferrer"&gt;WebTransport&lt;/a&gt; is a new specification that could offer an &lt;a href="https://ably.com/topic/websocket-alternatives" rel="noopener noreferrer"&gt;alternative to WebSockets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For applications that need low latency, event-driven communication between endpoints, WebSockets has been the go-to choice, but WebTransport may change that.&lt;/p&gt;

&lt;p&gt;In this article, you’ll learn what WebTransport offers and how it differs from WebSockets. You’ll also learn when—and if—you should make the switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebSockets vs WebTransport, at a glance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hubs.la/Q029qgRb0" rel="noopener noreferrer"&gt;WebSockets&lt;/a&gt; is a technology that enables bidirectional, full-duplex communication between client and server over a persistent, single-socket connection. This allows for low-latency, realtime updates, and the creation of richer communication and gaming applications. Previously, the web was dependent on &lt;a href="https://hubs.la/Q029qh830" rel="noopener noreferrer"&gt;requests and responses&lt;/a&gt;, which aren’t dynamic enough for those kinds of apps.&lt;/p&gt;

&lt;p&gt;The newer WebTransport offers secure, multiplexed, realtime transport and already has APIs for sending data both reliably and unreliably.&lt;/p&gt;

&lt;p&gt;In a reliable data transfer, the sender is notified of the success or failure of the data transmission, and failed transmissions are usually resent until they succeed, after which the next data packet is sent. In unreliable transfer, there’s no confirmation of transmission success, and packets that aren’t received simply don’t get delivered. Unreliable transfer is often used for things like streaming videos, where speed is a concern, and minor data loss, such as a few frames of video, is acceptable. Because WebTransport uses both of these methods, there are many use cases for it, such as bidirectional data streaming for multiplayer gaming, interactive live streams, and data transfer for sensors and internet of things devices.&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%2F8ujoaedrno1hdr2et8ap.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%2F8ujoaedrno1hdr2et8ap.png" alt="WebTransport sends multiple streams within a single connection" width="800" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While WebSockets creates a single stream per connection, WebTransport allows you to create multiple streams over a single connection. It avoids the head-of-line blocking delays that WebSockets suffers from, and is less resource intensive when creating connections. While WebSockets starts as a HTTP/1.1 protocol, WebTransport works on top of several different protocols, including some that WebSockets don't support.&lt;/p&gt;

&lt;p&gt;It works with &lt;a href="https://hubs.la/Q029qhCl0" rel="noopener noreferrer"&gt;HTTP/3&lt;/a&gt;, the upcoming version of the transport protocol used by the World Wide Web. HTTP/3 uses the &lt;a href="https://www.chromium.org/quic/" rel="noopener noreferrer"&gt;QUIC protocol&lt;/a&gt; for transport layer data exchange, which has several advantages. QUIC can prevent &lt;a href="https://en.wikipedia.org/wiki/Head-of-line_blocking" rel="noopener noreferrer"&gt;head-of-line blocking delays&lt;/a&gt;, improving network performance in many situations. This is a limitation of WebSockets.&lt;/p&gt;

&lt;p&gt;With WebTransport, you can also use features like &lt;a href="https://en.wikipedia.org/wiki/Promise_theory" rel="noopener noreferrer"&gt;promises&lt;/a&gt; and the &lt;a href="https://hubs.la/Q029qjs90" rel="noopener noreferrer"&gt;await keyword for asynchronous functions&lt;/a&gt;. The API runs in Web Workers, too, enabling multithreading.&lt;/p&gt;

&lt;p&gt;Here’s an example of an async function from the &lt;a href="https://www.w3.org/TR/webtransport/#example-sending-stream" rel="noopener noreferrer"&gt;WebTransport Working Group’s documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebTransport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createUnidirectionalStream&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;writable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWriter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The WebTransport working group is still hammering out the details of the specification, so more features and capabilities may come.&lt;/p&gt;

&lt;h2&gt;
  
  
  4 WebTransport use cases
&lt;/h2&gt;

&lt;p&gt;There are many potential use cases for WebTransport. Let’s look at four where it shines.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Encrypted streaming
&lt;/h3&gt;

&lt;p&gt;WebTransport’s streams API allows you to create connections for sending ordered data. As WebTransport uses the QUIC protocol, these connections are &lt;a href="https://web.dev/webtransport/" rel="noopener noreferrer"&gt;less resource intensive to open and close&lt;/a&gt; than with TCP used by WebSockets.&lt;/p&gt;

&lt;p&gt;You can also do things like offer streaming media more securely. WebTransport has several &lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#page-10" rel="noopener noreferrer"&gt;security measures&lt;/a&gt; in place, such as requiring use of the Origin header as well as specific opt-in via a transport parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Communication
&lt;/h3&gt;

&lt;p&gt;WebTransport can send &lt;a href="https://blog.bitsrc.io/will-webtransport-replace-webrtc-in-near-future-436c4f7f3484" rel="noopener noreferrer"&gt;multiple types of data&lt;/a&gt; over the same connection. That means you can send and receive video information unreliably while sending text or file data reliably.&lt;/p&gt;

&lt;p&gt;This feature allows you to do more with each connection, leading to richer communication between a greater number of simultaneous users. It means that you can send different types of content on different channels, so something like a large image could be sent over a different connection than other data, which would mean that the image can’t &lt;a href="https://www.youtube.com/watch?v=jvdg-jOYK5E" rel="noopener noreferrer"&gt;block chat communication&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also create bidirectional streams that allow either the server or client to initiate communication, so if you’re implementing a messaging system, data exchange can happen quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Multiplayer gameplay
&lt;/h3&gt;

&lt;p&gt;WebTransport works with the &lt;a href="https://hubs.la/Q029qjLF0" rel="noopener noreferrer"&gt;HTTP/2&lt;/a&gt;, HTTP/3, and QUIC protocols. It can receive data out of order over HTTP and can request data itself or listen to data that is pushed by the server. It can do this both reliably and unreliably.&lt;/p&gt;

&lt;p&gt;With WebTransport’s bidirectional streams, data pushed by the server has very low latency, which is a big advantage for game development. It means less delay between user input and the response, which can be critical in action games, such as shooting, driving, or fighting.&lt;/p&gt;

&lt;p&gt;It can also improve response times for cloud gaming services, where the server handles rendering and gameplay and streams video to a thin client, which passes back user input to the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Sensor data
&lt;/h3&gt;

&lt;p&gt;Many &lt;a href="https://www.oracle.com/internet-of-things/what-is-iot/" rel="noopener noreferrer"&gt;Internet of Things (IOT)&lt;/a&gt; devices log data that needs to be transferred to a server, and a potential use of WebTransport is having a low-latency method for transferring this. IOT devices often send small amounts of data regularly. Consuming fewer resources has advantages for both the battery life of the devices and with regard to network congestion.&lt;/p&gt;

&lt;p&gt;As the IOT grows, the large number of devices in use consuming an ever-increasing number of connections could become a problem, so the more lightweight those devices are, the better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you use WebTransport?
&lt;/h2&gt;

&lt;p&gt;Now let’s look at what specific features and capabilities WebTransport offers. The specification is still at the public draft stage but is fully usable.&lt;/p&gt;

&lt;p&gt;In addition to the QUIC protocol, there’s an API that lets browsers control streams and datagrams. The API is HTTPS only, enforcing security.&lt;/p&gt;

&lt;h3&gt;
  
  
  QUIC
&lt;/h3&gt;

&lt;p&gt;QUIC is a transport protocol designed to replace TCP and improve web performance. It uses UDP, and HTTP/3 is designed to take advantage of it. When using HTTP/3 over QUIC, it’s faster to establish connections, and congestion control feedback is available to help avoid problems. As mentioned previously, QUIC also prevents head-of-line blocking.&lt;/p&gt;

&lt;p&gt;It doesn’t require as much header information, which allows lightweight communication—without the overhead of HTTP. It’s also hardware independent, with support required only at the &lt;a href="https://www.ionos.com/digitalguide/hosting/technical-matters/quic-the-internet-transport-protocol-based-on-udp/" rel="noopener noreferrer"&gt;application level&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;QUIC is already widely supported in browsers and applications. Currently &lt;a href="https://w3techs.com/technologies/details/ce-quic" rel="noopener noreferrer"&gt;7.7% of websites&lt;/a&gt; use it, but the figure is increasing steadily.&lt;/p&gt;

&lt;p&gt;If you want to experiment with QUIC in Chrome and many other Chromium-based browsers, you can &lt;a href="https://ma.ttias.be/enable-quic-protocol-google-chrome/" rel="noopener noreferrer"&gt;activate it in the settings&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Type &lt;code&gt;chrome://flags&lt;/code&gt; in the URL bar, then find the &lt;strong&gt;Experimental QUIC protocol&lt;/strong&gt; setting and enable it, as in the screenshot below. (For Chromium-based browsers, simply replace &lt;code&gt;chrome://flags&lt;/code&gt; with &lt;code&gt;[browser name]://flags&lt;/code&gt;.)&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%2F5rdmv6fjeapdp6768gy1.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%2F5rdmv6fjeapdp6768gy1.png" alt="Chrome settings showing QUIC being enabled" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Other protocols
&lt;/h3&gt;

&lt;p&gt;WebTransport can also run on top of either HTTP/2 or HTTP/3 without QUIC. It can also use HTTP/2 as a fallback, allowing you to take advantage of the benefits of WebTransport, regardless of the network.&lt;/p&gt;

&lt;p&gt;HTTP/2 compatibility could potentially let you run it on older setups that don’t support HTTP/3 and lets you take advantage of features like resource prioritization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Datagrams
&lt;/h3&gt;

&lt;p&gt;WebTransport works primarily with datagrams and streams. A datagram is a self-contained packet of data that can arrive in any particular order. Datagrams are sent unreliably. Unreliable data can be sent at high speed, and the connection can cope if some data is dropped. The WebTransport specification allows you to limit the datagram size using the &lt;code&gt;maxDatagramSize&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;WebTransport objects have a datagrams object that can be accessed via its readable and writable attributes. Datagrams can be queued, and you can also create promises to wait for datagram transmission.&lt;/p&gt;

&lt;p&gt;The WebTransport objects also include state data, indicating whether they are connecting, connected, closed, or failed. They also include promises indicating if the object is ready or closed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streams
&lt;/h3&gt;

&lt;p&gt;Streams allow you to send ordered, reliable data. You can establish a stream and have the server push this content to the client. It allows low-latency, real-time communication.&lt;/p&gt;

&lt;p&gt;In the API, WebTransport objects have slots for further objects representing different types of stream. These include SendStream, ReceiveStream, and Bidirectional Stream objects, all defined in the &lt;a href="https://www.w3.org/TR/webtransport" rel="noopener noreferrer"&gt;working draft&lt;/a&gt; of the specification.&lt;/p&gt;

&lt;p&gt;WebTransport also allows multistream data to be sent in a partially reliable manner. Sent like this, data is not guaranteed to arrive in the right order overall but is ordered by the status of other related streams. This works well when sending video frames.&lt;/p&gt;

&lt;p&gt;With streams, you can also pipe received data into a handler, such as a TextDecoderStream that converts it into a format readily usable by your application.&lt;/p&gt;

&lt;h4&gt;
  
  
  SendStream
&lt;/h4&gt;

&lt;p&gt;SendStream objects are a kind of &lt;a href="https://streams.spec.whatwg.org/#writablestream" rel="noopener noreferrer"&gt;WritableStream&lt;/a&gt; used for outgoing data. They store stream objects, but a SendStream object also contains a promise, which defines an action it will take, such as being closed or aborted.&lt;/p&gt;

&lt;p&gt;SendStream objects also include a slot to attach them to the HTTP/3 transport layer.&lt;/p&gt;

&lt;h4&gt;
  
  
  ReceiveStream
&lt;/h4&gt;

&lt;p&gt;ReceiveStream objects are a kind of &lt;a href="https://streams.spec.whatwg.org/#readablestream" rel="noopener noreferrer"&gt;ReadableStream&lt;/a&gt; that handles incoming data.&lt;/p&gt;

&lt;p&gt;The ReceiveStream object is structured similarly to the SendStream objects, with a stream slot and transport slot. It doesn’t, however, include promises.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bidirectional stream
&lt;/h4&gt;

&lt;p&gt;The bidirectional stream combines the send and receive streams into a single object, which allows you to manage two-way communication in one place. Objects have a readable and writable property, corresponding to the ReceiveStream and SendStream objects discussed above.&lt;/p&gt;

&lt;p&gt;These properties let them work with data sent in either direction. You read the incoming stream and write to the outgoing stream.&lt;/p&gt;

&lt;h4&gt;
  
  
  Unidirectional streams
&lt;/h4&gt;

&lt;p&gt;You can also create unidirectional streams. IncomingUnidirectionalStreams are ReadableStreams consisting of multiple ReceiveStreams. You can call &lt;code&gt;createUnidirectionalStream()&lt;/code&gt; to create an outgoing equivalent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Could WebTransport replace WebSockets?
&lt;/h2&gt;

&lt;p&gt;The WebTransport specification is still in flux, but its future looks bright. As it continues to take shape, we’re sure to see more developers adopt it and utilize the new features it brings.&lt;/p&gt;

&lt;p&gt;Early adopters stand to reap big rewards but also take the biggest risks as it isn’t clear how fully the specification will deliver on its promises. We don’t know how well browsers will support it, how likely developers are to adopt it, or how it will evolve.&lt;/p&gt;

&lt;p&gt;WebTransport has advantages in some use cases, such as low-latency scenarios and reusing connections for multiple purposes. It isn’t a silver bullet, though, and there are scenarios where WebSockets will still be the best choice—or at least a perfectly good one.&lt;/p&gt;

&lt;p&gt;As a new specification, WebTransport is still nascent, so it will be harder to get working until other tools are updated to work with it. For now, that means keeping eyes and minds open and being ready to adapt.&lt;/p&gt;

&lt;p&gt;WebSockets still offer a great deal of functionality, and as an established standard, are fully supported by all modern browsers. WebTransport should offer modest performance improvements and incremental gains, but will also likely create new engineering challenges that will have to be addressed to build production-ready systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Ably support WebTransport?
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://hubs.la/Q029qkfD0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; where we offer a realtime communication API used by developers to build experiences like &lt;a href="https://hubs.la/Q029qkpx0" rel="noopener noreferrer"&gt;chat&lt;/a&gt;, &lt;a href="https://hubs.la/Q029qkGG0" rel="noopener noreferrer"&gt;multiplayer collaboration&lt;/a&gt;, and other live updates in your web and mobile applications at scale. Under the hood, we build on lower-level communication protocols to enable features like online presence, message history, queues, and more. Most developers use Ably with WebSocket transport, but we also support HTTP, SSE, and MQTT. Our community sometimes wonders - &lt;a href="https://hubs.la/Q029qkHP0" rel="noopener noreferrer"&gt;is Ably currently exploring WebTransport&lt;/a&gt;? And the answer is yes, but  until the future of WebTransport is more certain, we’re focused on WebSockets.&lt;/p&gt;

&lt;p&gt;If you are looking to implement reliable realtime messaging in your application, I encourage you to &lt;a href="https://hubs.la/Q029qk-M0" rel="noopener noreferrer"&gt;give Ably a try&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.w3.org/TR/webtransport/" rel="noopener noreferrer"&gt;WebTransport&lt;/a&gt; is already a viable &lt;a href="https://hubs.la/Q029qlvN0" rel="noopener noreferrer"&gt;alternative to WebSockets&lt;/a&gt;, with several advantages. The lack of head of line blocking, slightly lower latency, and versatility of the multi-protocol approach offer benefits in many scenarios.&lt;/p&gt;

&lt;p&gt;As the technology is not fully defined yet, the tooling is less developed, and will be for a while. On the other hand, the API is usable, and the opportunity is there to be the quickest to market with products that benefit from its strengths.&lt;/p&gt;

&lt;p&gt;It’s too early to predict &lt;a href="https://github.com/quicwg/datagram/issues/68" rel="noopener noreferrer"&gt;what WebTransport will be used for&lt;/a&gt;, but the first teams to use it have every chance to build something groundbreaking.&lt;/p&gt;

&lt;p&gt;Though there’s no urgent need to switch, developers should keep an eye on it for future projects and be ready to use it if it’s the best fit.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The complete guide to WebSockets with React</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Tue, 31 Oct 2023 12:04:12 +0000</pubDate>
      <link>https://dev.to/ably/the-complete-guide-to-websockets-with-react-5d0m</link>
      <guid>https://dev.to/ably/the-complete-guide-to-websockets-with-react-5d0m</guid>
      <description>&lt;p&gt;In this post, you’ll learn more than you ever thought you needed to know about WebSockets with React, including how to build a smooth realtime cursor experience from scratch:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/RATHiI8iNuk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Jump into the tutorial which uses React on the front and Node on the back or start with some general guidance when it comes to WebSockets with React below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are WebSockets?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hubs.la/Q027b6ch0" rel="noopener noreferrer"&gt;WebSockets&lt;/a&gt; are a communication protocol that enable bidirectional communication between applications.&lt;/p&gt;

&lt;p&gt;They are a great choice when two-way communication is needed such as chat and multiplayer collaboration.&lt;/p&gt;

&lt;p&gt;Additionally, WebSockets are well-suited when you need to push fresh data from the server as soon as it’s available - like &lt;a href="https://hubs.la/Q027b6jw0" rel="noopener noreferrer"&gt;live sports score updates&lt;/a&gt;, &lt;a href="https://hubs.la/Q027b6DP0" rel="noopener noreferrer"&gt;updates on a package delivery&lt;/a&gt;, or perhaps &lt;a href="https://hubs.la/Q027b6Kb0" rel="noopener noreferrer"&gt;realtime chart data&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because they are &lt;a href="https://en.wikipedia.org/wiki/Duplex_(telecommunications)" rel="noopener noreferrer"&gt;full-duplex&lt;/a&gt;, information can flow in both directions simultaneously, making WebSockets an attractive option for high throughput scenarios like an online multiplayer game as well.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wondering how WebSockets compare to alternative realtime transports like Server-Sent Events (EventSource)? I wrote all about that &lt;a href="https://hubs.la/Q027b78B0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How WebSockets work
&lt;/h2&gt;

&lt;p&gt;Unlike &lt;a href="https://hubs.la/Q027b7pW0" rel="noopener noreferrer"&gt;HTTP&lt;/a&gt; where requests are short-lived, WebSockets enable realtime communication using a long-lived stateful connection.&lt;/p&gt;

&lt;p&gt;Once the connection is established, it remains open until either side closes the connection.&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%2Fylj70ijpxvgtkerix9vl.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%2Fylj70ijpxvgtkerix9vl.png" alt="How WebSockets work" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because these connections are long-lived you don’t want to open more than necessary as not to cause memory problems. And since one WebSocket connection has plenty of bandwidth, it’s common practice to use one connection for all your messages (a technique called &lt;a href="https://www.kianmusser.com/articles/websocket-multiplexing/" rel="noopener noreferrer"&gt;multiplexing&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This leads to some interesting questions like “where should I put my connection in React?” and “how do I clean up the connection?”, both of which I will answer in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebSockets and React
&lt;/h2&gt;

&lt;p&gt;WebSockets have a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket" rel="noopener noreferrer"&gt;Web API&lt;/a&gt; accessible in all major web browsers, and since React is “just JavaScript” you can access it without any additional modules or React-specific code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Connection opened&lt;/span&gt;
&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connection established&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Listen for messages&lt;/span&gt;
&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Message from server &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no specific React library needed to get started with WebSockets, however, you might benefit from one.&lt;/p&gt;

&lt;p&gt;The simple and minimal WebSocket API gives you flexibility, but that also means additional work to arrive at a production-ready WebSocket solution.&lt;/p&gt;

&lt;p&gt;When you use the WebSocket API directly, here are just some of the things you should be prepared to code yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication and authorization.&lt;/li&gt;
&lt;li&gt;Robust disconnect detection by implementing a heartbeat.&lt;/li&gt;
&lt;li&gt;Seamless automatic reconnection.&lt;/li&gt;
&lt;li&gt;Recovering missed messages the user missed while temporarily disconnected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of reinventing the wheel, it’s usually more productive to use a general WebSocket library that provides the features listed above out of the box - this allows you to focus on building features unique to your application instead of generic realtime messaging code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best React WebSocket libraries
&lt;/h2&gt;

&lt;p&gt;Before we look at some WebSocket libraries, it’s helpful to distinguish React and JavaScript in this context.&lt;/p&gt;

&lt;p&gt;Since React is “just JavaScript”, you might not necessarily need a React-specific WebSocket library.&lt;/p&gt;

&lt;p&gt;That being said, it would definitely be a plus if the WebSocket library had idiomatic React functions available such as ready-made React hooks!&lt;/p&gt;

&lt;p&gt;There’s a bunch of WebSocket libraries out there (many of them outdated) so we previously wrote a post showing you only &lt;a href="https://hubs.la/Q027b8960" rel="noopener noreferrer"&gt;the best WebSocket libraries for React&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s the summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React useWebSocket:&lt;/strong&gt; A thin layer on top of the WebSocket API that features automatic reconnection and a fallback to &lt;a href="https://hubs.la/Q027b8ch0" rel="noopener noreferrer"&gt;Server-Sent Events&lt;/a&gt; (as long as you’ve coded support on your server). This library is specifically made for React, so it’s very natural to utilise the &lt;code&gt;useWebSocket&lt;/code&gt; hook and all its options. The downside is that &lt;code&gt;useWebSocket&lt;/code&gt; might not have all the features and reliability guarantees you need in production. &lt;a href="https://hubs.la/Q027b8C40" rel="noopener noreferrer"&gt;Learn more&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Socket.IO:&lt;/strong&gt; A JavaScript realtime messaging library based on WebSockets with an optional fallback to HTTP long polling in case the &lt;a href="https://hubs.la/Q0272lqv0" rel="noopener noreferrer"&gt;WebSocket connection can’t be established&lt;/a&gt;. Socket.IO has more features than useWebSocket, but it’s not specific to React, and there’s still work to do to ensure good performance and reliability in production. &lt;a href="https://hubs.la/Q027b8H-0" rel="noopener noreferrer"&gt;Learn more&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;React useWebSocket with Socket.IO:&lt;/strong&gt; useWebSocket actually works with Socket.IO, meaning you might be able to use them together in your React project. I haven’t tested this extensively, but it looks promising!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ably:&lt;/strong&gt; A realtime infrastructure platform featuring &lt;a href="https://hubs.la/Q027b8Px0" rel="noopener noreferrer"&gt;first-class React client support&lt;/a&gt;. With useWebSocket or Socket.IO, you need to host your own WebSocket server. That sounds simple enough, but it’s actually a big burden to &lt;a href="https://hubs.la/Q027b8SX0" rel="noopener noreferrer"&gt;manage your own WebSocket backend&lt;/a&gt;. With Ably, you create an account, and all the messages route through the Ably global infrastructure with the lowest possible latency. Instead of worrying about uptime or if your messages will be delivered exactly-once and in the correct order, you can just plug into the React hook and focus on building the features that actually matter to your users. &lt;a href="https://hubs.la/Q027b8Xd0" rel="noopener noreferrer"&gt;Learn more&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  React WebSocket best practices
&lt;/h2&gt;

&lt;p&gt;When using WebSockets with React, they become an important channel for new information to flow. Ideally, any component that needs to should be able to send and receive data using the shared connection.&lt;/p&gt;

&lt;p&gt;At the same time, you don’t want to create a mess by &lt;a href="https://scrimba.com/articles/react-context-api/" rel="noopener noreferrer"&gt;prop drilling&lt;/a&gt;, or introduce brittleness by making the WebSocket accessible to every component. When you do that, you violate &lt;a href="https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)" rel="noopener noreferrer"&gt;encapsulation&lt;/a&gt;, meaning any component can alter the WebSocket object, potentially causing unexpected behavior elsewhere in the code.&lt;/p&gt;

&lt;p&gt;When I was learning about WebSockets in React, this caused me a bit of anxiety! I went looking for a definitive best practice but, as it happens, there isn’t a universal “right” answer. It depends on what you’re building and the specific shape of your application.&lt;/p&gt;

&lt;p&gt;In the next section, I’ll share what I wish I had - a tour of the options to structure your WebSocket React code, with some considerations for and against (plus, some best practices).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do I put the WebSocket in React?
&lt;/h2&gt;

&lt;p&gt;Should you put the connection in a component, use context, create a hook, or something else entirely?&lt;/p&gt;

&lt;p&gt;Here’s what you need to know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use the useWebSocket hook
&lt;/h3&gt;

&lt;p&gt;While there are advantages to managing the connection yourself, for most of us, all of our “WebSockets-with-React” answers are but an &lt;code&gt;npm install&lt;/code&gt; away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/robtaussig/react-use-websocket" rel="noopener noreferrer"&gt;useWebSocket&lt;/a&gt; is an open source module with 1.2K stars (so you know it’s popular), and it provides a well-thought-out hook to establish a WebSocket connection and manage the connection lifecycle.&lt;/p&gt;

&lt;p&gt;Here is an example of it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useWebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReadyState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-use-websocket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://127.0.0.1:800&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;WS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;share&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;shouldReconnect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Run when the connection state (readyState) changes&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connection state changed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ReadyState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;subscribe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;general-chatroom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="c1"&gt;// Run when a new WebSocket message is received (lastJsonMessage)&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Got a new message: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Chat&lt;/span&gt; &lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apart from providing an idiomatic abstraction, useWebSocket also has some handy options like &lt;code&gt;share&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When set to &lt;code&gt;true&lt;/code&gt;, useWebSocket will share the connection to the same endpoint no matter where you call the hook.&lt;/p&gt;

&lt;p&gt;This makes it straightforward to reuse the connection from different instances of the component and generally access the WebSocket anywhere you need.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note - there is no need to cleanup the WebSocket connection since useWebsocket does that for you when the component unmounts. Very handy!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  In the top-level component
&lt;/h3&gt;

&lt;p&gt;In the previous section, we looked at how a third-party module called useWebSocket can help.&lt;/p&gt;

&lt;p&gt;In case you don’t want to depend on a library, next let's explore how to manage the WebSocket instance directly with idiomatic React code.&lt;/p&gt;

&lt;p&gt;For simple applications, it’s perfectly appropriate to open the WebSocket connection directly in the top-level component and utilize &lt;code&gt;useRef&lt;/code&gt; (&lt;code&gt;useMemo&lt;/code&gt; will also work) to hold on to the connection instance between renders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://127.0.0.1:800&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Connection opened&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Connection established&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// Listen for messages&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Message from server &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Chat&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a best practice, subscribe to the WebSocket events from the top-level component, update top-level state in response to new events, and pass the state down as a prop.&lt;/p&gt;

&lt;p&gt;While it might be tempting to pass the WebSocket object down as a prop so you can attach event handlers or call send directly, that could become hairy compared to managing your WebSocket connection in one place.&lt;/p&gt;

&lt;p&gt;In the likely event a child component needs to send data, you can &lt;a href="https://react.dev/learn/sharing-state-between-components" rel="noopener noreferrer"&gt;pass a callback function&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context API
&lt;/h2&gt;

&lt;p&gt;If you have a lot of components in your component hierarchy and many of them need access to the WebSocket, passing props from the top-level component can become really unwieldy really quickly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://react.dev/reference/react/useContext" rel="noopener noreferrer"&gt;Context&lt;/a&gt; is a React feature specifically invented to solve this problem.&lt;/p&gt;

&lt;p&gt;It provides a way to share data through the component tree without having to pass props down manually from parent to child component and is perfect for state that’s considered “global” in the application, such as the current authenticated user, theme, preferred language, or - you guessed it - a WebSocket connection.&lt;/p&gt;

&lt;p&gt;Here is an example of a WebSocket Context provider, borrowed from Kian Musser who wrote a very good &lt;a href="https://www.kianmusser.com/articles/react-where-put-websocket/#:~:text=Putting%20the%20Websocket%20in%20a%20hook%20works%20best%20when%20there,not%20be%20a%20good%20fit." rel="noopener noreferrer"&gt;post&lt;/a&gt; on the subject:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WebsocketContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="c1"&gt;//                                            ready, value, send&lt;/span&gt;

&lt;span class="c1"&gt;// Make sure to put WebsocketProvider higher up in&lt;/span&gt;
&lt;span class="c1"&gt;// the component tree than any consumer.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WebsocketProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsReady&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setVal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://echo.websocket.events/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WebsocketContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/WebsocketContext.Provider&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a consumer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Very similar to the WsHook component above.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WsConsumer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WebsocketContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// use it just like a hook&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// make sure to include send in dependency array&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt; &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  In a custom hook
&lt;/h3&gt;

&lt;p&gt;Hooks in React are what you make of them.&lt;/p&gt;

&lt;p&gt;At a minimum, a custom WebSocket hook would provide an idiomatic way to abstract your WebSocket-related code in a single place.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, WebSocket is a simple and minimal API and there’s always more work to do on top to achieve a minimum-viable production implementation.&lt;/p&gt;

&lt;p&gt;For example, you’ll need a way to detect disconnections and automatically reconnect. Additionally, you may want to associate some state with the connection, like a list of users currently connected (presence).&lt;/p&gt;

&lt;p&gt;It would make a lot of sense to abstract that code away in a hook instead of convoluting your components and risking messy code duplication.&lt;/p&gt;

&lt;p&gt;Here’s an example from &lt;a href="https://www.kianmusser.com/articles/react-where-put-websocket" rel="noopener noreferrer"&gt;Kian's post&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useWs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsReady&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setVal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIsReady&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setVal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="c1"&gt;// bind is needed to make sure `send` references correct `this`&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isReady&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WsComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://echo.websocket.events/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;// make sure to include send in dependency array&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt; &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While hooks are an idiomatic and clean way to share logic between components, they don’t solve the problem where you need to access the WebSocket connection throughout your application. You’d still need to lean on props (or Context) for that.&lt;/p&gt;

&lt;p&gt;Alternatively, you could take a page from useWebSocket and implement the &lt;a href="https://en.wikipedia.org/wiki/Singleton_pattern" rel="noopener noreferrer"&gt;singleton pattern&lt;/a&gt;, such that the hook only maintains a single connection, regardless of how many times it’s instantiated.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use WebSockets with React and Node
&lt;/h2&gt;

&lt;p&gt;We’ve covered the theory behind WebSockets. The aim now is to bring it all together by showing you concrete examples of the fundamentals in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you’ll build
&lt;/h3&gt;

&lt;p&gt;In this tutorial, you will learn how to build a live cursors experience with WebSockets and React:&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%2F0jnxiqr2pe5or07mbpwx.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%2F0jnxiqr2pe5or07mbpwx.png" alt="What you’ll build" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open a WebSocket connection using useWebSocket.&lt;/li&gt;
&lt;li&gt;Send and receive messages.&lt;/li&gt;
&lt;li&gt;Implement a “who’s online” feature (presence).&lt;/li&gt;
&lt;li&gt;Broadcast messages to every connected client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember, the fundamental ideas in this post can be adapted to work for chat, online updates, or even a full-blown realtime collaborative experience.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you want to jump ahead and grab the code, please go ahead!  It's all on &lt;a href="https://github.com/ably-labs/react-websockets-tutorial" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Maybe leave a star on the way by?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the WebSocket server in Node
&lt;/h3&gt;

&lt;p&gt;Every WebSocket application has two components - the server and the client.&lt;/p&gt;

&lt;p&gt;Since the client won’t be able to do much until it has a server to connect to, let’s build the server first!&lt;/p&gt;

&lt;p&gt;You can implement a WebSocket server in almost any programming language but since React uses JavaScript, we will use Node and the &lt;a href="https://www.npmjs.com/package/ws" rel="noopener noreferrer"&gt;ws&lt;/a&gt; module.&lt;/p&gt;

&lt;p&gt;Start by creating a new folder called react-websockets-tutorial and a subfolder called server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir react-websockets-tutorial
cd react-websockets-tutorial
mkdir server
cd server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Making sure you’re inside the server subfolder, run &lt;code&gt;npm init&lt;/code&gt; followed by &lt;code&gt;npm install&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init -y
npm install –-save ws uuid 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npm init&lt;/code&gt; creates a package.json file while npm install ws uuid download the modules we’ll need to reference from the code.&lt;/p&gt;

&lt;p&gt;After that, create index.js and paste the whole server code. I’ll explain it all below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebSocketServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;v4&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocketServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;
  &lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; updated their updated state: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; disconnected`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;broadcast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;wsServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; connected`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`WebSocket server is running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations! 🎉 If you now run &lt;code&gt;node index.js&lt;/code&gt;, you will be running a fully-functioning WebSocket server:&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%2Fje8r62i9kwgt6r15pijb.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%2Fje8r62i9kwgt6r15pijb.png" alt="fully-functioning WebSocket server" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s not that useful to run the server without a client and, of course, you’re here to understand the code (not just paste it!), but I think it’s worth pointing out how quickly we can get up and running.&lt;/p&gt;

&lt;p&gt;Let’s break down the code from &lt;strong&gt;bottom to top&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;First, we create a HTTP server and a WebSocket server before listening for incoming connections:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Normally these constants live at the top of the file but I pasted them here for context :)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocketServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`WebSocket server is running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though WebSocket is a separate protocol from HTTP, the WebSocket upgrade handshake happens over HTTP, meaning we need both.&lt;/p&gt;

&lt;p&gt;Next, we add an event handler for incoming connections.&lt;/p&gt;

&lt;p&gt;Here, things get much more interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;wsServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;username&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handleClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the client code, we will eventually connect to the server using a URL like &lt;code&gt;"ws://localhost:8080?username=Alex"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Step one is to extract the &lt;code&gt;{ username }&lt;/code&gt; from the query string so we know who is connecting.&lt;/p&gt;

&lt;p&gt;Next, we generate a universally unique identifier (UUID) by which to reference the user.&lt;/p&gt;

&lt;p&gt;We use a UUID as a key instead of the username in case two users with the same username connect.&lt;/p&gt;

&lt;p&gt;So far, so good!&lt;/p&gt;

&lt;p&gt;The next two lines require a bit more explanation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; connections[uuid] = connection
 users[uuid] = {
   username,
   state: { }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we add the &lt;code&gt;connection&lt;/code&gt; to the &lt;code&gt;connections&lt;/code&gt; dictionary.&lt;/p&gt;

&lt;p&gt;This will be handy when we want to send a message to all connected clients since we can now loop over &lt;code&gt;connections&lt;/code&gt; and call &lt;code&gt;connection.send&lt;/code&gt; on each.&lt;/p&gt;

&lt;p&gt;Next, we create and add a &lt;code&gt;user&lt;/code&gt; to the &lt;code&gt;users&lt;/code&gt; dictionary. Here, we associate information with the user, such as their username.&lt;/p&gt;

&lt;p&gt;Additionally, we initialize an empty &lt;code&gt;state&lt;/code&gt; object which we will later populate with information about the user’s status or attributes.&lt;/p&gt;

&lt;p&gt;For the realtime cursor experience we’re building, we will eventually update the user’s state with their cursor position, but this pattern I call &lt;a href="https://hubs.la/Q027b97B0" rel="noopener noreferrer"&gt;presence&lt;/a&gt; can be adapted to associate any information with the user.&lt;/p&gt;

&lt;p&gt;For example, if you are building a chat application, you could add a &lt;code&gt;typingStatus&lt;/code&gt; and &lt;code&gt;onlineStatus&lt;/code&gt; (personally, I look forward to being &lt;code&gt;"out for lunch 🍔"&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;You might be wondering - why create both a &lt;code&gt;connections&lt;/code&gt; and a &lt;code&gt;users&lt;/code&gt; dictionary?&lt;/p&gt;

&lt;p&gt;We &lt;em&gt;could&lt;/em&gt; maintain just a &lt;code&gt;connections&lt;/code&gt; dictionary then associate the user with the connection.&lt;/p&gt;

&lt;p&gt;On the surface, it looks simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;connection.user = { 
  username,
  state: { } 
}
connections[uuid] = connection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is valid, but I see connection state and user presence information as discrete concepts and choose to represent them that way in code.&lt;/p&gt;

&lt;p&gt;From a practical perspective, in a moment, we will &lt;code&gt;JSON.stringify&lt;/code&gt; (serialize) and broadcast the &lt;code&gt;users&lt;/code&gt; object to all connected clients. If we tried to serialize the &lt;code&gt;connections&lt;/code&gt; object, we’d get a really unfocused string with metadata about the ws connection.&lt;/p&gt;

&lt;p&gt;Speaking of the &lt;code&gt;broadcast&lt;/code&gt; function, here it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;broadcast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;broadcast&lt;/code&gt;  enumerates the &lt;code&gt;connection&lt;/code&gt; dictionary and sends each client an up-to-date view of who’s connected and their state.&lt;/p&gt;

&lt;p&gt;With just one call to &lt;code&gt;broadcast&lt;/code&gt;, connected clients will receive a list of who’s connected (making it possible to build a “who’s online list”), as well as state about each user.&lt;/p&gt;

&lt;p&gt;Most importantly - the client will see a snapshot of every user's cursor position so that, in turn, it can render a visual cursor.&lt;/p&gt;

&lt;p&gt;Still working our way up the server index.js file, let’s look at the event handlers next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;
  &lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;handleClose&lt;/code&gt; at the bottom deletes the &lt;code&gt;connection&lt;/code&gt; and &lt;code&gt;user&lt;/code&gt; before broadcasting the users object to all clients (excluding the one that just closed since we deleted it).&lt;/p&gt;

&lt;p&gt;Since the &lt;code&gt;user&lt;/code&gt; will have been deleted from &lt;code&gt;users&lt;/code&gt;, the client will basically get the message that someone disconnected.&lt;/p&gt;

&lt;p&gt;In an ideal world, we might broadcast a named event like &lt;code&gt;“user_gone”&lt;/code&gt;  with information about the disconnected user.&lt;/p&gt;

&lt;p&gt;For the purposes of this tutorial, however, we will leave it up to the client to spot the difference between the previous &lt;code&gt;users&lt;/code&gt; object and the new one to detect who exactly disconnected.&lt;/p&gt;

&lt;p&gt;Note that &lt;code&gt;handleMessage&lt;/code&gt; is run every time a message is received from the client. The only message the server is expecting in this example code is a state update message.&lt;/p&gt;

&lt;p&gt;Whenever the client wants to update the user’s state (for example, the cursor position), it sends a message to the server.&lt;/p&gt;

&lt;p&gt;In this handler, the server overwrites &lt;code&gt;user.state&lt;/code&gt; with whatever message object it received.&lt;/p&gt;

&lt;p&gt;As your application evolves, you will surely want to handle different types of messages and I reccomend you validate inputs to avoid &lt;a href="https://hubs.la/Q0272t7f0" rel="noopener noreferrer"&gt;WebSocket security issues&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the WebSocket server
&lt;/h3&gt;

&lt;p&gt;Even though the server code is short, there’s quite a lot going on.&lt;/p&gt;

&lt;p&gt;In the following video I quickly recap how the server works by sending and subscribing to data using an API platform &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;They make it possible to &lt;a href="https://learning.postman.com/docs/sending-requests/websocket/websocket/" rel="noopener noreferrer"&gt;send and receive messages over a WebSocket connection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By thoroughly understanding the shape of the WebSocket endpoint, it will become a lot easier to understand what needs doing on the client in the next section.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/C35k979BNQU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing the WebSocket client in React with useWebSocket
&lt;/h3&gt;

&lt;p&gt;Now the server is up and running, we can shift our attention to the React client where we’ll solve some interesting challenges around how to structure the WebSocket code, smoothly render the cursors over the network (spoiler - we are going to animate them!), and strike a balance between frequency of cursor updates and network efficiency.&lt;/p&gt;

&lt;p&gt;First things first, &lt;code&gt;cd&lt;/code&gt; back to the project root and scaffold a React application called “client” with vite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ..
npm create vite@latest client --template react
cd client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Making sure you’re inside the &lt;code&gt;./client&lt;/code&gt; subfolder, run &lt;code&gt;npm install&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install react-use-websocket lodash.throttle perfect-cursors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the purposes of a tutorial, it makes sense to install everything up front.&lt;/p&gt;

&lt;p&gt;The function of each module will become clearer as we progress through the tutorial but here’s a quick summary so you know what to expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React-use-websocket:&lt;/strong&gt; A React hook for WebSocket communication (click &lt;a href="https://dev.to/ably/the-complete-guide-to-websockets-with-react-1d13-temp-slug-9838050"&gt;here&lt;/a&gt; and jump up the page if you want more information).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lodash.throttle:&lt;/strong&gt;  Used to invoke a function at most once every X milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perfect-cursors:&lt;/strong&gt; Plots a smooth curve between two or more cursor positions using &lt;a href="https://en.wikipedia.org/wiki/Spline_interpolation" rel="noopener noreferrer"&gt;spline interpolation&lt;/a&gt;. We can then animates the cursor along said curve to prevent the cursor jumping between throttled updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Creating the login screen
&lt;/h4&gt;

&lt;p&gt;When a user opens the app, we first want to identify them by a username:&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%2F0f7u3bsmjo0e48q2dhn3.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%2F0f7u3bsmjo0e48q2dhn3.png" alt="Creating the login screen" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a new file called components/Login.jsx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Login&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUsername&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;What&lt;/span&gt; &lt;span class="nx"&gt;should&lt;/span&gt; &lt;span class="nx"&gt;people&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;
        &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="nf"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
          &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create a placeholder file called Home.jsx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then paste the following in ./index.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/Home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Login&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./components/Login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUsername&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Login&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setUsername&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a reminder, index.is is the entry-point for our clien - it’s the first code that runs, and we’ll use this opportunity to determine which screen to show used based on the application’s state.&lt;/p&gt;

&lt;p&gt;Here, we check if the &lt;code&gt;username&lt;/code&gt; has been set.  If it hasn’t, we return (render) the &lt;code&gt;Login&lt;/code&gt; component. Otherwise, we render the &lt;code&gt;Home&lt;/code&gt; component and pass the &lt;code&gt;username&lt;/code&gt; as a prop.&lt;/p&gt;

&lt;p&gt;In the next step, we’ll use this &lt;code&gt;username&lt;/code&gt; prop to identify the user to the server when establishing the WebSocket connection.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sending cursor positions over WebSockets
&lt;/h4&gt;

&lt;p&gt;When it comes to rendering realtime cursors, we can divide the problem in two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sending the cursor positions.&lt;/li&gt;
&lt;li&gt;Receiving the cursor positions and efficiently rendering a cursor for each user.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this section, we’ll focus on the first part of the problem - connecting to the WebSocket server and sending the cursor position when it moves.&lt;/p&gt;

&lt;p&gt;Let’s start with a code dump before explaining it line by line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;In&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jsx &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;empty&lt;/span&gt; &lt;span class="nx"&gt;placeholder&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="nx"&gt;we&lt;/span&gt; &lt;span class="nx"&gt;defined&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;entire&lt;/span&gt; &lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;following&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useWebSocket&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-use-websocket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;throttle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lodash.throttle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`ws://127.0.0.1:8000`&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendJsonMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;share&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;THROTTLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendJsonMessageThrottled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;THROTTLE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sendJsonMessageThrottled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we import the useWebSocket hook and call it with the WebSocket server’s address - in this case, &lt;code&gt;"ws://127.0.0.1:8000"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tip - make sure the WebSocket server is running in the background and double-check the port in the server the console output matches the one here (8000).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Additionally, we pass a &lt;code&gt;queryParams&lt;/code&gt; option with the &lt;code&gt;username&lt;/code&gt;.  In a nutshell, useWebSocket will take the &lt;code&gt;username&lt;/code&gt; property and append the value to the WebSocket server URL like this &lt;code&gt;"ws://127.0.0.1:8000?username=Alex&lt;/code&gt;".&lt;/p&gt;

&lt;p&gt;If you recall, the server has been coded to extract the &lt;code&gt;username&lt;/code&gt; query parameter to identify the user.&lt;/p&gt;

&lt;p&gt;We also pass &lt;code&gt;share: true&lt;/code&gt; as an option to useWebSocket.&lt;/p&gt;

&lt;p&gt;This isn’t strictly necessary for this project, however, I include to illustrate a key feature of useWebSocket that will probably be of use in your project.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;share&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;, you can call &lt;code&gt;useWebSocket(WS_URL)&lt;/code&gt; anywhere in your React application and - if useWebSocket already has a connection to that specific URL, it will share the connection between instances instead of opening a new one.&lt;/p&gt;

&lt;p&gt;This is true if you create multiple instances of the same component (potentially simplifying the code since you can just call &lt;code&gt;useWebSocket&lt;/code&gt; and not worry about managing the connection yourself), or, if you need to access the connection from a completely separate component (another screen, for example).&lt;/p&gt;

&lt;p&gt;As you can see, setting up the WebSocket connection with useWebSocket is easy enough.&lt;/p&gt;

&lt;p&gt;The unique code in this component lies within the &lt;code&gt;useEffect&lt;/code&gt; hook.&lt;/p&gt;

&lt;p&gt;Here, we call &lt;code&gt;useEffect&lt;/code&gt; with an empty &lt;code&gt;[]&lt;/code&gt;, which effectively says “call me once when the component first mounts only”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;THROTTLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendJsonMessageThrottled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;THROTTLE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sendJsonMessageThrottled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above snippet, we listen to the mouse movement and send the cursor X and Y position to the server.&lt;/p&gt;

&lt;p&gt;In a perfect world, cursor updates would happen with zero latency and arrive at the same rate as the user’s monitor.&lt;/p&gt;

&lt;p&gt;In practice, however, it makes more sense to “throttle” the updates to roughly one every 50-80 milliseconds.&lt;/p&gt;

&lt;p&gt;Why 50-80 milliseconds?&lt;/p&gt;

&lt;p&gt;Even briefly wiggling the cursor on the screen could potentially fire hundreds of events in a few milliseconds (maybe thousands if you have a high mouse sensitivity), and that would put undue strain on the server, especially coming from tens, hundreds, or thousands of users.&lt;/p&gt;

&lt;p&gt;At Ably, we built a tool called &lt;a href="https://ably.com/spaces" rel="noopener noreferrer"&gt;Spaces&lt;/a&gt; to specifically help enable realtime collaborative features like realtime cursors (user presence too). Our thorough user experience testing revealed 50-80 milliseconds is the sweet spot between performance and efficiency.&lt;/p&gt;

&lt;p&gt;This isn't a post about Spaces (I just think realtime cursors are cool) but we apply that learning here by creating a custom function called &lt;code&gt;sendJsonMessageThrottled&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sendJsonMessageThrottled&lt;/code&gt; will only allow the message to be sent maximally once per every 50 milliseconds and it achieves this by using the lodash &lt;code&gt;throttle&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;A key point about &lt;code&gt;sendJsonMessageThrottled&lt;/code&gt; is that we hold a reference to the throttled function using &lt;code&gt;useRef&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If not for &lt;code&gt;useRef&lt;/code&gt; (&lt;code&gt;useCallback&lt;/code&gt; would work as well by the way), we would be calling &lt;code&gt;throttle&lt;/code&gt; every time the renders (this happens many times per second). This isn’t only quite inefficient, but it would break the &lt;code&gt;throttle&lt;/code&gt; function because we would also be starting the internal 50ms timer from scratch every render.&lt;/p&gt;

&lt;p&gt;With the server running in the background, it should now be possible to open the client, “login”, and observe the cursor position being received and logged in the WebSocket server console every 50 milliseconds or so. As you can see, the updates are still plenty frequent even though they're throttled:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/iz4dmgm1iHI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Feel free to open multiple tabs and watch the messages print in the console - it’s quite fun!&lt;/p&gt;

&lt;p&gt;The next step is, of course, to subscribe to the server broadcasts and render the cursors, but herein lies a problem we must address in the next section.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rendering cursors with perfect-cursors
&lt;/h4&gt;

&lt;p&gt;Sending a cursor update every time the cursor twitches isn’t sustainable, so we throttle the update by 50 milliseconds.&lt;/p&gt;

&lt;p&gt;This is frequent enough that the cursor is basically realtime, but it also means the cursor might appear to jump or twitch when rendered due to the 50 millisecond gap.&lt;/p&gt;

&lt;p&gt;Enter perfect-cursors.&lt;/p&gt;

&lt;p&gt;Perfect-cursors is a handy library by &lt;a href="https://twitter.com/steveruizok" rel="noopener noreferrer"&gt;Steve Ruiz&lt;/a&gt;, who is the creator of an online whiteboard called tldraw. I mention this because tldraw actually uses perfect-cursors under the hood, which is a testament to its production-readiness!&lt;/p&gt;

&lt;p&gt;Perfect-cursors is a hook that can blend the cursors between two points.&lt;/p&gt;

&lt;p&gt;Ordinarily, if you move your cursor from &lt;code&gt;[0, 0]&lt;/code&gt; to &lt;code&gt;[0, 500]&lt;/code&gt;, it would appear to jump across the screen.&lt;/p&gt;

&lt;p&gt;With perfect-cursors, it becomes possible to gently animate the cursor from &lt;code&gt;[0, 0]&lt;/code&gt; to &lt;code&gt;[0, 500]&lt;/code&gt; basically giving the illusion that the cursor is updated in true realtime.&lt;/p&gt;

&lt;p&gt;In this section, we’ll quickly scaffold perfect-cursors before going back to Home.jsx to import and use it.&lt;/p&gt;

&lt;p&gt;We already "npm installed" perfect-cursors at the beginning of this section so no need to worry about that!&lt;/p&gt;

&lt;p&gt;Start by creating a file called components/Cursor.jsx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: https://github.com/steveruizok/perfect-cursors&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;usePerfectCursor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../hooks/usePerfectCursor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Cursor&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rCursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animateCursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rCursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="nx"&gt;elm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;`translate(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;px, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;px)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onPointMove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePerfectCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animateCursor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useLayoutEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;onPointMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;onPointMove&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;svg&lt;/span&gt;
      &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rCursor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
        &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;absolute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="nx"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://www.w3.org/2000/svg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 0 35 35&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="nx"&gt;fillRule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;evenodd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rgba(0,0,0,.2)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;translate(1,1)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;white&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m19.751 24.4155-1.844.774-3.1-7.374 1.841-.775z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m13 10.814v11.188l2.969-2.866.428-.139h4.768z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/svg&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a file called hooks/useCursor.jsx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Source: https://github.com/steveruizok/perfect-cursors&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PerfectCursor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;perfect-cursors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;usePerfectCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PerfectCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useLayoutEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onPointChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pc&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;onPointChange&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more information on perfect-cursors, you can check out the &lt;a href="https://github.com/steveruizok/perfect-cursors" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; over on GitHub.&lt;/p&gt;

&lt;p&gt;Now Cursor.jsx is set up, we can reference it from our &lt;code&gt;Home&lt;/code&gt; component.&lt;/p&gt;

&lt;h4&gt;
  
  
  Receiving cursor positions over WebSockets
&lt;/h4&gt;

&lt;p&gt;Okay, time to receive cursor updates from the server and render them!&lt;/p&gt;

&lt;p&gt;Replace the entirety of Home.jsx with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Cursor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./Cursor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useWebSocket&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-use-websocket&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;throttle&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lodash.throttle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderCursors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Cursor&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WS_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`ws://127.0.0.1:8000`&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;share&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;THROTTLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendJsonMessageThrottled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;THROTTLE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;sendJsonMessageThrottled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;renderCursors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we are replacing the entire file, note -  this snippet contains all the same code to &lt;strong&gt;send&lt;/strong&gt; updates that we discussed above. Additionally, we import the &lt;code&gt;Cursor&lt;/code&gt; component, &lt;strong&gt;subscribe&lt;/strong&gt; to updates, and render a cursor for each connected user.&lt;/p&gt;

&lt;p&gt;Let’s break it down.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendJsonMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useWebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;share&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;queryParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Subscribing to realtime updates with useWebSocket is easy enough. We just need to reference &lt;code&gt;lastJsonMessage&lt;/code&gt;, as shown above.&lt;/p&gt;

&lt;p&gt;Every time useWebSocket receives an update, it updates &lt;code&gt;lastJsonMessage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This behaves like a state variable returned by &lt;code&gt;useState&lt;/code&gt;, meaning every time &lt;code&gt;lastJsonMessage&lt;/code&gt; changes, the component will render, allowing us to reference the newest &lt;code&gt;lastJsonMessage&lt;/code&gt; value and execute some rendering logic.&lt;/p&gt;

&lt;p&gt;In the following snippet we check if &lt;code&gt;lastJsonMessage&lt;/code&gt; has been set yet (it won’t be until the first WebSocket update is received), and, if it is, we call &lt;code&gt;renderCursors&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;renderCursors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderCursors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Cursor&lt;/span&gt; 
        &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
        &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;  
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a reminder, every time a user joins, leaves, or updates their cursor position, the server broadcasts a dictionary (object) of users and their state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;renderCursors&lt;/code&gt; takes this object, enumerates it, and returns a &lt;code&gt;&amp;lt;Cursor /&amp;gt;&lt;/code&gt; component for each user, which are then subsequently rendered on the screen.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rendering a “who’s online” list
&lt;/h4&gt;

&lt;p&gt;As a final touch, let’s leverage the list of users to render a crude “who’s online list”.&lt;/p&gt;

&lt;p&gt;With some UI work, it could look something like this but we will keep it simple and render just plaintext as this tutorial draws to a close:&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%2Fw9rca3z2db8h7rbts3tp.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%2Fw9rca3z2db8h7rbts3tp.png" alt="How who's online list could look" width="474" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, open Home.jsx and create a function called &lt;code&gt;renderUsersList&lt;/code&gt;. I suggest pasting it below &lt;code&gt;renderCursors&lt;/code&gt;, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// do not paste&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderCursors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c1"&gt;// do not paste&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// paste this *below* renderCursors&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renderUsersList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;])}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/li&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;})}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the same Home.jsx file, replace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;renderCursors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;renderUsersList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;renderCursors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastJsonMessage&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when you run the application and connect from multiple tabs (or computers), you should see a textual representation of who’s currently connected and their state.&lt;/p&gt;

&lt;p&gt;Note how if you disconnect, the user is removed from the list, making it trivial to build a “who’s online list”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, you learned how to go realtime with React and WebSockets.&lt;/p&gt;

&lt;p&gt;Rather than jump straight into the tutorial, we started with a focus on the fundamentals. This way, no matter what specific shape your application takes, you can feel confident about the best way to manage the WebSocket connection in React.&lt;/p&gt;

&lt;p&gt;Realtime cursors are a fun example, and they do a good job illustrating how WebSockets and React make light work of intensive realtime updates, such as a cursor position, which updates very frequently.&lt;/p&gt;

&lt;p&gt;Don’t forget - the key learnings in this post can be applied to all manner of realtime use cases such as &lt;a href="https://hubs.la/Q027b9wn0" rel="noopener noreferrer"&gt;chat&lt;/a&gt;, &lt;a href="https://hubs.la/Q027b9S80" rel="noopener noreferrer"&gt;notifications&lt;/a&gt;,  and &lt;a href="https://hubs.la/Q027bb4V0" rel="noopener noreferrer"&gt;graphs&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking to add a realtime experience to your React application?
&lt;/h2&gt;

&lt;p&gt;In this post, we built a fullstack JavaScript WebSocket application that works great on localhost or with a small set of users.&lt;/p&gt;

&lt;p&gt;Ensuring WebSocket connections work reliably with low latency and high uptime in demanding production environments is a much harder task, but it doesn’t have to be - you can let &lt;a href="https://hubs.la/Q027b8Xd0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; handle the backend WebSocket infrastructure for you.&lt;/p&gt;

&lt;p&gt;With Ably, WebSocket messages are routed through the Ably global infrastructure to ensure the lowest possible roundtrip latency and the highest percentile of uptime (Ably can legitimately offer a 99.999% uptime SLA).&lt;/p&gt;

&lt;p&gt;The best part?&lt;/p&gt;

&lt;p&gt;The Ably JavaScript SDK makes working with Ably from React just as easy as useWebSocket does, except it also supports, &lt;a href="https://hubs.la/Q027b97B0" rel="noopener noreferrer"&gt;presence&lt;/a&gt; and &lt;a href="https://hubs.la/Q027bc1N0" rel="noopener noreferrer"&gt;history&lt;/a&gt; - and provides quality of service guarantees client-side libraries cannot like guaranteed message ordering and exactly-once semantics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ably&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-channel-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;presenceData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateStatus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePresence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-channel-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;initial state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;updateStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;out for lunch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://hubs.la/Q027bclr0" rel="noopener noreferrer"&gt;Create a free account&lt;/a&gt; (no credit card required) or learn how to send and receive realtime messages and display users' presence status with Ably and React &lt;a href="https://hubs.la/Q027b8Px0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>WebSocket security: 9 common vulnerabilities &amp; prevention methods</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Mon, 25 Sep 2023 13:46:47 +0000</pubDate>
      <link>https://dev.to/ably/websocket-security-9-common-vulnerabilities-prevention-methods-4kil</link>
      <guid>https://dev.to/ably/websocket-security-9-common-vulnerabilities-prevention-methods-4kil</guid>
      <description>&lt;p&gt;The &lt;a href="https://hubs.la/Q022xyj50" rel="noopener noreferrer"&gt;WebSocket&lt;/a&gt; communication protocol is used for a range of purposes, including transmitting sensitive information and running actions with elevated privileges. This makes it an increasingly attractive attack vector for malicious actors.&lt;/p&gt;

&lt;p&gt;The best defense against common WebSocket vulnerabilities is to be aware of them and implement the proper controls to reduce exposure.&lt;/p&gt;

&lt;p&gt;In this post, we’ll look at the most common WebSocket security vulnerabilities and how to prevent them through a combination of modern security approaches and testing tools.&lt;/p&gt;

&lt;p&gt;For each security vulnerability, I'm going to outline: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What it is&lt;/li&gt;
&lt;li&gt;The impact &lt;/li&gt;
&lt;li&gt;And how to prevent against an attack (I'll show you best practices and some tools)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Along the way, I'll also link to in-depth posts that I think you'll find helpful.&lt;/p&gt;

&lt;p&gt;Let's start with a fundamental one a lot of developers get wrong - broken authentication. &lt;/p&gt;

&lt;h2&gt;
  
  
  Broken authentication
&lt;/h2&gt;

&lt;p&gt;WebSockets don’t prescribe any particular way for servers to authenticate clients, putting the responsibility on us, the developers, to devise and implement a mechanism to confirm our user’s identity and securely manage their session.&lt;/p&gt;

&lt;p&gt;The lack of a standard way to authenticate incoming WebSocket connections leaves room to make critical security mistakes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of broken authentication
&lt;/h3&gt;

&lt;p&gt;The impact of a WebSocket authentication vulnerability is severe. Once an attacker has bypassed authentication, they have access to all the data and functionality that the compromised account has.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention of broken authentication
&lt;/h3&gt;

&lt;p&gt;Preventing broken authentication involves authenticating the &lt;a href="https://hubs.la/Q023d8d50" rel="noopener noreferrer"&gt;handshake&lt;/a&gt; before establishing the WebSocket connection, either with a cookie or token&lt;/p&gt;

&lt;p&gt;When using cookie authentication, the browser automatically sends cookies to the server with the WebSocket handshake. Just beware of Cross-Site WebSocket Hijacking (more on that in the section below).&lt;/p&gt;

&lt;p&gt;Token authentication is a bit more tricky.&lt;/p&gt;

&lt;p&gt;Browsers don’t allow arbitrary headers with the WebSocket connection request, meaning you need to find another medium to send the token to the server.&lt;/p&gt;

&lt;p&gt;It can be tempting to send the authentication token in the query string, however, this should be approached with extreme caution. Query strings form part of the URL, which is plaintext and may appear in logs and be used in an attack.&lt;/p&gt;

&lt;p&gt;One possible solution is to use your long-lived authentication token to make a HTTP request for an ephemeral one-time token from your authentication service. Send this one-time token with the WebSocket connection in the query string then discontinue it on the server-side. This way, if the token is logged somehow or shared accidentally, it's no longer usable.&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%2Frhuf4os7zoewwta9dozn.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%2Frhuf4os7zoewwta9dozn.png" alt="websocket-handshake-token-flow-diagram" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you'd like to learn more about how to implement a token-based authentication flow, Senior Software Engineer Jimmie Potts has a &lt;a href="https://nuvalence.io/insights/websocket-token-based-authentication/" rel="noopener noreferrer"&gt;concise post&lt;/a&gt; on the subject.&lt;/p&gt;

&lt;h2&gt;
  
  
  Broken access control
&lt;/h2&gt;

&lt;p&gt;In the previous section, we looked at authentication, which confirms the client is who they say they are. Access control, on the other hand, involves granting or denying access to specific resources or actions based on the authenticated identity.&lt;/p&gt;

&lt;p&gt;Suppose Lord Voldemort authenticates with a password. Surely he shouldn’t be authorized to subscribe to Harry Potter and the gang’s WebSocket channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of broken access control
&lt;/h3&gt;

&lt;p&gt;Broken access control is &lt;a href="https://owasp.org/Top10/A01_2021-Broken_Access_Control/" rel="noopener noreferrer"&gt;the most commonly encountered&lt;/a&gt; and often a critical security vulnerability. It happens when a user can in fact access some resource or perform some action that they are not supposed to be able to access. Failures typically lead to unauthorized information disclosure, modification, or destruction of all data, or performing a business function outside the user's limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention of broken access control
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Unless a resource is intended to be publicly available, deny access by default.&lt;/li&gt;
&lt;li&gt;Explicitly declare the access that is allowed for each resource.&lt;/li&gt;
&lt;li&gt;Thoroughly audit and test access controls to ensure they’re working as designed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the design and management of access control is a complex and dynamic problem, most issues can be solved with the above best practices.&lt;/p&gt;

&lt;p&gt;Many developers rely on obfuscation for WebSocket security, but security through obscurity is not security at all.&lt;/p&gt;

&lt;p&gt;WebSocket URLs are normally tucked away in code and the paths themselves often contain identifiers that seem random and impossible to predict, creating a false sense of security.&lt;/p&gt;

&lt;p&gt;Today, it's trivial for a malicious actor to use a WebSocket traffic inspectors like &lt;a href="https://portswigger.net/burp/documentation/desktop/tutorials/testing-websockets" rel="noopener noreferrer"&gt;Burp&lt;/a&gt; or even &lt;a href="https://blittle.github.io/chrome-dev-tools/network/websockets.html" rel="noopener noreferrer"&gt;Chrome’s WebSocket inspector&lt;/a&gt; to recognise and exploit a pattern, making it more important than ever to double-check every resource is behind an access rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Insecure Direct Object References (IDOR)
&lt;/h2&gt;

&lt;p&gt;Insecure Direct Object References (IDOR) is a subcategory of access control vulnerabilities. It's one of the most common access control weaknesses, so we wanted to call it out directly.&lt;/p&gt;

&lt;p&gt;An IDOR vulnerability allows a malicious actor to exploit a WebSocket application by manipulating a "direct object reference" in the WebSocket request, such as a file name or query parameter.&lt;/p&gt;

&lt;p&gt;In an IDOR attack, the request succeeds due to missing access control checks, which fail to verify whether a user should be allowed to access specific data.&lt;/p&gt;

&lt;p&gt;To give an example - in a post entitled &lt;a href="https://footstep.ninja/posts/idor-via-websockets/" rel="noopener noreferrer"&gt;IDOR via WebSockets&lt;/a&gt;, Security Engineer Shuaib Oladigbolu describes how they managed to comment as another user simply by tampering with the authorID parameter in the WebSocket comment request - sometimes it's that easy for an attacker to cause problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of IDOR
&lt;/h3&gt;

&lt;p&gt;IDOR vulnerabilities are among the most commonly-reported and are trivial to execute. They can result in unauthorized data exposure, leakage of personally identifiable information, and even create the potential for data manipulation or deletion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention of IDOR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Implement access control checks for each object that users try to access.&lt;/li&gt;
&lt;li&gt;Use complex identifiers like GUIDs instead of predictable incremental IDs as a defense-in-depth measure (but remember that access control is crucial even with these identifiers).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more information on IDOR, The Open Web Application Security Project (OWASP) has a handy &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html" rel="noopener noreferrer"&gt;IDOR prevention cheat sheet&lt;/a&gt; - they are a non-profit dedicated to application security and have many educational resources dedicated to the security subjects described on this page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Injection attacks (SQL Injection, XSS, etc.)
&lt;/h2&gt;

&lt;p&gt;Injection is an attacker’s attempt to send data to an application, with the intention of exploiting vulnerabilities in the application's processing. It's one of the most common vulnerabilities with HTTP, and it applies to WebSockets as well, which are often used to transmit sensitive information and run actions with elevated privileges.&lt;/p&gt;

&lt;p&gt;Common types of injection attacks include SQL injection, where attackers manipulate database queries, and cross-site scripting (XSS), where malicious scripts are injected into web pages viewed by other users.&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%2F25apxezy4uf84b5ajnvb.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%2F25apxezy4uf84b5ajnvb.png" alt="sql-injection-attack-v2.0_2x" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of injection attacks
&lt;/h3&gt;

&lt;p&gt;Injections are among the oldest and most dangerous attacks aimed at web applications in general and often arise in relation to WebSockets. They can lead to unauthorized access, data loss, denial of service, or even full system compromise if not properly mitigated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention of injection attacks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Escape special characters in user inputs before incorporating them into queries or commands. This prevents them from being interpreted as code.&lt;/li&gt;
&lt;li&gt;Treat all user-supplied input as untrusted. Validate all inputs on the server, and use an allowlist of acceptable inputs.&lt;/li&gt;
&lt;li&gt;Examine and remove unwanted key characters, such as HTML tags that are deemed to be unsafe. Keep the safe data and remove any potentially unsafe characters.&lt;/li&gt;
&lt;li&gt;Most instances of SQL injection can be prevented by using parameterized queries (also known as prepared statements) instead of string concatenation within the query.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Denial of Service (DoS)
&lt;/h2&gt;

&lt;p&gt;A Denial of Service (DoS) attack exploits vulnerabilities in your WebSocket implementation to crash the WebSocket server, making it inaccessible for your genuine users.&lt;/p&gt;

&lt;p&gt;DoS attacks accomplish this by flooding the WebSocket server with traffic, or sending it information that causes a crash.&lt;/p&gt;

&lt;p&gt;A DoS attack can be compared to a phone system being flooded with constant, non-stop calls from prank callers, jamming the lines and preventing legitimate callers from getting through, thus disrupting trade and losing the business money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of DoS
&lt;/h3&gt;

&lt;p&gt;A successful DoS results in sluggish behavior or a total system crash. In both instances, the DoS attack deprives genuine users of the service they expected.&lt;/p&gt;

&lt;p&gt;Though DoS attacks don't typically result in unauthorized access to sensitive data, they can have a devastating impact on critical services, leading to direct financial loss and reputational damage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention of DoS
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Only allow authenticated users to open a WebSocket connection to prevent an attacker from flooding the WebSocket server with a large number of connection requests.&lt;/li&gt;
&lt;li&gt;Ensure that your authentication mechanism is efficient so that DoS attacks using an invalid token can be quickly and cheaply refused.&lt;/li&gt;
&lt;li&gt;Limit the number of requests that can be made to the WebSocket server from the same IP address over a specific time period.&lt;/li&gt;
&lt;li&gt;Limit the number of connections allowed to the WebSocket server to circumvent a &lt;a href="https://en.wikipedia.org/wiki/Resource_exhaustion_attack" rel="noopener noreferrer"&gt;resource exhaustion attack&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Limit message size to a reasonable number to prevent message size attacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DoS is not a specific attack but rather, a category of attacks. It would be outside the scope of this post to list every type of DoS however, I can highly recommend this post on &lt;a href="https://cqr.company/web-vulnerabilities/denial-of-service-dos-via-websockets/" rel="noopener noreferrer"&gt;DoS via WebSockets by CQR&lt;/a&gt;. They go into more detail about the above vulnerabilities and touch on a few others as well - for example, a pesky type of DoS called a WebSocket SYN Flood DoS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Denial of Service (DDoS)
&lt;/h2&gt;

&lt;p&gt;An additional type of DoS attack is the Distributed Denial of Service (DDoS) attacks.&lt;/p&gt;

&lt;p&gt;DDoS attacks are more sophisticated and impactful than DoS attacks. They involve a coordinated effort from multiple sources - often compromised computers or devices forming a &lt;a href="https://www.cloudflare.com/en-gb/learning/ddos/what-is-a-ddos-botnet/" rel="noopener noreferrer"&gt;botnet.&lt;/a&gt; These sources simultaneously DoS the WebSocket server, making it even more challenging to defend against or mitigate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of DDoS
&lt;/h3&gt;

&lt;p&gt;The most immediate and obvious impact of a DDoS is that your WebSocket server becomes overwhelmed and crashes. Suppose you’re building a &lt;a href="https://hubs.la/Q01XM4Xc0" rel="noopener noreferrer"&gt;chat experience&lt;/a&gt; or &lt;a href="https://hubs.la/Q023dkQt0" rel="noopener noreferrer"&gt;realtime collaborative experience&lt;/a&gt; over WebSockets - if your WebSocket server is compromised, your entire website may as well be down. As your service becomes intermittent, users may lose faith in the experience and use another service altogether.&lt;/p&gt;

&lt;p&gt;Since the attack is distributed, connections will likely come from all around the world. It will be hard to distinguish malicious and genuine connections due to the fact the botnet is composed of compromised systems that otherwise seem like genuine users. This makes a DDoS difficult to mitigate, resulting in intermittent disruptions to the service, often leading to reputational damage or straight up financial loss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention of DDoS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ensure your server is not vulnerable to DoS attacks (see section above).&lt;/li&gt;
&lt;li&gt;Use a hosted WebSocket infrastructure like ours at Ably - our near-limitless scale means we can mitigate huge increases in traffic and defend against DDoS attacks - so you benefit from our scale as attacks have no effect on your own servers.&lt;/li&gt;
&lt;li&gt;Proxy WebSocket connections through a content delivery network (CDN) that offer DDoS protection services that can detect and mitigate attacks in realtime.&lt;/li&gt;
&lt;li&gt;Distribute WebSocket connections across multiple servers and data centers, making it harder for attackers to overwhelm a single server.&lt;/li&gt;
&lt;li&gt;Have a plan in place for responding to a DDoS attack, including procedures for notifying stakeholders, isolating affected systems, and restoring services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more information on how DDoS attacks work in the real world (and some guidance on how to mitigate them), CloudFlare has an &lt;a href="https://www.cloudflare.com/en-gb/learning/ddos/what-is-a-ddos-attack/" rel="noopener noreferrer"&gt;in-depth post&lt;/a&gt; on the subject. As it happens, CloudFlare is a CDN that offers DDoS protection. We use them at &lt;a href="https://hubs.la/Q01XM6GG0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-site WebSocket hijacking
&lt;/h2&gt;

&lt;p&gt;Cross-site WebSocket hijacking (also known as cross-origin WebSocket hijacking) is a security vulnerability that occurs when a malicious actor makes an unauthorized WebSocket connection to a different origin (site) than the one the user is currently on.&lt;/p&gt;

&lt;p&gt;It arises when the WebSocket handshake request relies solely on HTTP cookies for authentication and isn't relevant to you if you're using token-based authentication.&lt;/p&gt;

&lt;p&gt;Modern browsers ordinarily prevent &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;cross-origin resource sharing&lt;/a&gt; (CORS) with a same-origin policy - while this applies to HTTP, counterintuitively, this does not apply to WebSocket connections, introducing a potentially surprising attack vector - unless you happen to know better. Good job you're reading this post!&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of cross-site WebSocket hijacking
&lt;/h3&gt;

&lt;p&gt;The implication of a cross-site WebSocket hijacking vulnerability depends on the scope of the attack and the hacker's ability to leverage it.&lt;/p&gt;

&lt;p&gt;At a minimum, a successful cross-site WebSocket hijacking attack enables malicious actors to abuse the established WebSocket connection to perform actions on the user's behalf without their consent, potentially compromising their data or executing unintended actions. In many cases, the user won't know they've been exploited until it's too late, making it hard to react and defend their own data.&lt;/p&gt;

&lt;p&gt;In a serious case of cross-site WebSocket hijacking, an attacker managed to &lt;a href="https://snyk.io/blog/gitpod-remote-code-execution-vulnerability-websockets/" rel="noopener noreferrer"&gt;execute arbitrary JavaScript in a subdomain of the domain where the WebSocket communication was occurring.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention of cross-site WebSocket hijacking
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;On the server side, validate the origin of incoming WebSocket connections. Only allow connections from trusted domains by checking the Origin header. Reject connections from unexpected or unauthorized origins.&lt;/li&gt;
&lt;li&gt;Introduce a session-individual token (like a &lt;a href="https://analyticssteps.com/blogs/all-you-need-know-about-csrf-tokens" rel="noopener noreferrer"&gt;CSRF token&lt;/a&gt;) into the user's session, which is then included in the WebSocket connection request sent to the server. This token is not known to malicious actors, making it difficult for them to forge requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sensitive information disclosure over a network
&lt;/h2&gt;

&lt;p&gt;When transmitting sensitive information from one client to another over WebSockets, it must traverse the network. Malicious actors may exploit vulnerabilities to eavesdrop on the communication and steal personally identifiable information, such as account details, sensitive health data, private communications, and intellectual property.&lt;/p&gt;

&lt;p&gt;Broadly speaking, this is known as a &lt;a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack" rel="noopener noreferrer"&gt;man-in-the-middle&lt;/a&gt; attack and is the equivalent of a mailman opening your bank statement, writing down your account details, and then resealing the envelope and delivering it to your door - you might not know you've been hacked until it's too late!&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of sensitive information disclosure over a network
&lt;/h3&gt;

&lt;p&gt;The most immediate impact of a data leak is the violation of privacy for your users whose personal information has been exposed. At a minimum, users will lose faith in your ability to secure their information, leading to reputational damage and potentially loss of business as well. Depending on the nature of the breach, you may incur legal consequences and regulatory fines also. &lt;/p&gt;

&lt;p&gt;The severity of the impact can vary based on the nature of the data exposed, and the motivation of the attackers. By intercepting or manipulating sensitive data, a malicious actor could steal intellectual property or even use data obtained from the breach to carry out a secondary attack, possibly causing wider operational disruptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention of sensitive information disclosure over a network
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Always use the WebSocket Secure (wss) protocol instead of WebSocket (ws) to ensure data exchanged between the client and server is encrypted using SSL (TLS), making it difficult for attacks to intercept or decipher.&lt;/li&gt;
&lt;li&gt;Ensure that the SSL certificate used for security the WebSocket connection is valid and issued by a trusted &lt;a href="https://www.ssl.com/faqs/what-is-a-certificate-authority/" rel="noopener noreferrer"&gt;certificate authority&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Regularly review the information sent over WebSockets to determine if a justifiable business need exists for transmitting each item present. Any items deemed unnecessary should be removed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using vulnerable or outdated WebSocket libraries
&lt;/h2&gt;

&lt;p&gt;When building with WebSockets, it’s common to use a library like &lt;a href="https://hubs.la/Q01Z56630" rel="noopener noreferrer"&gt;Socket.IO&lt;/a&gt; to help you get up and running more quickly. These libraries tend to implement features you probably need - like automatic reconnection and message acknowledgements, as well as provide patterns for grouping connections by “rooms” or “topics” (&lt;a href="https://hubs.la/Q023dz5q0" rel="noopener noreferrer"&gt;pub/sub&lt;/a&gt;) to make routing messages simple. They are very convenient!&lt;/p&gt;

&lt;p&gt;However, before depending on a WebSocket library, do some research, and possibly some auditing.&lt;/p&gt;

&lt;p&gt;Using a third-party WebSocket library can save time upfront, but it’s not without risk of introducing a vulnerability.&lt;/p&gt;

&lt;p&gt;For example, according to Snyk, &lt;a href="https://security.snyk.io/package/pip/websockets" rel="noopener noreferrer"&gt;websockets@9.1 introduces a DOS vulnerability before being patched in websockets@10.&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Here's one more example of a &lt;a href="https://security.snyk.io/vuln/SNYK-JS-WS-1296835" rel="noopener noreferrer"&gt;Regular Expression Denial of Service (ReDoS) vulnerability&lt;/a&gt; introduced by the third-party ws package, which has since been solved.&lt;/p&gt;

&lt;p&gt;Newer WebSocket libraries (or newer WebSocket library versions) may be more susceptible to vulnerabilities due to the fact they haven’t yet been battle-tested. &lt;/p&gt;

&lt;p&gt;Additionally, watch out for older or unmaintained WebSocket libraries. Often, libraries with known vulnerabilities don’t get fixed due to a lack of maintainers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Impact of using vulnerable or outdated WebSocket libraries
&lt;/h3&gt;

&lt;p&gt;A vulnerable or outdated dependency may be susceptible to any of the common WebSocket vulnerabilities described in this post. &lt;/p&gt;

&lt;p&gt;Malicious actors can exploit these vulnerabilities to gain access to sensitive data or take control of the application entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevention of using vulnerable or outdated WebSocket libraries
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use a commercial WebSocket-based API like &lt;a href="https://hubs.la/Q01XM6GG0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt;, which is actively maintained by a dedicated team, frequently undergoes penetration testing, and has a respectable &lt;a href="https://hubs.la/Q023dHpc0" rel="noopener noreferrer"&gt;vulnerability disclosure policy&lt;/a&gt; (bug bounty).&lt;/li&gt;
&lt;li&gt;Carefully inspect any open source dependency you add to your WebSocket server, as it may have an outstanding vulnerability.&lt;/li&gt;
&lt;li&gt;Keep open source dependencies updated to ensure you have the latest security patches, reducing the risk of an exploit.&lt;/li&gt;
&lt;li&gt;Optionally use an integration such as &lt;a href="https://snyk.io/" rel="noopener noreferrer"&gt;Snyk&lt;/a&gt; to continuously perform security scanning on your open source dependencies and detect potential vulnerabilities.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WebSocket security FAQ
&lt;/h2&gt;

&lt;p&gt;To wrap up this post, let's cover some of the most commonly-asked questions about WebSocket security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do WebSockets need SSL?
&lt;/h3&gt;

&lt;p&gt;Yes, it's highly recommended to use SSL encryption with WebSockets (wss) to prevent eavesdropping and tampering by malicious actors. Using SSL also helps to maintain the integrity and confidentiality of the communication and prevents various security vulnerabilities that can arise when data is transmitted over an unencrypted connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  How secure are WebSockets?
&lt;/h3&gt;

&lt;p&gt;WebSockets are very secure when used over an encrypted SSL connection (wss) and you follow the WebSocket security best practices described in this post. Secure authentication, authorization, and input validation are crucial for preventing unauthorized access and attacks. Regular updates to libraries and ongoing testing contribute to overall security.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you test WebSocket security?
&lt;/h3&gt;

&lt;p&gt;Comprehensive WebSocket security testing requires a deep understanding of the WebSocket protocol and practical experience in both manual and automated security testing techniques. Open tools like &lt;a href="https://github.com/PalindromeLabs/STEWS" rel="noopener noreferrer"&gt;STEWS&lt;/a&gt; can detect known WebSocket vulnerabilities while commercial security tools like &lt;a href="https://portswigger.net/burp/documentation/desktop/tutorials/testing-websockets" rel="noopener noreferrer"&gt;Burp Suite&lt;/a&gt; exist to intercept and manipulate WebSocket frames with ease, however they won't catch everything. Perform manual testing and fuzzing to identify unexpected behavior or vulnerabilities that automated tools might miss.&lt;/p&gt;

&lt;p&gt;If you lack experience in WebSocket security testing, consider consulting with security experts or penetration testers who specialize in this area, as the consequences of poor WebSockets security can be dire.&lt;/p&gt;

</description>
      <category>websockets</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 best chat APIs and messaging SDKs in 2024</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Fri, 14 Jul 2023 11:51:35 +0000</pubDate>
      <link>https://dev.to/ably/5-best-chat-apis-and-messaging-sdks-2b32</link>
      <guid>https://dev.to/ably/5-best-chat-apis-and-messaging-sdks-2b32</guid>
      <description>&lt;p&gt;Looking for the perfect chat solution can be a daunting task. &lt;/p&gt;

&lt;p&gt;You need a provider that not only supports all the necessary chat features but also offers delivery and scaling guarantees to ensure a smooth chat operation as your business grows.&lt;/p&gt;

&lt;p&gt;While it might be tempting to start with a simple solution and see how your needs evolve, underestimating the importance of finding the right provider can be a costly mistake. Switching solutions later requires significant time, resources, and money, not to mention meticulous testing to avoid disruption to your business, which can lead to reputational damage and lost revenue. You need to get it right the first time, and this post is here to help you do exactly that. &lt;/p&gt;

&lt;p&gt;We've narrowed it down to just the 5 best chat APIs and messaging SDKs, taking into consideration factors like features, scalability, and price. This way, you can make an informed decision about which provider will work best for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are chat messaging APIs and SDKs?
&lt;/h2&gt;

&lt;p&gt;Fundamentally, chat APIs and SDKs enable developers to build chat more quickly by managing the infrastructure necessary to send and receive messages in realtime, plus &lt;a href="https://hubs.la/Q01XM4Xc0" rel="noopener noreferrer"&gt;must-have chat features&lt;/a&gt; like typing indicators, online indicators, and read receipts.&lt;/p&gt;

&lt;p&gt;If you’re wondering how an API and SDK differ in this context, it has to do with how developers interface with said infrastructure. &lt;/p&gt;

&lt;p&gt;An API gives developers direct access to the infrastructure through endpoints to send and receive messages, subscribe to typing indicators, and more. Equipped with the nuts and bolts, developers can now implement a chat UI with total flexibility.&lt;/p&gt;

&lt;p&gt;An SDK, on the other hand, offers a complete set of development tools, usually including prebuilt chat UI components. By assembling chat “building blocks”, developers can save time up front. &lt;/p&gt;

&lt;p&gt;Remember that APIs and SDKs aren’t mutually exclusive, and some providers offer both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should you look for in a live chat solution?
&lt;/h2&gt;

&lt;p&gt;Chat apps like WhatsApp and Slack have set the bar for live chat experiences, and it’s important to find a chat provider that enables you to deliver the same quality experience. &lt;/p&gt;

&lt;p&gt;These are the ten foundational features you have to be able to deliver using your chosen solution, and that you should consider during your evaluation process.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low Latency:&lt;/strong&gt; If messages take too long to send or load, users won’t have a smooth user experience and will take the conversation elsewhere. That could be another channel (for example, a customer might pick up the phone and start their support request from scratch) or, if the experience is consistently laggy, users might even switch to another app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The ability to scale:&lt;/strong&gt; As your user volume grows, the load on the server also increases. If the server can’t handle this increased load, chat features can become unresponsive, causing a poor user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reliability:&lt;/strong&gt; Even a short downtime or an occasional error can be frustrating for users. This is unfortunate, but for businesses that rely on chat applications for customer support or sales, downtime can result in lost revenue or missed opportunities. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guaranteed delivery:&lt;/strong&gt; Messages should be delivered exactly once and in the correct order, even in the face of network issues. Otherwise, messages might come flying in out of order, and more than once, making it almost impossible to discern what is trying to be communicated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Elasticity:&lt;/strong&gt; Chat app usage can be unpredictable. Applications may experience sudden spikes in usage during peak hours or events, or during product launches and marketing campaigns. If the server can’t scale its resources up dynamically in response to changes in user demand, chat features may become unresponsive. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rich features:&lt;/strong&gt; Your users will expect typing indicators, online status indicators, read receipts, push notifications, and more. If users can’t accomplish what they need to with your chat, they might switch to another app where they can. For a comprehensive guide to the features needed, see our &lt;a href="https://hubs.la/Q01XM4Xc0" rel="noopener noreferrer"&gt;ultimate list of live chat features&lt;/a&gt; (complete with examples).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integrations:&lt;/strong&gt; Live chat solutions need to integrate with the other tools in your tech stack. Dedicated integrations between technologies can help keep engineering requirements to a minimum.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data governance:&lt;/strong&gt; The way data is stored, processed, and disposed of needs to meet regulatory requirements, wherever you operate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chat moderation:&lt;/strong&gt; Chat experiences need to be safe for both end users and chat operators. The process of monitoring and controlling user-generated messages helps ensure compliance with your terms of use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontend UI:&lt;/strong&gt; Customizable themed components including colors and fonts help to keep your chat solution a cohesive part of your customer experience. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another key consideration when evaluating a live chat solution is, of course, cost.&lt;/p&gt;

&lt;p&gt;Most chat API providers base their pricing on the number of &lt;a href="https://hubs.la/Q01XM4Mx0" rel="noopener noreferrer"&gt;monthly active users (MAUs)&lt;/a&gt;. Some also account for additional factors like the number of messages and concurrent users. &lt;/p&gt;

&lt;p&gt;While it might be hard to predict your precise usage up front, you should evaluate how a given provider’s pricing model might scale for your project. For example, some pricing models get gradually more expensive as you grow, while others hit an inflection point.&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%2Fae9t8tzos64qfka39mis.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%2Fae9t8tzos64qfka39mis.png" alt="chat-api-pricing-models-illustrated--1-" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we’ve covered the fundamentals, let’s look at the 5 best chat APIs on the market.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5 best chat API and messaging SDKs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Ably
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://hubs.la/Q01XM6GG0" rel="noopener noreferrer"&gt;Ably&lt;/a&gt; is a realtime experience infrastructure used by companies like HubSpot, I7Live, and Riff to build feature-rich and reliable chat.&lt;/p&gt;

&lt;p&gt;With the Ably API, you get the nuts and bolts needed to implement chat your way. For example, &lt;a href="https://hubs.la/Q01XM7Zz0" rel="noopener noreferrer"&gt;publish&lt;/a&gt; and &lt;a href="https://hubs.la/Q01XM7Zz0" rel="noopener noreferrer"&gt;subscribe&lt;/a&gt; functions to send and receive messages in a 1:1 or group setting, as well as additional data like which users are typing. Ably also provides other essential features, including &lt;a href="https://hubs.la/Q01XM8bK0" rel="noopener noreferrer"&gt;presence&lt;/a&gt; to see who’s online, &lt;a href="https://ably.com/tutorials/history" rel="noopener noreferrer"&gt;message history&lt;/a&gt;, and &lt;a href="https://hubs.la/Q01XM8qb0" rel="noopener noreferrer"&gt;push notifications&lt;/a&gt;, and they all work cross-platform, enabling you to deliver a consistent experience across the web, Android, and iOS.&lt;/p&gt;

&lt;p&gt;Whether it's a fundamental feature like threads, a contact book that needs to integrate with your database, or even live stream chat with donations, Ably's flexibility extends beyond customization options, allowing you to create engaging chat experiences beyond what's typically possible. Further, because you’re building on the &lt;a href="https://hubs.la/Q01XM8qn0" rel="noopener noreferrer"&gt;Ably platform&lt;/a&gt;, you’ll benefit from low latency, guaranteed message ordering, and an assuring 99.999% average uptime SLA.&lt;/p&gt;

&lt;p&gt;Although the nuts and bolts provided by Ably are perfect for chat, they’re not limited to chat alone. That’s great news because once you get started with Ably, you can use the same foundations to build &lt;a href="https://hubs.la/Q01XM8KD0" rel="noopener noreferrer"&gt;other realtime features&lt;/a&gt;, including notifications, and realtime collaboration.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Twilio
&lt;/h3&gt;

&lt;p&gt;Twilio is a communication platform with a suite of products designed to help developers code communication features like SMS, email, video, and - you guessed it - chat. &lt;/p&gt;

&lt;p&gt;Their chat product is called &lt;a href="https://www.twilio.com/en-us/messaging/conversations-api" rel="noopener noreferrer"&gt;Conversations API&lt;/a&gt; and it’s unique because it’s specifically designed to enable omni-channel messaging. &lt;/p&gt;

&lt;p&gt;Suppose your project revolves around connecting members from your team with customers. With Conversations API, the customer can start a conversation in-app then continue it on WhatsApp, for example. Twilio helps connect the dots, creating a seamless omni-channel experience on both sides. &lt;/p&gt;

&lt;p&gt;Although Conversations API &lt;em&gt;could&lt;/em&gt; be adapted to enable your users to chat together, that isn’t really the intended use case. As a result, Conversations API offers a limited chat feature set compared to some alternative solutions. For example, you can’t use Twilio to &lt;a href="https://hubs.la/Q01XM8LX0" rel="noopener noreferrer"&gt;implement public group chat&lt;/a&gt;, and they don't support @user and @channel mentions or reactions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Firebase
&lt;/h3&gt;

&lt;p&gt;Firebase is an app development platform backed by Google that famously replaces the need for a backend. It accomplishes this by providing developers with a variety of services, including authentication, push notifications, and a database called &lt;a href="https://firebase.google.com/docs/firestore" rel="noopener noreferrer"&gt;Cloud Firestore&lt;/a&gt; that can be adapted to store and broadcast chat messages in realtime.&lt;/p&gt;

&lt;p&gt;Firebase can be an attractive option because it manages all of your backend services in one place. These services are meant to gel well together, and it should therefore be smooth to implement user management, roles and permissions, chat history, and even push notifications under the roof of one platform. Of course, this might not be so compelling if you already have a backend and user management system. In that case, you’ll probably want to assess how you’ll keep Firebase and your systems in sync instead.&lt;/p&gt;

&lt;p&gt;Remember, since Firebase is a general-purpose platform, it doesn’t have a specific notion of chat. While implementing basic chat might be fairly straightforward, expect to invest a significant amount of upfront development to get features like &lt;a href="https://hubs.la/Q01XM9820" rel="noopener noreferrer"&gt;typing indicators&lt;/a&gt;, &lt;a href="https://hubs.la/Q01XM9zz0" rel="noopener noreferrer"&gt;read receipts&lt;/a&gt;, and presence indicators to work, especially without a hitch at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Sendbird
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://sendbird.com/" rel="noopener noreferrer"&gt;Sendbird Chat&lt;/a&gt; enables developers to implement chat by providing an SDK with ready-made chat UI components. They cover all the essential features you might expect, including voice and video, which makes it possible to deliver a comprehensive chat experience. &lt;/p&gt;

&lt;p&gt;This out-of-the-box experience enables developers to “turn on” chat quickly, albeit at a recurring monthly premium. A common concern about Sendbird is that, beyond a certain scale, their pricing becomes somewhat opaque. This can make it hard to calculate precisely how much you’ll pay, especially if you’re dealing with a high number of users and traffic spikes.  &lt;/p&gt;

&lt;p&gt;Under the hood, Sendbird manages the chat infrastructure necessary to send and receive messages in realtime while ensuring that messages are stored securely and compliant with relevant standards like SOC 2 and HIPAA. &lt;/p&gt;

&lt;p&gt;It should be noted that although Sendbird touts eight available data centers, each app must reside in a single data center, creating a single point of failure. Moreover, this can negatively impact latency, unless your users happen to be connecting from the same geographical region.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Stream (formerly GetStream)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://getstream.io/chat/" rel="noopener noreferrer"&gt;Stream&lt;/a&gt; provides APIs and chat UI components that developers can use to implement live chat into their web and mobile applications.&lt;/p&gt;

&lt;p&gt;They make it possible to build chat experiences with rich features like typing indicators, user presence, threaded conversations, push notifications, message &amp;amp; user moderation, reactions, plus @user and @channel mentions. In other words, everything you need for a modern chat experience.&lt;/p&gt;

&lt;p&gt;All the infrastructure is managed for you. However, all chat traffic is processed and stored in a single data center. This can have a negative implication on latency and creates a single point of congestion and failure. This might explain why Stream only offers a 99.95% SLA unless you pay more for an enterprise plan.&lt;/p&gt;

&lt;p&gt;It’s worth noting that chat often goes hand-in-hand with other realtime use cases, such as multiplayer collaboration (think of a product like Figma, where you can edit your design collaboratively and chat with other users in realtime). However, Stream is very much a chat-centric solution. If you’re looking to build other realtime experiences alongside chat, Stream can’t help – you will have to use different technologies, which brings additional complexity and increased costs to your project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Which is the best chat solution for your use case?
&lt;/h2&gt;

&lt;p&gt;Like everything in software, the answer is “it depends” - on the features you need, how you envision your users engaging with your app, and on if you want to save time up front with a turn key solution at the cost of flexibility later. It’s critical to try and answer these questions early on so you don’t end up locked-in with a vendor, or with a solution that is great for chat but forces you to source another solution to implement other realtime features like presence indicators, multiplayer collaboration (a la &lt;a href="https://ably.com/blog/buy-vs-build-adobe-acquires-figma" rel="noopener noreferrer"&gt;Figma&lt;/a&gt;), collaborative whiteboarding, and collaborative text editing. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>30 inspiring web developer portfolio examples you have never seen before</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Mon, 20 Feb 2023 22:40:04 +0000</pubDate>
      <link>https://dev.to/scrimba/30-inspiring-web-developer-portfolio-examples-you-have-never-seen-before-3774</link>
      <guid>https://dev.to/scrimba/30-inspiring-web-developer-portfolio-examples-you-have-never-seen-before-3774</guid>
      <description>&lt;p&gt;There are many decisions that a recruiter makes while evaluating your application for your first junior developer role.&lt;/p&gt;

&lt;p&gt;Do you have the personality and capacity to confront technical challenges? Do you have a genuine passion for coding and the dedication to improve despite not having professional experience?&lt;/p&gt;

&lt;p&gt;Your secret weapon to convince them?&lt;/p&gt;

&lt;p&gt;An amazing web developer portfolio.&lt;/p&gt;

&lt;p&gt;A web developer portfolio is a showcase of who you are. It shows recruiters that you have the right skills, enthusiasm, and desire to complete technical challenges. It's your opportunity both to impress them and standout from another dozen of candidates in the hiring process.&lt;/p&gt;

&lt;p&gt;If you're ready to create your web developer portfolio and yet you don't know how to turn it into something beautiful, this article is the only resource you will ever need.&lt;/p&gt;

&lt;p&gt;I've gone through dozens of forums, portfolio directories and design websites to find the most stunning, yet unseen portfolios for you to take examples from.&lt;/p&gt;

&lt;p&gt;Let's dive deep into them now and find out what makes them great.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do I really need a web developer portfolio?
&lt;/h2&gt;

&lt;p&gt;No. A web developer portfolio is not a requirement to be hired for a junior position. However, missing one means missing a tremendous opportunity to prove to recruiters your skills and dedication to the role despite your lack of professional experience.&lt;/p&gt;

&lt;p&gt;Without one, you're limited to your words during the interview process, and as always, showing your skills is better than telling them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Should I Know Before Creating My Web Developer Portfolio?
&lt;/h2&gt;

&lt;p&gt;Before diving into creating your own web developer portfolio, keep in mind the following fundamentals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Display only your best work&lt;/strong&gt;: don't mind using your first program where you used a while loop. Your portfolio is a place to show complete, meaningful projects. Also remember: you don't need a dozen of significant projects. Usually, two to four is a perfect balance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use your name as the domain's name&lt;/strong&gt;: this is the absolute best way to make sure you're found during the interview process when recruiters search for you on Google.&lt;/li&gt;
&lt;li&gt;Include a contact form: to make sure recruiters can easily contact you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add honorable mentions&lt;/strong&gt;: if somebody mentions your work in a publication or praises you online for it, mention it in the portfolio. There's no better way of selling yourself than social proof.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not use tutorial projects&lt;/strong&gt;: your portfolio should be a place for originality, so don't use projects from Udemy courses or YouTube tutorials. It might surprise you how many devs do and how easily recruiters will spot that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Important note&lt;/strong&gt;: while a great web developer portfolio can help you a long way in your career search, a bad one will for sure damage your image in the eyes of recruiters. So always strive for a website that is looking beautiful, both on mobile and desktop, which you maintain regularly. You never want recruiters to click on your portfolio only to find a 404-page error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Top 30 Stunning Web Developer Portfolio Examples
&lt;/h2&gt;

&lt;p&gt;Let's know deep dive into our favorite portfolio examples for you to take inspiration from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our top 30 stunning web developer portfolio examples
&lt;/h2&gt;

&lt;p&gt;Let's know deep dive into our favourite portfolio examples for you to take inspiration from.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.safetpojskic.com/" rel="noopener noreferrer"&gt;Safet Pojskic&lt;/a&gt;
&lt;/h3&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%2Fkhe7eiwczeysbzpb086z.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%2Fkhe7eiwczeysbzpb086z.png" alt="Safet's portfolio homepage." width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I love the simplicity of Safet's portfolio. He follows a classic structure of an introductory line + nice photo of himself. He also placed a contact button as a call to action, which is very smart as it directs website's visitors to their next step when interacting with him.&lt;/p&gt;

&lt;p&gt;After listing his skills, he also gives recruiters the opportunity to download an updated version of his CV with a button click.&lt;/p&gt;

&lt;p&gt;I really enjoyed the particle animation they used for the background of the portfolio, adding a sense of dynamic to a spotless color palette, using simply black, red and white.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: if you want to implement particles effect in your portfolio too, you can use &lt;a href="https://animejs.com/" rel="noopener noreferrer"&gt;Anime.js&lt;/a&gt; as Safet did.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://benscott.dev/" rel="noopener noreferrer"&gt;Ben Scott&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FBen-Scott.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FBen-Scott.png" title="Ben's portfolio homepage." alt="Ben's portfolio homepage." width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ben's description of himself is uncomplicated. His portfolio is very similar to Safet's one in terms of background effects and palette colors. I loved his choice of displaying his skills as images and, most of all, all his projects list a live link to test the final result, allowing recruiters to visualize his work without having to pull down the project from GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: want to create a dynamic lightning background like Ben? He used&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" rel="noopener noreferrer"&gt;HTML's canvas API&lt;/a&gt; for that.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://briceclain.com/en/" rel="noopener noreferrer"&gt;Brice Clain&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FBrice-Clain.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FBrice-Clain.png" title="Brice's portfolio homepage" alt="Brice's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Brice's portfolio feels alive thanks to the animation he placed across the whole page. Visiting this website feels like having a conversation with him, where he explains the history behind each one of his projects and how they came to life. A really memorable experience for the eyes.&lt;/p&gt;

&lt;p&gt;Tip: &lt;a href="https://animejs.com/" rel="noopener noreferrer"&gt;Anime.js&lt;/a&gt; is the library behind his typewriting effects on the text.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://iuri.is/" rel="noopener noreferrer"&gt;Iuri De Paula&lt;/a&gt;
&lt;/h3&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%2Fv13vajqfeilmapnja20p.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%2Fv13vajqfeilmapnja20p.png" alt="Iuri's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first time I visited Iuri's portfolio, I realized how a beautiful web design mixed with storytelling can make for a masterpiece website. His portfolio is an award-winning experience, where he guides you step-by-step through his career via stunning graphics and scrolling effects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: want to create a portfolio just like Iuri? The code for his website is open-source, and you can find it&lt;a href="https://github.com/iuridepaula/portfolio" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.urosignjatovic.com/" rel="noopener noreferrer"&gt;Uroš Ignjatović&lt;/a&gt;
&lt;/h3&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%2Fyfq3dquye2bc097eplxc.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%2Fyfq3dquye2bc097eplxc.png" alt="Uroš' portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite Uroš portfolio's simplicity, he did an amazing job of creating an elegant website with a traditional black and white color palette and an amazing font choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: the font used for this website is called "Segoe UI".&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://bruno-figueiredo.nl/index.html" rel="noopener noreferrer"&gt;Bruno Figueiredo&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FBruno-Figueiredo.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FBruno-Figueiredo.png" title="Bruno's portfolio homepage" alt="Bruno's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bruno's portfolio is a visually appealing website. This is possible thanks to a pleasant choice of the color purple, a great illustration to welcome visitors, and various nice scroll effects spread across the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: if you want to adopt this website's scroll animations, you can use a library called &lt;a href="https://michalsnik.github.io/aos/" rel="noopener noreferrer"&gt;AOS&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://hakim.se/" rel="noopener noreferrer"&gt;Hakim El Hattab&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FHakim-El-Hattab.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FHakim-El-Hattab.png" title="Hakim's portfolio homepage" alt="Hakim's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want your portfolio to be uncomplicated, you can opt for the approach used by Hakim. He composed this website with a simple description of himself plus links to his work, making it for an easy yet effective way of displaying who he is to potential employers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://jgthms.com/" rel="noopener noreferrer"&gt;Jeremy Thomas&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FJeremy-Thomas.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FJeremy-Thomas.png" title="Jeremy's portfolio homepage" alt="Jeremy's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this is not exactly a developer portfolio aimed at getting hired for the first time, it still uses a lot of great ideas you can integrate into your design. First, Jeremy lists the company he worked with, which fits perfectly in with the principle of using, when available, social proof to impress visitors.&lt;/p&gt;

&lt;p&gt;In addition, his case study section is a magnificent example of presenting yourself as a professional. He lists every challenge he faced for each client, the solutions he adopted, and why. An approach you can use for your portfolio too.&lt;/p&gt;

&lt;p&gt;✅&lt;strong&gt;Pro Idea&lt;/strong&gt;: Don't just show your project, talk about them. Create case studies around your work, explain your challenges, choices you made and the tradeoffs you had to come up with, it will impress recruiters and give you a huge help during the interview process.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.adamhartwig.co.uk/" rel="noopener noreferrer"&gt;Adam Hartwig&lt;/a&gt;
&lt;/h3&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%2Fzhzydjjm1mghl2m8y1ao.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%2Fzhzydjjm1mghl2m8y1ao.png" alt="Adam's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I loved this site from the first moment I laid my eyes on it. The lively color palettes welcomes its visitors into a memorable experience, where nothing is trivial, from the animations to the way its author Adam uses them to describe his skills. I'm sure he had fun creating this portfolio, and that reflects perfectly into this piece of work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: &lt;a href="https://textillate.js.org/" rel="noopener noreferrer"&gt;Textillate.js&lt;/a&gt; is the text animations library you can use to animate your texts like Adam did.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://timroussilhe.com/" rel="noopener noreferrer"&gt;Timothèe Roussilhe&lt;/a&gt;
&lt;/h3&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%2Fmh2h1l3md7dq9a0my6gl.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%2Fmh2h1l3md7dq9a0my6gl.png" alt="Timothèe's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This website is very simple, yet memorable thanks to a choice of colors that add an artistic aura to Timothèe's portfolio, making it very stylish and notable in the panorama of dev portfolios.&lt;/p&gt;

&lt;p&gt;✅&lt;strong&gt;Pro Idea&lt;/strong&gt;: not sure about which color palette to use for your website? Check out this free color palette generator &lt;a href="https://coolors.co/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://adamgreenough.me/" rel="noopener noreferrer"&gt;Adam Greenough&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FAndy-Patrick.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FAndy-Patrick.png" title="Adam Greenough's portfolio homepage" alt="Adam Greenough's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adam's portfolio is simple. He uses a beautiful color and a moving background to make the website look more lively. He has some amazing projects listed, and I really appreciated his listed coding blog posts, which increase Adam's credibility as a professional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: &lt;a href="https://www.vantajs.com/" rel="noopener noreferrer"&gt;Vanta.js&lt;/a&gt; is the library used for the background effects.&lt;/p&gt;

&lt;p&gt;✅&lt;strong&gt;Pro Idea&lt;/strong&gt;: if you ever wrote technical blog posts during your path to learn coding, add them to your website. Recruiters love to see this kind of community engagement and efforts. Actually, I landed my last dev job also thanks to my contribution online on Medium!&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://andypatrickdesign.com/" rel="noopener noreferrer"&gt;Andy Patrick&lt;/a&gt;
&lt;/h3&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%2Fax914g5h4leo5iy8vhq5.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%2Fax914g5h4leo5iy8vhq5.png" alt="Andy's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a UX designer, Andy uses a very creative approach to narrating his story, with an airplane landing through the various stages of his career, making this portfolio remarkable thanks to this minor detail, despite his simplicity in other sections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: to create loading effects as cool as Andy's portfolio, &lt;a href="https://loading.io/css/" rel="noopener noreferrer"&gt;Loading.io&lt;/a&gt; is an impressive library I discovered for this purpose.&lt;/p&gt;

&lt;p&gt;✅&lt;strong&gt;Pro Idea&lt;/strong&gt;: sometimes a memorable animation is everything needed to make a portfolio easy to remember.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.seanhalpin.design/" rel="noopener noreferrer"&gt;Sean Halpin&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FSean-Halpin.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FSean-Halpin.png" title="Sean's portfolio homepage" alt="Sean's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This portfolio is my favorite on the entire list. The palette of pastel colors is delightful, the "Bogart" font makes the whole page look slick, and the above the folder content has the genius touch of an eye animation looking at the text, directing the user's eye where Sean wants.&lt;/p&gt;

&lt;p&gt;The author describes his work with amazing case studies, where he shows the entire process of creating a project, including his personal notes and sketches. A true masterpiece thanks to his care for details and overall look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: You can also create a moving eyeball in CSS &lt;a href="https://lenadesign.org/2021/04/27/css-js-eye-follow-mouse-cursor-blink-on-hover/" rel="noopener noreferrer"&gt;using this tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://gillesvermeulen.be/" rel="noopener noreferrer"&gt;Gilles Vermeulen&lt;/a&gt;
&lt;/h3&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%2Frhhjrub2ggdq9u62oei9.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%2Frhhjrub2ggdq9u62oei9.png" alt="Gille's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite Gille's portfolio comprising a single series of scrolling images and descriptions, he went big on creativity, with this animated character head opening to reveal all his cool projects and a background effect. A very witty approach to showcase his work in a fun way.&lt;/p&gt;

&lt;p&gt;✅&lt;strong&gt;Pro Idea&lt;/strong&gt;: never be afraid of showing personality on your portfolio, whether that's via a witty copy or an image of yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.raoul-gaillard.com/" rel="noopener noreferrer"&gt;Raoul Gaillard&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FRaoul-Gaillard.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FRaoul-Gaillard.png" title="Raoul's portfolio homepage" alt="Raoul's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What impressed me the most about Raoul's website is his elegance in using colors, and the way he presents every detail beautifully thanks to amazing parallax effects, 3D images and a 3D matrix's background that will make you feel as if the website is constantly moving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: Here's an amazing guide on creating&lt;a href="https://polypane.app/css-3d-transform-examples/" rel="noopener noreferrer"&gt; 3D-looking images with CSS&lt;/a&gt;. Here you can find a guide to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix3d" rel="noopener noreferrer"&gt;3D matrix in HTML&lt;/a&gt; for the background effects used by Raoul.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.seanw.org/" rel="noopener noreferrer"&gt;Sean Wilson&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FSean-Wilson.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FSean-Wilson.png" title="Sean's portfolio homepage" alt="Sean's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sean's portfolio is a minimalist website. Yet, what makes it worthy of notice is his use of great copy to catch his visitors. Instead of simply presenting himself, he targets the specific audience of people in need of a website's redesign, explaining how he can help them and why he's the solution to their problem. A great example of how a well-thought copy can increase your portfolio's effectiveness.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://eli.wtf/" rel="noopener noreferrer"&gt;Eli.wtf&lt;/a&gt;
&lt;/h3&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%2Fgzprsl6y4fjmjsq3vpkl.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%2Fgzprsl6y4fjmjsq3vpkl.png" alt="Eli's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eli describes himself as a developer who loves creating weird websites, and his portfolio is a perfect example of that. The website has a high visual impact thanks to an initial moving skull animation, with quirky animations leading you from one page to another.&lt;/p&gt;

&lt;p&gt;He also lists the conferences he took part in as a speaker for added credibility, besides an amazing use cases section where he describes how his clients benefited from his work. Truly an amusing experience as a website.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: the animations on the website were made using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API" rel="noopener noreferrer"&gt;HTML's canvas API&lt;/a&gt;. I found also a CodePen explaining to you how to create a moving head animation like his, &lt;a href="https://codepen.io/EllenProbst/pen/KXxxaR" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://lbebber.github.io/public/" rel="noopener noreferrer"&gt;Lucas Bebber&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FLucas-Bebber.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FLucas-Bebber.png" title="Lucas' portfolio homepage" alt="Lucas' portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This portfolio is proof of how simplicity, when accompanied by great visuals can make for an amazing portfolio. The initial image of moving mountains make the whole project feel alive, and it welcomes users to the projects created by Lucas and live demo links to try them.&lt;/p&gt;

&lt;p&gt;Tip: here's a &lt;a href="https://codepen.io/jackrugile/pen/DrOZoY" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; on creating moving mountains like Lucas did.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://samsy.ninja/" rel="noopener noreferrer"&gt;Samsy.ninja&lt;/a&gt;
&lt;/h3&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%2Flh4.googleusercontent.com%2FDWsb4cakXUvOEBkjz5fB-SW5dmbN8EuQpxpijD5j2IaHmZk-47XlTFv-20ec8nILctLDuTl-XGNc8nkZ-kfjXyREVvDSB-HVk2fZmIdbIujm4_xVsJEGCSA2A4MDkUwgl9sWnmmIVffj2aR85ANFvIhNS9-rGfnLCD5LJzKm8x--hUrsEx0r8UQ_G1nX0A" 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%2Flh4.googleusercontent.com%2FDWsb4cakXUvOEBkjz5fB-SW5dmbN8EuQpxpijD5j2IaHmZk-47XlTFv-20ec8nILctLDuTl-XGNc8nkZ-kfjXyREVvDSB-HVk2fZmIdbIujm4_xVsJEGCSA2A4MDkUwgl9sWnmmIVffj2aR85ANFvIhNS9-rGfnLCD5LJzKm8x--hUrsEx0r8UQ_G1nX0A" alt="Samsy's portfolio homepage" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Samsy's portfolio opens with a stunning effect of moving lines and a minimalistic color palette made of gray colors. I loved his approach to transitioning between sections by creating a zoom-in effect, rather than a classic scroll. Plus, every use case he lists has videos attached to it, making the experience a delight for the eyes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://dmnsgn.me/" rel="noopener noreferrer"&gt;Damien Seguin&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FDamien-Seguin.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FDamien-Seguin.png" title="Damien's portfolio homepage" alt="Damien's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Damien describes himself as a creative developer, and that shines clearly through his portfolio. This website speaks volumes about the artistic personality of the author, showcasing his work in an easy yet remarkable way, with beautiful use cases built around his projects and great images to give the whole thing a retro, artsy look.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://luruke.com/" rel="noopener noreferrer"&gt;Luigi de Rosa&lt;/a&gt;
&lt;/h3&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%2Fs2xqn7qxouf8u5hz4j1s.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%2Fs2xqn7qxouf8u5hz4j1s.png" alt="Luigi's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luigi adopted quite an unusual color palette for his portfolio, making the result truly distinctive. Bubbles animations also helped the entire website to achieve an impressive and admirable ultimate effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: The bubbles on this website were realized using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d" rel="noopener noreferrer"&gt;translate3D in CSS&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.grifo.tv/" rel="noopener noreferrer"&gt;Danilo Figueiredo&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FDanilo-Figueiredo.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FDanilo-Figueiredo.png" title="Danilo's portfolio homepage" alt="Danilo's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Danilo chose a text only approach to welcome users into his portfolio, which I found quite effective thanks to a great font choice (SF pro). I also appreciated his idea of mentioning the tech stack he used for each one of his use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="http://riccardozanutta.com/" rel="noopener noreferrer"&gt;Riccardo Zanutta&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FRiccardo-Zanutta.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FRiccardo-Zanutta.png" title="Riccardo's portfolio homepage" alt="Riccardo's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This portfolio feels rich thanks to many small details: the rotating background matrix scattered with geometric figures, the beautiful and colored loading states, the detailed case studies (which list also the color preferences adopted for the clients), plus an experiments section which is a great idea to display creativity and willingness to try, the very essence of a developer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: I found no specific info about the effects implementation on this website, but &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix3d" rel="noopener noreferrer"&gt;matrix3D should do the trick&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://prashantsani.com/" rel="noopener noreferrer"&gt;Prashant Sani&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FPrashant-Sani.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FPrashant-Sani.png" title="Prashant's portfolio homepage" alt="Prashant's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I find the most beautiful thing about this portfolio is its synthwave palette, which is helped by amazing animations to display texts and loading states, plus motivational quotes placed carefully to reflect the personality of Prashant and his work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: The images on this website were realized using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translate3d" rel="noopener noreferrer"&gt;translate3D in CSS&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://mattfarley.ca/" rel="noopener noreferrer"&gt;Matt Farley&lt;/a&gt;
&lt;/h3&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%2Ffy1yi1fi7ngu0lanfwlf.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%2Ffy1yi1fi7ngu0lanfwlf.png" alt="Matt's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Matt's portfolio has a very simple above the folder content, with a witty description of himself and a nice illustration as a profile pic. I enjoyed how he described his skills using a table rather than classing images. He also lists the companies he worked with and specifies his coding experiments, which adds for added respect for him for showing he's a truly passionate developer.&lt;/p&gt;

&lt;p&gt;✅&lt;strong&gt;Pro Idea&lt;/strong&gt;: got some extra cool experiments you tried you could mention on your portfolio? Do it. Always show creativity and inventive .&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://robbowen.digital/" rel="noopener noreferrer"&gt;Rob Owen&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FRob-Owen.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FRob-Owen.png" title="Robb's portfolio homepage" alt="Robb's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rob's portfolio palette makes the entire website feel light and lively. What I appreciated the most about his work was the way he described himself and his work policy, opening up to his personality and adding charisma to the character. His projects section is amazing thanks to relevant projects he created, like &lt;a href="https://marketplace.visualstudio.com/items?itemName=RobbOwen.synthwave-vscode" rel="noopener noreferrer"&gt;Syntwave 84&lt;/a&gt;, the popular Visual Studio Code extension.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: try &lt;a href="https://www.befunky.com/" rel="noopener noreferrer"&gt;Befunky&lt;/a&gt; to turn your website into drawing illustrations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://michaelpumo.com/" rel="noopener noreferrer"&gt;Michael Pumo&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FMichael-Pumo.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FMichael-Pumo.png" title="Michael's portfolio homepage" alt="Michael's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Michael set up a great color palette for his website, adding a unique touch to it thanks to a scrolling effect which shows you his project while keeping his description of himself always highlighted. In addition, I really appreciated his choice of images for his projects, which he accompanies with reviews and logos from his clients to show his expertise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: If you're interested in checking out the source code for this portfolio, check it out &lt;a href="https://github.com/michaelpumo/michaelpumo" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://om-sp.netlify.app/" rel="noopener noreferrer"&gt;Omar Moquete&lt;/a&gt;
&lt;/h3&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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FOmar-Moquete.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%2Fscrimba.com%2Farticles%2Fcontent%2Fimages%2F2023%2F01%2FOmar-Moquete.png" title="Omar's portfolio homepage" alt="Omar's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Omar's portfolio is easy in its structure, yet very polished and with an amazing animation of an IDE greeting visitors, which is what made this creation memorable for me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: If you're interested in how he realized his project, you can visit his open source portfolio project &lt;a href="https://github.com/omar-moquete/porfolio-sharing-version" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://ahmedzougari.com/" rel="noopener noreferrer"&gt;Ahmed Zougari&lt;/a&gt;
&lt;/h3&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%2Fihulk69ikj2agswoid0m.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%2Fihulk69ikj2agswoid0m.png" alt="Ahmed's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahmed's portfolio feels smooth, with a description of himself and a great photo. What makes it standout is his choice of the font "Fingerprint", which makes the entire website feel playful and minimalistic, thanks also to a classic black and white palette.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://alexandros.tech/" rel="noopener noreferrer"&gt;Alex Michailidis&lt;/a&gt;
&lt;/h3&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%2Ffojkj4t49kqm7o86j3y8.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%2Ffojkj4t49kqm7o86j3y8.png" alt="Alex's portfolio homepage" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I loved the most about interacting with Alex's award-winning portfolio was its storytelling. You can follow through with it and click on each one of his experiences to get a full description of what he did. Plus, this website gave me nostalgic feelings, with its color palette reminding me of a Linux terminal, really a well-done creation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tip&lt;/strong&gt;: &lt;a href="https://animejs.com/" rel="noopener noreferrer"&gt;Anime.js&lt;/a&gt; is the library behind this website's animations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key takeaways from these portfolios
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Top portfolios always have a beautiful above the folder content, with a description of the author plus their image or illustration, and a nice background.&lt;/li&gt;
&lt;li&gt;Slick animations placed around portfolios make them memorable.&lt;/li&gt;
&lt;li&gt;Aim for a minimalistic color palette to make your web developer portfolio look neat and elegant.&lt;/li&gt;
&lt;li&gt;Most portfolios are usually a Single Page Application, with the possibility of scrolling to view through the different sections.&lt;/li&gt;
&lt;li&gt;Most portfolios list around four projects.&lt;/li&gt;
&lt;li&gt;Build case studies around your portfolio, explain which challenges you faced and how you went about solving them.&lt;/li&gt;
&lt;li&gt;Remember to always list your skills, but never use percentages to describe your knowledge of a programming language. More info about &lt;a href="https://bit.ly/3vDnQXA" rel="noopener noreferrer"&gt;this common error here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Add testimonials if you have them available.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Creating your web developer portfolio is a serious task, but it all becomes worth it once you have a beautiful website showcasing your personality and skills to potential recruiters, becoming your secret trick to standing out during the hiring process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S&lt;/strong&gt;: do you need a free video guide on how to create your web developer portfolio? &lt;a href="http://bit.ly/3ijLghO" rel="noopener noreferrer"&gt;We have created one for you here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>portfolio</category>
      <category>inspiration</category>
    </item>
    <item>
      <title>11 reasons why you should become a web developer (that aren’t just the high salary)</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Tue, 14 Feb 2023 01:56:54 +0000</pubDate>
      <link>https://dev.to/scrimba/11-reasons-why-you-should-become-a-web-developer-that-arent-just-the-high-salary-6cm</link>
      <guid>https://dev.to/scrimba/11-reasons-why-you-should-become-a-web-developer-that-arent-just-the-high-salary-6cm</guid>
      <description>&lt;p&gt;Interested in becoming a web developer, but not sure if web development is a good career?&lt;/p&gt;

&lt;p&gt;The world of web development can seem intimidating when you're just starting out. Throw post-pandemic economic uncertainty into the mix, and it's understandable if you have concerns about switching careers.&lt;/p&gt;

&lt;p&gt;Some feel that the only thing that makes becoming a developer worth it is the high salary. But in reality, web development can be a hugely rewarding, flexible, and varied career path in its own right.&lt;/p&gt;

&lt;p&gt;Of course, what you're paid will always be a major consideration when it comes to your career (good news then that despite the economic downturn, web development salaries have continued to rise!). But there's so much more to web development than the salary.&lt;/p&gt;

&lt;p&gt;In this post, we've rounded up some of the lesser-known benefits that make web development a career worth considering. Feel free to jump down the page if there's something that catches your eye! 👇&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High demand means plenty of choice when it comes to roles 🌎&lt;/li&gt;
&lt;li&gt;You can choose a career that aligns with your values ✅&lt;/li&gt;
&lt;li&gt;Satisfying work, tangible results 💪🏻&lt;/li&gt;
&lt;li&gt;Opportunities for growth in almost any direction 🚀&lt;/li&gt;
&lt;li&gt;You can express your creativity 🎨&lt;/li&gt;
&lt;li&gt;Lots of opportunities to collaborate and pick up new skills 🤝🏻&lt;/li&gt;
&lt;li&gt;The community spirit! 🍻&lt;/li&gt;
&lt;li&gt;You can work from anywhere 💻&lt;/li&gt;
&lt;li&gt;The flexibility of freelancing 🕺🏻&lt;/li&gt;
&lt;li&gt;It can be really (really!) fun 🎉&lt;/li&gt;
&lt;li&gt;BONUS - It's well paid 💰&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  High demand means plenty of choice when it comes to roles 🌎
&lt;/h2&gt;

&lt;p&gt;Across almost every industry, web developers are in high demand. In the US alone, the number of people employed as developers is &lt;a href="https://www.bls.gov/ooh/computer-and-information-technology/web-developers.htm" rel="noopener noreferrer"&gt;forecast to grow 23% by 2031&lt;/a&gt; — much faster than other occupations.&lt;/p&gt;

&lt;p&gt;Don't get us wrong: it's not always easy to get your foot in the door as a developer. Securing that first opportunity requires hard graft, and you'll need to really put in the hours to get your skills up to scratch. But once you've got a year or so of experience under your belt, you're likely to have a lot more choice available to you than at the same point in another profession.&lt;/p&gt;

&lt;p&gt;Here are just a few examples of where a training as a web developer could take you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating mobile apps for an international game developer 👾&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%2Fzjtvmuookdxsxhwun04r.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%2Fzjtvmuookdxsxhwun04r.png" alt="Anthony Scrimba Student worked on Call of Duty" width="800" height="714"&gt;&lt;/a&gt;&lt;br&gt;Scrimba student Anthony Moreno worked and credited on a Call of Duty title!
  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building banking interfaces at the cutting edge of FinTech 📈&lt;/li&gt;
&lt;li&gt;Developing new learning platforms for schools and universities 🎓&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With so many companies looking for talent, web developers often have lots of freedom to try out different industries and types of organization. Many senior developers, for example, choose to hone their skills at large companies before moving to startups where they can really influence the direction of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can choose a career that aligns with your values ✅
&lt;/h2&gt;

&lt;p&gt;Web development is a hugely transferable skill. There aren't many organizations that don't have a web presence of some kind. So no matter where you start out, if there's an industry you really care about — for example, conservation or education - it's likely you'll be able to move into that area once you get some experience.&lt;/p&gt;

&lt;p&gt;And one of the things that make web development so great to work in is the fact that so many of the best developers started out in completely different careers. The relatively low barrier to entry of frontend web development in particular means that it's entirely possible to retrain and start a new career, even after 10, 15 or 20 years in another role.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Need proof? Check out &lt;a href="https://scrimba.com/podcast/michael-robards-coca-cola/" rel="noopener noreferrer"&gt;our recent podcast&lt;/a&gt; with Michael Robards, a career changer who became a software developer for Coca-Cola at 51!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're in a career at the moment that you don't find fulfilling, web development might be the skill you can use to unlock a role in an area that really matters to you. And your previous experience will definitely come in handy! Skills such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;project management&lt;/li&gt;
&lt;li&gt;problem solving&lt;/li&gt;
&lt;li&gt;data analytics&lt;/li&gt;
&lt;li&gt;design&lt;/li&gt;
&lt;li&gt;team management&lt;/li&gt;
&lt;li&gt;customer research&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... &lt;a href="https://scrimba.com/articles/web-developer-skills-needed/" rel="noopener noreferrer"&gt;and more&lt;/a&gt; are all extremely transferable to a career in web development.&lt;/p&gt;

&lt;p&gt;Long-term job satisfaction is all about balance — for example, between work you care about and work that pays you enough for the lifestyle you want. With such high demand for talent, becoming a web developer gives you enough choice to find the role with the right balance for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Satisfying work, tangible results 💪
&lt;/h2&gt;

&lt;p&gt;We won't lie — the feeling of being able to say 'I made that!' never gets old. From solving &lt;a href="https://www.hackerrank.com/challenges/fizzbuzz/problem" rel="noopener noreferrer"&gt;FizzBuzz&lt;/a&gt; for the first time to building websites that handle millions of visits a day, a career in web development is full of the satisfaction that comes with cracking complex problems.&lt;/p&gt;

&lt;p&gt;And you don't need to train for years before you can start making cool stuff! Once you pick up the basics of frontend development, it won't be long before you can make things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Websites and blogs 💻&lt;/li&gt;
&lt;li&gt;Games and puzzles 🎲&lt;/li&gt;
&lt;li&gt;Calculators and forms 🔢&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you become a web developer, you'll be making tangible products that people use every day — which isn't something you can say about every job out there.&lt;/p&gt;

&lt;p&gt;Of course, the flip side of that is the frustration that comes with struggling to get something to work (or the stress of potentially breaking something that people rely on!). If you're easily frustrated or not a fan of problem-solving, web development might not be the role for you. But if enjoy figuring out puzzles and having the autonomy to work things out for yourself, it can be a seriously rewarding career.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opportunities for growth in almost any direction 🚀
&lt;/h2&gt;

&lt;p&gt;One thing that's sometimes unclear to people before they make the move into web development is what their career progression might look like.&lt;/p&gt;

&lt;p&gt;A standard progression over the course of a career might be something broadly along the lines of: Junior Developer → Senior Developer → Tech Lead → Chief Technical Officer (CTO).&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%2F0xn5z1evkj31l3br6onj.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%2F0xn5z1evkj31l3br6onj.png" alt="General career progression as a developer" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;em&gt;Bear in mind that terminology varies between organizations, and 'Tech Lead' in one company might be 'Principal Developer' in another. Most companies also have several levels to each role, for example 'Level 2 Senior Developer.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But this path is by no means the only one available. Once you become a web developer, a wide range of careers open up to you (including setting up on your own as a freelancer!).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you love working with and coaching people, you might move into &lt;strong&gt;team leadership&lt;/strong&gt; or &lt;strong&gt;mentoring&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If you like getting to grips with customer problems and researching their ideal solutions, you might want to become a &lt;strong&gt;Technical Product Manager&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If you want to craft entire systems from scratch, you could expand your skillset into &lt;strong&gt;full stack development&lt;/strong&gt; (👉 &lt;em&gt;find out more about frontend vs backend vs full stack development &lt;a href="https://scrimba.com/articles/frontend-backend-or-fullstack/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;You could even move across to a lucrative role in &lt;strong&gt;technical writing&lt;/strong&gt;, crafting documentation for complex systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dream of becoming a technical expert who sets the direction of an entire organization? Or want to work closely with customers as an all-in-one consultant? Whatever you want from your career, a solid grounding in frontend web development will set you on the right path to achieve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can express your creativity 🎨
&lt;/h2&gt;

&lt;p&gt;Creativity might not be the first word that many people associate with web development. But it's a much more creative career than the stereotypes would have you believe.&lt;/p&gt;

&lt;p&gt;If you become a web developer, you'll be entering a career where there's no one 'right' way to solve a problem. Creating good software requires ingenuity, imagination, and a healthy dose of flair.&lt;/p&gt;

&lt;p&gt;Web developers work closely with user experience (UX) and user interface (UI) designers to bring a design on a page to life. Developers use &lt;a href="https://scrimba.com/articles/html-css-javascript/" rel="noopener noreferrer"&gt;programming languages like HTML, CSS and JavaScript&lt;/a&gt; to add style, functionality, animation, and more to web interfaces, making them as engaging and user-friendly as possible.&lt;/p&gt;

&lt;p&gt;The best developers are often highly creative thinkers, always thinking of new ways to approach their craft. In fact, more and more web designers are teaching themselves to code, and vice versa. Having both skills in your arsenal means you can design and build products from scratch, opening up a wealth of creative possibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lots of opportunity to collaborate and pick up new skills 🤝
&lt;/h2&gt;

&lt;p&gt;One of the best reasons to become a web developer is the opportunity it gives you to work cross-functionally with different stakeholders across your organization.&lt;/p&gt;

&lt;p&gt;In your day-to-day as a web developer, you're likely to work with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;product manager&lt;/strong&gt; responsible for the vision and direction of what you're building, making sure it meets the needs of the business and of customers.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;UX designer&lt;/strong&gt; responsible for crafting the overall experience of using your product or software.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;UI designer&lt;/strong&gt; responsible for designing the interactive elements of your product (such as buttons and menus) that you then build.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Web developers work collaboratively with these roles to craft products that run smoothly, are user-friendly, and fit the overall aims of the organization.&lt;/p&gt;

&lt;p&gt;Outside of your immediate team, you're also likely to have close contact with customer-facing roles like Customer Success Manager or Integrations Manager. You might be helping to troubleshoot a customer problem, or gathering feedback on your latest release.&lt;/p&gt;

&lt;p&gt;You might also work closely with the product marketing team to help them showcase what you've built!&lt;/p&gt;

&lt;p&gt;If you're looking for a career with plenty of variety and exposure to lots of different business functions, web development could be a really great fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The community spirit! 🍻
&lt;/h2&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%2Fm4brkhajj7xvgau0x9ty.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%2Fm4brkhajj7xvgau0x9ty.png" alt="The Scrimba community" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The global web development community is one of the most vocal, supportive, and fast-growing communities in tech! There are countless Slack groups and Discord servers across the globe that bring web developers together to chat, solve problems and help each other out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Check out our roundup of the &lt;a href="https://scrimba.com/articles/developer-communities/" rel="noopener noreferrer"&gt;&lt;strong&gt;top 9 web developer communities to join&lt;/strong&gt;&lt;/a&gt; to find out more!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It can be a bit counter-intuitive if you come to web development from an academic background, or any environment where googling the answer is considered 'cheating' — but web development thrives on crowdsourced knowledge and sharing solutions to common problems.&lt;/p&gt;

&lt;p&gt;From the moment you take the first step on your journey to becoming a web developer, you'll be part of a community of helpful folks around the world who can keep you motivated, inspired, and continuously learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  You can work from anywhere 💻
&lt;/h2&gt;

&lt;p&gt;Working from home is no longer just an occasional luxury for most people — in fact, &lt;a href="https://www.forbes.com/sites/bryanrobinson/2022/02/01/remote-work-is-here-to-stay-and-will-increase-into-2023-experts-say/" rel="noopener noreferrer"&gt;25% of all professional jobs in the US are now fully remote&lt;/a&gt;. Most career paths now have some provision for hybrid working, either requiring a certain number of in-office days per week or leaving it completely up to the individual to decide.&lt;/p&gt;

&lt;p&gt;The digital-first nature of web development means that it's particularly well-suited to remote working. Even as a Junior Developer, &lt;a href="https://www.youtube.com/watch?v=MyiW7N7-qBs" rel="noopener noreferrer"&gt;you'll have the opportunity to work from home from anywhere in the world.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The abundance of remote-first opportunities makes web development a great career path for a huge range of people, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;👩‍👧‍👦 Parents who want the flexibility to work from home&lt;/li&gt;
&lt;li&gt;👨‍💻 Neurodivergent folk who have a preferred work setup that meets their needs&lt;/li&gt;
&lt;li&gt;✈️ Digital nomads who want to take their work with them on their travels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if a fully remote career isn't on your agenda, don't worry — the demand for in-office tech teams isn't going anywhere either. There are loads of benefits to having a physical workspace, including a better tech setup (second screen, anyone?) and the opportunity to hang out with colleagues you don't work closely with every day.&lt;/p&gt;

&lt;p&gt;Whether you prefer remote, hybrid, or in-office working, the high demand for web developers means that once your career gets going, you'll be able to find the right fit for your style of working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flexibility of freelancing 🕺
&lt;/h2&gt;

&lt;p&gt;Not everyone who becomes a web developer wants to work a 9-5. The good news is, freelance web developers are highly sought-after: demand grew by over 40% on freelance platform &lt;a href="https://www.upwork.com/press/releases/upwork-unveils-top-10-most-in-demand-skills-for-technology-marketing-and-customer-service-independent-talent-in-2022" rel="noopener noreferrer"&gt;Upwork&lt;/a&gt; in the year to 2022.&lt;/p&gt;

&lt;p&gt;Of course, freelancing isn't for everyone. Being employed by a company secures you perks, sick pay, a pension, and all sorts of other useful stuff. But if you want the freedom and flexibility that comes with freelancing — or want to earn some extra money on the side of your day job — becoming a web developer is a great start.&lt;/p&gt;

&lt;p&gt;We recently &lt;a href="https://scrimba.com/podcast/becoming-a-freelance-developer-with-gary-simon/" rel="noopener noreferrer"&gt;interviewed&lt;/a&gt; designer, developer and Scrimba teacher &lt;a href="https://scrimba.com/teachers/designcourse" rel="noopener noreferrer"&gt;Gary Simon&lt;/a&gt; about how he grew his freelance business from $3k a month to $100k a year. To future-proof yourself as a freelancer, Gary says, it's all about &lt;strong&gt;continuous learning&lt;/strong&gt;. Whether it's staying active on &lt;a href="https://scrimba.com/articles/developer-communities/" rel="noopener noreferrer"&gt;developer communities&lt;/a&gt; or refreshing yourself on how to rank highly for your services on Google, success as a freelancer requires a constant commitment to learning new things.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 If you want to hear more from web developers who made it work as a freelancer, check out these episodes of the Scrimba podcast 👇&lt;/p&gt;

&lt;p&gt;️🎙️ &lt;a href="https://scrimba.com/podcast/start-freelancing-no-experience/" rel="noopener noreferrer"&gt;Get started freelancing on Upwork with no experience&lt;/a&gt;&lt;br&gt;
🎙️ &lt;a href="https://scrimba.com/podcast/becoming-a-freelance-developer-with-gary-simon/" rel="noopener noreferrer"&gt;Becoming a six figure freelancer with Gary Simon&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  It can be really (really!) fun 🎉
&lt;/h2&gt;

&lt;p&gt;Web development can be hard work — it wouldn't be so well-paid if it wasn't! — but for most developers, there's absolutely no shortage of fun and exciting moments to keep you engaged.&lt;/p&gt;

&lt;p&gt;Web development is evolving at an incredibly fast pace, which means you never stop learning. From the increased accessibility of AI to exciting new developments in PWA (progressive web application) technology, there's constantly something new to discover and add to your practice.&lt;/p&gt;

&lt;p&gt;It's definitely the case that no two days are the same for a web developer. This is a career that requires constant testing and innovation to adapt to fast-changing technology and consumer behavior. And that means there are plenty of opportunities to have fun as you build (and break!) brand-new systems on a day-to-day basis.&lt;/p&gt;

&lt;p&gt;A career in development is a uniquely rewarding mix of good pay, good perks, and enjoyable, varied projects to work on (which might be why nearly 24,000 students have taken Scrimba's &lt;a href="https://scrimba.com/learn/frontend" rel="noopener noreferrer"&gt;Frontend Developer Career Path&lt;/a&gt; to date!).&lt;/p&gt;

&lt;h2&gt;
  
  
  BONUS - It's well paid 💰
&lt;/h2&gt;

&lt;p&gt;Okay, we said we wouldn't mention salaries — but this one bears repeating. Starter salaries for web developers are good (often great), and there's potential for it to be an incredibly high-earning career.&lt;/p&gt;

&lt;p&gt;We take a deep-dive into salary data in our in-depth &lt;a href="https://scrimba.com/articles/how-to-become-web-developer-guide/" rel="noopener noreferrer"&gt;guide to becoming a developer in 2023&lt;/a&gt;, but to give you a quick idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Average web developer salary in the USA 🇺🇸: &lt;strong&gt;$53,628-$107,028&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Average web developer salary in the UK 🇬🇧: &lt;strong&gt;£25,690-£49,434&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Average web developer salary in Australia 🇦🇺: &lt;strong&gt;A$57,500-A$100,000&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And those are just national averages: if you're, say, a Senior Developer working in London, you're likely to be earning a whole lot more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👉 Curious to know why web development is such a high-earning career? We recently took a &lt;a href="https://scrimba.com/articles/why-web-developers-get-paid-so-much/" rel="noopener noreferrer"&gt;deep-dive into web development salaries&lt;/a&gt; on the blog, but at a high level it boils down to: &lt;strong&gt;high demand&lt;/strong&gt;, a &lt;strong&gt;technical skillset&lt;/strong&gt;, and significant levels of &lt;strong&gt;responsibility&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;2023 is a great year to become a web developer. It's a growing field, there are more remote opportunities than ever before, and salaries are the best they've ever been.&lt;/p&gt;

&lt;p&gt;If you want to find out more about how to become a web developer in 2023, we'd recommend checking out our &lt;a href="https://scrimba.com/articles/how-to-become-web-developer-guide/" rel="noopener noreferrer"&gt;complete guide&lt;/a&gt;. In there, you'll find loads more information on web development as a career and how to get started.&lt;/p&gt;

&lt;p&gt;And if you're ready to jump in and start learning, our &lt;a href="https://scrimba.com/learn/frontend" rel="noopener noreferrer"&gt;Frontend Development Career Path&lt;/a&gt; is designed and created by industry-leading developers to transform beginners into hireable web developers. You'll learn everything from the basics of web dev to advanced interview techniques, all for the fraction of the price of a coding bootcamp.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
      <category>developer</category>
    </item>
    <item>
      <title>6 Reasons to learn and use React in 2023</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Sat, 11 Feb 2023 02:09:26 +0000</pubDate>
      <link>https://dev.to/scrimba/6-reasons-to-learn-and-use-react-in-2023-19ij</link>
      <guid>https://dev.to/scrimba/6-reasons-to-learn-and-use-react-in-2023-19ij</guid>
      <description>&lt;p&gt;When Meta (then Facebook) announced React in 2013, it took web developers by storm.&lt;/p&gt;

&lt;p&gt;Ten years later, it's natural to wonder: &lt;strong&gt;Should I learn React in 2023?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;yes, absolutely&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;React is still launching new features all the time and has the weight of Meta and their brilliant, dedicated engineering team behind it.&lt;/p&gt;

&lt;p&gt;The ecosystem (think add-on libraries and documentation) has reached a point of maturity, and in most places around the world, there are usually more React jobs than Angular, Vue, or Svelte.&lt;/p&gt;

&lt;p&gt;Here are 6 more reasons React is still popular in 2023.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/why-use-react/#react-isn-t-going-anywhere-anytime-soon" rel="noopener noreferrer"&gt;React isn't going anywhere anytime soon&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/why-use-react/#react-has-great-features" rel="noopener noreferrer"&gt;React has great features&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/why-use-react/#react-can-power-any-app" rel="noopener noreferrer"&gt;React can power any app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/why-use-react/#there-are-lots-of-react-jobs" rel="noopener noreferrer"&gt;There are lots of React jobs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍&lt;a href="https://scrimba.com/articles/why-use-react/#react-is-on-the-cutting-edge" rel="noopener noreferrer"&gt;React is on the cutting-edge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍&lt;a href="https://scrimba.com/articles/why-use-react/#it-s-just-javascript" rel="noopener noreferrer"&gt;It's "just" JavaScript&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  React isn't going anywhere anytime soon
&lt;/h2&gt;

&lt;p&gt;When React came out in 2013, it popularised the idea of breaking down your app into components:&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%2Fjjldao90vuwl79bzzl7e.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%2Fjjldao90vuwl79bzzl7e.png" alt="Browser and UI Tree" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was a bit unorthodox at the time (the best ideas usually are), but developers eventually warmed up the idea they could split the UI into independent, reusable pieces because working on each piece in isolation made them productive.&lt;/p&gt;

&lt;p&gt;Working with components in this way meant teams could adopt React piece by piece. Instead of converting their &lt;em&gt;whole&lt;/em&gt; application at once (which would put new features on pause and take a lot of time), developers could convert a component here, a component there, and reap the benefits of React incrementally.&lt;/p&gt;

&lt;p&gt;When I wrote that "React took web developers by storm," this is exactly how! Year after year, bit by bit, apps incrementally converted to React.&lt;/p&gt;

&lt;p&gt;Around the time React was born web apps were becoming more dynamic.&lt;/p&gt;

&lt;p&gt;Components are great for managing this newfound complexity, so developers would often react for React when working on new projects as well.&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%2Ffe7q97sd6nsdmorwyjnj.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%2Ffe7q97sd6nsdmorwyjnj.png" alt="Static versus dynamic website example" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This unique set of circumstances (and the influence of React's creators, Meta) allowed developers to adopt React in a unique way that a new framework or library would struggle to emulate today.&lt;/p&gt;

&lt;p&gt;Ten years later, React is tried and true, and there hasn't really been a compelling reason for developers, their teams, or companies to use a new frontend &lt;a href="https://scrimba.com/articles/difference-between-framework-and-library/" rel="noopener noreferrer"&gt;library or framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you learn React in 2023, you can bet it's not going anywhere soon!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Will Svelte take over?&lt;/p&gt;

&lt;p&gt;You might have heard of a React competitor called Svelte. Is it a threat to React?&lt;/p&gt;

&lt;p&gt;We &lt;a href="https://scrimba.com/articles/svelte-vs-react/" rel="noopener noreferrer"&gt;compared Svelte and React&lt;/a&gt; and concluded Svelte is honestly really good! It allows you to achieve the same things as React, albeit in a more concise way. &lt;strong&gt;Here's why Svelte doesn't hold a candle to React.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Svelte is a bit better, but it's not so much better that teams will rewrite their apps and learn new technology.  React also has more libraries, including &lt;a href="https://reactnative.dev/" rel="noopener noreferrer"&gt;React Native&lt;/a&gt;, which allows you to build and deploy native-feeling iOS and Android applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  React has great features
&lt;/h2&gt;

&lt;p&gt;React is fast, modular, and scalable. Without getting too technical, some of the top features include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A slight learning curve (if you know JavaScript already)&lt;/li&gt;
&lt;li&gt;Reusable components&lt;/li&gt;
&lt;li&gt;Data binding&lt;/li&gt;
&lt;li&gt;High-performance thanks to the vDOM 📈&lt;/li&gt;
&lt;li&gt;Support for web and mobile apps with React Native&lt;/li&gt;
&lt;li&gt;Dedicated tools like React Developer Tools for easy debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React also supports advanced features you might not need today but you'll be glad for later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server-side rendering&lt;/li&gt;
&lt;li&gt;Efficient data-fetching&lt;/li&gt;
&lt;li&gt;Caching&lt;/li&gt;
&lt;li&gt;Error-handling&lt;/li&gt;
&lt;li&gt;SEO-friendly&lt;/li&gt;
&lt;li&gt;Dedicated deployment tools to make your live website as efficient as possible&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  React can power any app
&lt;/h2&gt;

&lt;p&gt;If you learn React, you can build from basic apps to amazing dashboards. React can be used to create any type of web application, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Social media platforms like Facebook and Instagram&lt;/li&gt;
&lt;li&gt;E-commerce websites like Amazon and Etsy&lt;/li&gt;
&lt;li&gt;Project management tools like Asana and Trello&lt;/li&gt;
&lt;li&gt;Dashboards for data visualization and analytics like Google Analytics and Mixpanel&lt;/li&gt;
&lt;li&gt;Gaming platforms like Angry Birds and Candy Crush&lt;/li&gt;
&lt;li&gt;Video streaming websites like YouTube and Netflix&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Companies using React:&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%2Fe5i36nozfhj875vs7bkh.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%2Fe5i36nozfhj875vs7bkh.png" alt="Companies that use React" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  There are lots of React jobs
&lt;/h2&gt;

&lt;p&gt;According to the &lt;a href="https://insights.stackoverflow.com/survey/2021#section-most-loved-dreaded-and-wanted-web-frameworks" rel="noopener noreferrer"&gt;Stack Overflow Developer Survey for 2021&lt;/a&gt;, React was the most popular frontend JavaScript library, with 63.5% of respondents saying they had used it. It was also the fourth most popular technology overall, with 74.5% of respondents saying that they had worked with it 🤯&lt;/p&gt;

&lt;p&gt;This shows that there is strong demand for developers who are proficient in React, and this demand will likely continue in the future.&lt;/p&gt;

&lt;p&gt;You should also know the average salary for a React developer in the United States is &lt;a href="https://www.google.com/url?q=https://www.glassdoor.com/Salaries/react-developer-salary-SRCH_KO0,15.htm&amp;amp;sa=D&amp;amp;source=docs&amp;amp;ust=1674239800937702&amp;amp;usg=AOvVaw2CjR1H-6sRfuBVj9sf0Yjk" rel="noopener noreferrer"&gt;$92,419&lt;/a&gt; per year, according to data from Glassdoor. This is higher than the average salary for a developer with experience in other frontend technologies, such as Angular or Vue.&lt;/p&gt;

&lt;p&gt;Overall, learning React is likely to be a valuable investment for frontend developers in 2023 and beyond.&lt;/p&gt;

&lt;h2&gt;
  
  
  React is on the cutting-edge
&lt;/h2&gt;

&lt;p&gt;Even though React is ten years old, it's not exactly long in the tooth 🦷!&lt;/p&gt;

&lt;p&gt;Every year, React releases updates to support the evolving demands of modern web developers.&lt;/p&gt;

&lt;p&gt;React Native, a framework for building native mobile applications using React has gained popularity in recent years and is likely to power more and more mobile applications every year.&lt;/p&gt;

&lt;p&gt;Moreover, new frameworks like &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js by Vercel&lt;/a&gt; is built on React are gaining popularity. This trend heavily suggests continued interest and investment in the React ecosystem, which will likely contribute to its future growth and development ♻️!&lt;/p&gt;

&lt;h2&gt;
  
  
  It's "just" JavaScript
&lt;/h2&gt;

&lt;p&gt;One thing that's nice about React is that it's "just" JavaScript, meaning if you already know the fundamentals of JavaScript, it might be easier to learn React than you think!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👀 Suggested reading: &lt;a href="https://scrimba.com/articles/how-much-javascript-before-react/" rel="noopener noreferrer"&gt;How much JavaScript do you need to know before learning React?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;React uses JSX, a syntax extension for JavaScript, which allows you to write HTML-like code in your JavaScript.&lt;/p&gt;

&lt;p&gt;If you already know HTML, this will make it much easier to learn React, as the syntax is familiar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
function Hola(props) {
    return &amp;lt;h1&amp;gt;Hola, {props.name}!&amp;lt;/h1&amp;gt;
}

export default Hola;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;Hola&lt;/code&gt; component is a function that takes a &lt;a href="https://scrimba.com/articles/react-props/" rel="noopener noreferrer"&gt;props&lt;/a&gt; object as an argument and returns a JSX element.&lt;/p&gt;

&lt;p&gt;Knowing JavaScript will give you a head start, but it's important to note that React is a large and complex library, and it can take time and practice to become proficient in it.&lt;/p&gt;

&lt;p&gt;Give yourself the time you need to fully understand the concepts and build your skills.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;👀 Suggested reading: &lt;a href="https://scrimba.com/articles/how-long-to-learn-react/" rel="noopener noreferrer"&gt;How long to learn React?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;After considering the various technical and practical benefits of learning React, as well as the current state of the job market and the strong community of developers who use and support the library, we think it's safe to say React is a solid investment for any developer looking to expand their skills and advance their career.&lt;/p&gt;

&lt;p&gt;If you are a developer who is interested in learning React, here's my best advice as a Senior React developer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with the fundamentals&lt;/strong&gt; Make sure you have a solid understanding of the core concepts of React, such as components, state, and props, before moving on to more advanced topics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice, practice, practice&lt;/strong&gt; The best way to learn any new technology is to build things with it. Set aside time to work on projects and exercises, and don't be afraid to make mistakes and learn from them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join the community&lt;/strong&gt; React has a large and active community of developers who are always willing to help and share their knowledge. Consider joining forums, meetups, or online communities to connect with other React developers and learn from them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep learning&lt;/strong&gt; React is a rapidly evolving library, and there is always more to learn. Make sure to keep up with the latest developments in the React ecosystem and continue to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many resources available for learning React, including online tutorials, video courses, and books. You are reading the Scrimba blog so of course we think you should try our free courses 😉:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://scrimba.com/learn/learnreact" rel="noopener noreferrer"&gt;Learn React for free&lt;/a&gt; is a beginner-level course that covers the fundamentals of React, including components, state, and props. This course comes with structured lessons, experienced instructors (it's the head of Scrimba, in fact!), and hands-on exercises.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://scrimba.com/learn/react" rel="noopener noreferrer"&gt;Learn advanced React&lt;/a&gt;💰 is an intermediate-level course that covers more advanced topics in React, such as hooks, performance optimization, and testing. You need to make sure your existing knowledge is rock-hard, and this course paves a great way for that and takes them to the next level!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some other popular options for learning React include :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://reactjs.org/docs/getting-started.html" rel="noopener noreferrer"&gt;The official React documentation&lt;/a&gt; is a comprehensive resource that covers all aspects of React, including core concepts, API references, and examples.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.freecodecamp.org/news/free-react-course-2022/" rel="noopener noreferrer"&gt;FreeCodeCamp's React course for 2022&lt;/a&gt; is a free, self-paced online course that covers the fundamentals of React, including components, state, and props.&lt;/li&gt;
&lt;li&gt;Fullstack React: The Complete Guide to ReactJS and Friends 💰: The Complete Guide to ReactJS and Friends is a paid book that covers the fundamentals of React and related technologies such as Redux and GraphQL.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>2023</category>
    </item>
    <item>
      <title>Will AI like ChatGPT replace programmers?</title>
      <dc:creator>Alex Booker</dc:creator>
      <pubDate>Tue, 07 Feb 2023 13:35:50 +0000</pubDate>
      <link>https://dev.to/scrimba/will-ai-like-chatgpt-replace-programmers-50a1</link>
      <guid>https://dev.to/scrimba/will-ai-like-chatgpt-replace-programmers-50a1</guid>
      <description>&lt;p&gt;ChatGPT. Everyone's talking about it. There's tons of content where people interact with ChatGPT in amazingly creative and even frivolous ways.&lt;/p&gt;

&lt;p&gt;ChatGPT can help you plan your day, write an essay, and even make you some money (at least according to so many Tiktok videos).&lt;/p&gt;

&lt;p&gt;Developers have not been left behind. For example, &lt;em&gt;joshsowin&lt;/em&gt; on Tiktok asked ChatGPT to &lt;a href="https://www.tiktok.com/@joshsowin/video/7175217410104954158?_r=1&amp;amp;_t=8Z9nTEwenvJ" rel="noopener noreferrer"&gt;code an analog clock&lt;/a&gt;, and &lt;em&gt;A.I. Financial&lt;/em&gt; was able to create a &lt;a href="https://www.tiktok.com/@financial.ai/video/7183876471553821995?_r=1&amp;amp;_t=8Z9obAz1x5S" rel="noopener noreferrer"&gt;predictive script for Bitcoin trading&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before you get all giddy, the big question is: just like &lt;a href="https://www.tiktok.com/@asap_blockie/video/7175079712971033857?_r=1&amp;amp;_t=8Z9okSLOzdF" rel="noopener noreferrer"&gt;&lt;em&gt;A$AP Blockie&lt;/em&gt;&lt;/a&gt; on TikTokasks, will AI take your coding job?&lt;/p&gt;

&lt;p&gt;Part of your job as a JavaScript developer involves "CRUD" operations: Create, Read, Update, and Delete. It has to do with sending and retrieving user data.&lt;/p&gt;

&lt;p&gt;"Will AI replace JavaScript developers?" or at least part of their job? Bearing in mind that CRUD operations are overly repetitive, and AI automates repetitive tasks?&lt;/p&gt;

&lt;p&gt;Let's go on a journey to explore the question, "Will AI make programmers obsolete?" and more.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/will-ai-replace-programmeres/#so-will-ai-replace-javascript-developers" rel="noopener noreferrer"&gt;Will AI replace JavaScript developers?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/will-ai-replace-programmeres/how-does-ai-write-code" rel="noopener noreferrer"&gt;How does AI write code?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/will-ai-replace-programmeres/#examples-of-programming-tasks-ai-is-already-doing-well" rel="noopener noreferrer"&gt;Examples of programming tasks AI is already doing well&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/will-ai-replace-programmeres/what-limitations-does-ai-have" rel="noopener noreferrer"&gt;What limitations does AI have?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/will-ai-replace-programmeres/#will-learning-to-code-be-worth-it-in-the-near-future" rel="noopener noreferrer"&gt;Will learning to code be worth it in the near future?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📍 &lt;a href="https://scrimba.com/articles/will-ai-replace-programmeres/#the-verdict" rel="noopener noreferrer"&gt;The verdict&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So, will AI replace JavaScript developers?
&lt;/h2&gt;

&lt;p&gt;There are two schools of thought here. There are people who see AI as another tool in developers' arsenals to help increase productivity.&lt;/p&gt;

&lt;p&gt;On the other hand, the second school of thought is that AI will get so good that it will replace programmers. Hello ChatGPT?&lt;/p&gt;

&lt;p&gt;Let's think about a scenario where a JavaScript developer needs to use a code generator or assistant like ChatGPT, AlphaCode, Co-Pilot, Codex, CodeQL, SonarQube, or Coverity.&lt;/p&gt;

&lt;p&gt;Let's ask ChatGPT to generate code to connect our database to our endpoints.&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%2F4n24bem3q0vr24gigasw.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%2F4n24bem3q0vr24gigasw.png" alt="a screenshot asking ChatGPT to generate code to connect our database to our endpoints" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT prompts us back, asking for more information about our specific use case and the technology stack.&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%2Fweb0q8vzvsikv7oyfcs4.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%2Fweb0q8vzvsikv7oyfcs4.png" alt="a screenshot of ChatGPT prompting us back, asking for more information about our specific use case and the technology stack" width="800" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The assistant generates the code snippet, but you still have to tweak it to fit your particular use case, for example, by taking care of the specific versions of the tools you are using.&lt;/p&gt;

&lt;p&gt;Depending on whether your code is in development or production, you might also want to make sure that sensitive information is hidden.&lt;/p&gt;

&lt;p&gt;You could have gotten the same information from Stack Overflow. The only difference is that with ChatGPT, you don't get to read through several answers.&lt;/p&gt;

&lt;p&gt;Let's further explore the question, "Will AI make programmers obsolete?"&lt;/p&gt;

&lt;h2&gt;
  
  
  How does AI write code?
&lt;/h2&gt;

&lt;p&gt;If all this talk about AI writing its own code sounds complex or confusing, let's take a step back and have a look at the "behind the scenes" of how it all works.&lt;/p&gt;

&lt;p&gt;Before we define AI, let's talk about programming. Programming is the process of writing instructions for a computer to follow so as to achieve a particular goal.&lt;/p&gt;

&lt;p&gt;This set of instructions is known as a "program."&lt;/p&gt;

&lt;p&gt;You can write a program for an e-commerce site like Amazon. For example. The instructions would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A customer logs in.&lt;/li&gt;
&lt;li&gt;Goes through the item catalog.&lt;/li&gt;
&lt;li&gt;Chooses an item.&lt;/li&gt;
&lt;li&gt;Adds it to the cart.&lt;/li&gt;
&lt;li&gt;Checks out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the above instructions to be carried out, a lot of things need to take place. The user details need to be confirmed, for example.&lt;/p&gt;

&lt;p&gt;The instructions, therefore, need to be very specific.&lt;/p&gt;

&lt;p&gt;AI, or artificial intelligence, can be seen as "advanced programming." In addition to writing instructions for a computer to follow, we also "teach" it some human intelligence. For example, we "teach" AI to identify cat images.&lt;/p&gt;

&lt;p&gt;This happens through a very complex process known as machine learning (ML). In ML, there are models (which represent real-world concepts). Programmers train these models using real-world data.&lt;/p&gt;

&lt;p&gt;For example, a programmer can train a machine learning model to identify cat images. To do so, the model has to "see" lots of cat images. Over time, the model will identify the patterns that help it distinguish between a cat and something that is not a cat (something called "unsupervised learning").&lt;/p&gt;

&lt;p&gt;The same thing happens with code — a model goes through lots of code or is "trained" via large amounts of code. Over time, it begins to identify the patterns that "define" code. Eventually, it will be able to generate some code.&lt;/p&gt;

&lt;p&gt;This is what happened with ChatGPT. ChatGPT is basically a machine learning model that was trained with a lot of data from the internet, including code. The model on which ChatGPT was trained is called GPT-3 (Generative Pre-trained Transformer 3).&lt;/p&gt;

&lt;p&gt;It then used an ML concept called reinforcement learning, where models are "rewarded" for accuracy and consistency.  &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%2Fes43ozx1xj2au6ulw74p.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%2Fes43ozx1xj2au6ulw74p.png" alt="Screenshot showing ChatGPT Limitation" width="800" height="719"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, there were also human AI trainers that helped frame conversations between humans and an AI. The conversations were then "fed" into the models. This is referred to as Reinforcement Learning from Human Feedback (RLHF).&lt;/p&gt;

&lt;p&gt;ChatGPT is currently available for free in the research phase.&lt;/p&gt;

&lt;p&gt;Now that we know how AI writes code, will AI replace JavaScript developers? Let's keep going to find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of programming tasks AI is already doing well
&lt;/h2&gt;

&lt;p&gt;Now that AI is more advanced and there's more infrastructure to handle large data sets, will AI make programmers obsolete?&lt;/p&gt;

&lt;p&gt;In this section, we look at some programming tasks that AI is doing well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coding competitions
&lt;/h3&gt;

&lt;p&gt;Coding competitions attract lots of developers, and for good reason. They are a good way for programmers to bolster their problem-solving skills. This way, they end up becoming better at their craft.&lt;/p&gt;

&lt;p&gt;Moreover, there are also enviable prizes to be won.&lt;/p&gt;

&lt;p&gt;AlphaCode is an ML model built for coding competitions. This far, it has ranked among the top 54% in coding competitions on sites like Codeforces, where there are tens of thousands of participants.&lt;/p&gt;

&lt;p&gt;Who knows, AI might soon change how hackathons — coding competitions where you get to build a solution within a very short period of time (between 2 and 3 days) — are done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;p&gt;Debugging is a necessary part of writing code. This is because developers need to deliver code that works according to a specified user story.&lt;/p&gt;

&lt;p&gt;Remember our "list of instructions" for an e-commerce site? They can be rewritten into user stories, which are more customer-focused.&lt;/p&gt;

&lt;p&gt;Instead of "a customer logs in," we can have something like "as Jaime, I would like to be able to log in to my account."&lt;/p&gt;

&lt;p&gt;User stories and debugging are some coding best practices that every developer needs to learn in order to deliver a great product.&lt;/p&gt;

&lt;p&gt;You can imagine what would happen if anyone was able to log in using Jaime's account. They could steal their credit card information, for example.&lt;/p&gt;

&lt;p&gt;Let's not even get started with all the regulations around PII (personally identifiable information), like GDPR.&lt;/p&gt;

&lt;p&gt;This is why debugging is important. It helps to identify aspects of your code you might have overlooked, for example, confirming Jaime's credentials before logging in.&lt;/p&gt;

&lt;p&gt;The only downside to debugging is that it can get quite time-consuming. While writing tests (another programming best practice) helps with debugging, sometimes a developer can get absolutely "stuck."&lt;/p&gt;

&lt;p&gt;They may not be able to identify an error or how to fix it. They spend hours on the internet and on sites like Stack Overflow trying to find out whether other people have experienced similar errors and how to solve them.&lt;/p&gt;

&lt;p&gt;The good news is that with AI tools like ChatGPT and SonarQube (a code review tool), debugging becomes a faster process.&lt;/p&gt;

&lt;p&gt;AI can therefore be used for pair programming, where it helps you write and debug code.&lt;/p&gt;

&lt;h4&gt;
  
  
  Let's look at a ChatGPT debugging example.
&lt;/h4&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%2Fub5kaypumi45ol2hvoky.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%2Fub5kaypumi45ol2hvoky.png" alt="a screenshot of ChatGPT Generating code" width="800" height="861"&gt;&lt;/a&gt;&lt;br&gt;Image source: &lt;a href="https://twitter.com/amasad/status/1598042665375105024/photo/1" rel="noopener noreferrer"&gt;https://twitter.com/amasad/status/1598042665375105024/photo/1&lt;/a&gt;&lt;br&gt;

  &lt;/p&gt;

&lt;p&gt;A practical use case of the above example is where you might have just learned about the JavaScript &lt;code&gt;setTimeout()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;You may not understand the output (why it keeps printing "5"). Don't you have a for loop with a condition that checks the value of &lt;em&gt;i&lt;/em&gt; every time, (if &lt;em&gt;i&lt;/em&gt; &amp;lt; 5)?&lt;/p&gt;

&lt;p&gt;Why does it print 5 in the first place, yet the loop should stop when the condition is no longer being met?&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;setTimeout()&lt;/code&gt; method sets a timer to execute a piece of code when the timer expires.&lt;/p&gt;

&lt;p&gt;As you can see, ChatGPT not only points out the bug or error but also suggests a way to fix it.&lt;/p&gt;

&lt;p&gt;Note that ChatGPT was not built as a debugger. It is a model that is meant to generate language and code (since code is written in programming languages).&lt;/p&gt;

&lt;p&gt;The fact that you can "push" it to debug code means that we already have a framework and blueprint for an AI that can eventually be built to specialize in debugging and generating code.&lt;/p&gt;

&lt;p&gt;This would make debugging way faster and easier in the near future, saving developers copious amounts of time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code generation
&lt;/h3&gt;

&lt;p&gt;As we have seen in the previous example, AI can help you debug code. In addition, it can generate code from prompts written in human language.&lt;/p&gt;

&lt;h4&gt;
  
  
  A ChatGPT code generation example
&lt;/h4&gt;

&lt;p&gt;Let's ask &lt;a href="https://chat.openai.com/" rel="noopener noreferrer"&gt;ChatGPT&lt;/a&gt; to generate a code snippet that can help run the Fibonacci sequence. Its response:&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%2Flm4zrcw8aytqf13xho1n.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%2Flm4zrcw8aytqf13xho1n.png" alt="a screenshot of ChatGPT Generating code" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The follow-up prompt was to ask ChatGPT to generate a code snippet for the Fibonacci sequence. It did so in Python.&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%2Fj4osttw98f91npc6gxr9.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%2Fj4osttw98f91npc6gxr9.png" alt="a screenshot of ChatGPT Generating code" width="800" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, it generated a code snippet in Python and explained the code precisely — the different components, and expected output.&lt;/p&gt;

&lt;p&gt;Next prompt: convert the code snippet from Python to Javascript.&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%2Fl8kv8e886z9tbcddlhgi.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%2Fl8kv8e886z9tbcddlhgi.png" alt="a screenshot of ChatGPT Generating code" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, ChatGPT converted the code from Python to Javascript and explained it too, stating the expected output.&lt;/p&gt;

&lt;h2&gt;
  
  
  What limitations does AI have?
&lt;/h2&gt;

&lt;p&gt;We have learned that AI can do some cool and phenomenal things. However, will AI replace web developers?&lt;/p&gt;

&lt;p&gt;The good news is that it won't happen. At least not yet. This is because there are a few things AI has yet to figure out and some challenges it has yet to overcome. Let's explore them further.&lt;/p&gt;

&lt;h3&gt;
  
  
  Legal issues
&lt;/h3&gt;

&lt;p&gt;When an AI uses data from the internet as part of its training data set, there are bound to be legal issues, especially with copyright, intellectual property, and licenses. For example, Codex's neural network (the OpenAI model Copilot is based on) was trained via billions of lines of code stored on GitHub in order to learn how to write code.&lt;/p&gt;

&lt;p&gt;We don't know whether it only used code from public GitHub repositories or under free distribution licenses.&lt;/p&gt;

&lt;p&gt;Recent research from MIT, on the other hand, shows that &lt;a href="https://news.mit.edu/2022/synthetic-data-ai-improvements-1103" rel="noopener noreferrer"&gt;machine learning models trained with synthetic data&lt;/a&gt; can sometimes be more accurate than those trained with real-world data.&lt;/p&gt;

&lt;p&gt;This could help with copyright issues. It would raise problems like working excellently in isolated cases (something referred to as "local maxima") but failing miserably at generalization.&lt;/p&gt;

&lt;p&gt;For accurate results, a good ML model must be able to generalize data well.&lt;/p&gt;

&lt;p&gt;Let's delve into accuracy when it comes to AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accuracy
&lt;/h3&gt;

&lt;p&gt;AI models like ChatGPT do give incorrect responses. In fact, in December 2022, &lt;a href="https://www.theverge.com/2022/12/5/23493932/chatgpt-ai-generated-answers-temporarily-banned-stack-overflow-llms-dangers" rel="noopener noreferrer"&gt;answers from ChatGPT were temporarily banned from StackOverflow&lt;/a&gt; due to inaccuracy.&lt;/p&gt;

&lt;p&gt;The AI does caution about its accuracy limitations, though.&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%2F29o7of9uwyfxrh4fjb49.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%2F29o7of9uwyfxrh4fjb49.png" alt="Screenshot showing ChatGPT Limitation" width="800" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The AI gives inconsistent responses as you tweak consecutive prompts. Besides, even when it's plainly wrong, it sounds really confident.&lt;/p&gt;

&lt;p&gt;Another aspect that affects chatGPT's accuracy is the fact that it was trained with data until the beginning of 2022.&lt;/p&gt;

&lt;p&gt;This makes it impossible to show current data. If you ask for current information, it says that it is unable to search the internet.&lt;/p&gt;

&lt;p&gt;In addition, ChatGPT responses are inherently biased. It can, as a result, respond to harmful prompts or give inappropriate responses.&lt;/p&gt;

&lt;p&gt;ChatGPT is also too wordy and uses the same phrases over and over, like saying again and again that it is a language model trained by OpenAI.&lt;/p&gt;

&lt;p&gt;The company behind ChatGPT, OpenAI, on the other hand, is eager to address its model's limitations. This could hopefully resolve the inaccuracies and biases in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  The need for human input
&lt;/h3&gt;

&lt;p&gt;While AI is doing &lt;a href="https://www.tiktok.com/@quiet_coder12/video/7184091274708864283?_r=1&amp;amp;_t=8Yuj8KTrjXi&amp;amp;is_from_webapp=v1&amp;amp;item_id=7184091274708864283" rel="noopener noreferrer"&gt;a great job at code generation&lt;/a&gt;, it still needs input from humans to write code that actually works.&lt;/p&gt;

&lt;p&gt;For example, when ChatGPT was asked to &lt;a href="https://www.tiktok.com/@quiet_coder12/video/7184091274708864283?_r=1&amp;amp;_t=8Yuj8KTrjXi&amp;amp;is_from_webapp=v1&amp;amp;item_id=7184091274708864283" rel="noopener noreferrer"&gt;write a game in pure Javascript, HTML, and CSS&lt;/a&gt;, it did not give a complete response when asked to add the specified functions.&lt;/p&gt;

&lt;p&gt;Moreover, it still needed more prompts to generate code that worked as expected.&lt;/p&gt;

&lt;p&gt;So, will AI replace web developers? Not really.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will learning to code be worth it in the near future?
&lt;/h2&gt;

&lt;p&gt;Despite the advancements that we are seeing in AI, coding will still be an important skill in the near future.&lt;/p&gt;

&lt;p&gt;Let's imagine for a moment that an AI like ChatGPT can write perfect code.&lt;/p&gt;

&lt;p&gt;First, we'll still need humans who understand code to write appropriate prompts for AI to use. Secondly, a programmer needs to "verify" that the generated code actually works for their particular use case.&lt;/p&gt;

&lt;p&gt;Thirdly, we need humans to think about other aspects of the software that is being built---the user experience and other constraints that may affect performance and scalability.&lt;/p&gt;

&lt;p&gt;For example, programmers will need to make decisions on the best software combination to go with to ensure that it can handle traffic spikes.&lt;/p&gt;

&lt;p&gt;In short, there are things AI cannot do yet, so learning to code is still a great idea. So, will AI replace JavaScript developers? Highly unlikely.&lt;/p&gt;

&lt;p&gt;Code generation tools are "assistants." There's a reason why Github's code-generating tool is called Co-pilot. It is helping you "navigate" through your code, rather than taking over the pilot's seat.&lt;/p&gt;

&lt;p&gt;As we mentioned, AI would work great for pair programming. You become the navigator, instructing it, and it generates code as the driver.&lt;/p&gt;

&lt;p&gt;We cannot ignore the fact that AI is getting better at learning. Who knows? We might see a decrease in demand for coding skills.&lt;/p&gt;

&lt;p&gt;We might actually be very wrong. There's a book called "&lt;a href="https://www.amazon.com/Optimism-Bias-Irrationally-Positive-Brain/dp/0307473511" rel="noopener noreferrer"&gt;Optimism Bias&lt;/a&gt;" by Tali Sharot, and it scientifically explains why humans are wired to misinterpret the reality of circumstances and how grossly inaccurate we can get with predicting our own futures.&lt;/p&gt;

&lt;p&gt;So, AI might actually be coming for web development jobs 🤭&lt;/p&gt;

&lt;h2&gt;
  
  
  The verdict
&lt;/h2&gt;

&lt;p&gt;So, will AI replace JavaScript developers? We have gone on a wild ride where we have explored some ways that people are imaginatively using AI, from making their lives and work easier to generating code.&lt;/p&gt;

&lt;p&gt;We then dove deeper into questions around "Will AI make programmers obsolete?" We defined how AI generates code — the "behind the scenes" of how AI "writes" code.&lt;/p&gt;

&lt;p&gt;We also looked at some examples of where AI is thriving as well as some of its limitations. Finally, we concluded that learning to code is still a valuable skill in the wake of AI advancements.&lt;/p&gt;

&lt;p&gt;It is important to note that AI will keep getting better, and it might be a smart move to upskill and acquire advanced skills like DevOps in the future. Or stay alert for new jobs that will be created as a result of AI.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
