<?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: JorensM</title>
    <description>The latest articles on DEV Community by JorensM (@jorensm).</description>
    <link>https://dev.to/jorensm</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%2F789711%2Fbdcc1061-9ebb-45cb-bc67-05e04ac016c9.png</url>
      <title>DEV Community: JorensM</title>
      <link>https://dev.to/jorensm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jorensm"/>
    <language>en</language>
    <item>
      <title>Creating a tab-synced music player in React</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Wed, 10 Jan 2024 08:24:54 +0000</pubDate>
      <link>https://dev.to/jorensm/creating-a-tab-synced-music-player-in-react-11pa</link>
      <guid>https://dev.to/jorensm/creating-a-tab-synced-music-player-in-react-11pa</guid>
      <description>&lt;p&gt;&lt;strong&gt;Warning, kind of a long read, here is the &lt;a href="https://github.com/Printy-Studios/react-music-player" rel="noopener noreferrer"&gt;source code&lt;/a&gt; if you don't feel like reading the entire article&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hi dear readers!&lt;/p&gt;

&lt;p&gt;In a recent project, I was tasked with creating a music player. The requirements were as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The player had to work and sync even with multiple tabs open&lt;/li&gt;
&lt;li&gt;The player state needed not to be reset upon a refresh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was certainly an interesting task and the features listed above were something I hadn't worked with before.&lt;/p&gt;

&lt;p&gt;In this article I'd like to recreate this music player, and show you, step-by-step, how to do it.&lt;/p&gt;

&lt;p&gt;Our player will have the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tab syncing&lt;/li&gt;
&lt;li&gt;State persistence between refreshes&lt;/li&gt;
&lt;li&gt;Play/Pause/Prev/Next buttons&lt;/li&gt;
&lt;li&gt;Song info such as cover art and title/author&lt;/li&gt;
&lt;li&gt;Songs will be fetched from the app's &lt;code&gt;public&lt;/code&gt; folder, and you can easily add more songs by adding a sound file, cover art and a metadata file to the app's &lt;code&gt;public&lt;/code&gt; folder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project will be written in TypeScript, but you can use JS just as well (just omit any TypeScript syntax).&lt;/p&gt;

&lt;p&gt;This is what the final result will look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs13.gifyu.com%2Fimages%2FS0KkN.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs13.gifyu.com%2Fimages%2FS0KkN.gif" alt="Showcase of the music player"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up
&lt;/h2&gt;

&lt;p&gt;I will assume that you already know how to set up a project. I will personally be using &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; to set up my project.&lt;/p&gt;

&lt;p&gt;After having set up our project, we will need to install 2 libraries (that I wrote myself) - &lt;a href="https://www.npmjs.com/package/@printy/react-tab-state" rel="noopener noreferrer"&gt;react-tab-state&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@printy/react-persist-state" rel="noopener noreferrer"&gt;react-persist-state&lt;/a&gt;. These libraries will allow us to sync our player instances between tabs and also persist them even after the page has been closed. If you want to learn how these libraries work (they're actually quite simple), I wrote 2 articles about that, which you can find by following the links below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/jorensm/how-to-keep-state-between-page-refreshes-in-react-3801"&gt;How to keep state between page refreshes in React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jorensm/how-to-sync-react-state-across-tabs-with-workers-2mpg"&gt;How to sync React state across tabs with workers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For icons, I will be using the following ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.svgrepo.com/svg/532511/play" rel="noopener noreferrer"&gt;Play icon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.svgrepo.com/svg/522621/pause" rel="noopener noreferrer"&gt;Pause icon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.svgrepo.com/svg/521767/next" rel="noopener noreferrer"&gt;Next icon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.svgrepo.com/svg/521791/prev" rel="noopener noreferrer"&gt;Previous icon&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But you can of course use any icons you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;After having set up our project, let's walk through our project structure and which files we'll be writing&lt;/p&gt;

&lt;p&gt;The project structure will be as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;App.tsx&lt;/code&gt; - The page where our music player will be displayed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;style.css&lt;/code&gt; - Where our styles will reside&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useMusicPlayer.ts&lt;/code&gt; - A hook for manipulating audio&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MusicPlayer.ts&lt;/code&gt; - The music player component&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SongMetadata.ts&lt;/code&gt; - Type for our song metadata objects&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;util.ts&lt;/code&gt; - Utility functions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;IconButton.tsx&lt;/code&gt; - Reusable icon button with animations&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PlayButton.tsx&lt;/code&gt; - Component for our play button&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TimeDisplay.tsx&lt;/code&gt; - Component for displaying time in the playere player&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, our &lt;code&gt;public&lt;/code&gt; folder will have the following structure:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cover_art/&lt;/code&gt; - This is where we will keep the cover art for the songs. The file name should be of &lt;code&gt;.jpg&lt;/code&gt; format and should match the ID of the song defined in &lt;code&gt;songs_metadata.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;icons/&lt;/code&gt; - Folder with our icon files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;songs/&lt;/code&gt; - Folder with our songs. The files should be of &lt;code&gt;.wav&lt;/code&gt; filetype and their name should match the ID of the song&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;worker.js&lt;/code&gt; - Our worker file that will manage the information about the main tab (needed for syncing the players across tabs)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;songs_metadata.json&lt;/code&gt; - This is where information about our songs will be, such as song ID, title and author.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alright, now we know what our project structure will look like, we can start coding! If at any point you feel lost as to where a certain piece of code should go, feel free to refer to the &lt;a href="https://github.com/Printy-Studios/react-music-player" rel="noopener noreferrer"&gt;source code&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;useMusicPlayer.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;First of all, let's write our &lt;code&gt;useMusicPlayer&lt;/code&gt; hook.&lt;/p&gt;

&lt;p&gt;Let's begin by scaffolding our function&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;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;useMusicPlayer&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;Next up, let's add all the necessary state&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;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;useMusicPlayer&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;audio&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;audio&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;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTabState&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is_playing&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;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSrc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="c1"&gt;//setCurrentTime is for local use only, for setting time use updateTime&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;currentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useTabState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current_time&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;updatedTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updateTime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTabState&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated_time&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;isMainTab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsMainTab&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;storedTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStoredTime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePersistState&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stored_time&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;maxTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMaxTime&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="mi"&gt;0&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;Let's go through each line and see what kind of state they define&lt;/p&gt;

&lt;p&gt;First we create an &lt;code&gt;audio&lt;/code&gt; ref, this will hold a reference to our HTML audio element that we will use to play audio. We won't actually be showing this element on the page, we'll just be using it for playback.&lt;/p&gt;

&lt;p&gt;Next we have &lt;code&gt;isPlaying&lt;/code&gt;. This determines whether our player is currently playing or paused. You may notice that we're not using a regular &lt;code&gt;useState()&lt;/code&gt; function to create this state. &lt;code&gt;useTabState()&lt;/code&gt; is a special hook that I created that allows you to sync the given state across tabs. So if the value changes in one tab, it will also change in any other tabs for this page. The first argument is the initial value, and the second argument is a unique ID to identify our state.&lt;/p&gt;

&lt;p&gt;Then we have a &lt;code&gt;src&lt;/code&gt; state that determines the URL of the source sound file that the audio player should be playing.&lt;/p&gt;

&lt;p&gt;Next we have &lt;code&gt;currentTime&lt;/code&gt; and &lt;code&gt;updatedTime&lt;/code&gt;. &lt;code&gt;currentTime&lt;/code&gt; tells us the current time of our audio playback, and &lt;code&gt;updatedTime&lt;/code&gt; is needed to act as a proxy when setting current time, so we don't run into an infinite loop when setting current time with our slider and syncing state across tabs. You may notice that &lt;code&gt;currentTime&lt;/code&gt; and 'updatedTime' also use &lt;code&gt;useTabState&lt;/code&gt;, this is because we want to sync the current time across tabs.&lt;/p&gt;

&lt;p&gt;Next up is &lt;code&gt;isMainTab&lt;/code&gt;. This tells us whether the current tab is the 'main' tab. Later we will be writing a small worker script that will determine which tab should be the main tab. We need this 'main tab' feature because we will want to have audio playing only from one tab if there are multiple tabs open.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;storedTime&lt;/code&gt; will hold state for our 'stored' current time that will be saved into localStorage and persisted across refreshes. This state uses another one of my custom hooks - &lt;code&gt;usePersistState&lt;/code&gt;, which creates a state that gets persisted across page refreshes. Just like with &lt;code&gt;useTabState&lt;/code&gt;, the first arg is the initial value and the second arg is the localStorage key to store the state under.&lt;/p&gt;

&lt;p&gt;And finally &lt;code&gt;maxTime&lt;/code&gt; tells us the max time (length) of our current song.&lt;/p&gt;

&lt;p&gt;Alright, now that we have set up our state, let's write our &lt;code&gt;useEffect&lt;/code&gt;s&lt;/p&gt;

&lt;p&gt;First off let's write the &lt;code&gt;useEffect&lt;/code&gt; that gets called upon mounting of our app. Here we will set up our connection with the worke and add event listeners to the &lt;code&gt;audio&lt;/code&gt; element:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;worker&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="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;setInterval&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;worker&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="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ping&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="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;worker&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="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;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="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="nx"&gt;e&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;switch&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="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set_main_port&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;setIsMainTab&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="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unset_main_port&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;setIsMainTab&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="k"&gt;break&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;audio&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;preload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;audio&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;ondurationchange&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="nf"&gt;setMaxTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audio&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;duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;audio&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;onended&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="nf"&gt;setIsPlaying&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;audio&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;ontimeupdate&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="nf"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;audio&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;currentTime&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;Don't worry if you don't understand some of the code - I will explain what it does.&lt;/p&gt;

&lt;p&gt;First of all we set up our connection with the worker. &lt;code&gt;worker.port.start();&lt;/code&gt; ensures that our port is open and can receive/send messages.&lt;/p&gt;

&lt;p&gt;Next we create an interval using &lt;code&gt;setInterval&lt;/code&gt; that gets run every second. We use this interval to ping our worker to let it know that our tab has not been closed. For example if a tab gets closed, it will stop pinging the worker, and the worker will assume that the tab was closed and, if the tab was the main tab, will change the main tab to a different one.&lt;/p&gt;

&lt;p&gt;We add a message handler to &lt;code&gt;worker.port.onmessage&lt;/code&gt; to be able to receive messages from the worker. There will only be 2 messages - &lt;code&gt;set_main_port&lt;/code&gt;, which notifies the tab that it should be the main tab, and &lt;code&gt;unset_main_port&lt;/code&gt;, which notifies the tab that it should stop being the main tab.&lt;/p&gt;

&lt;p&gt;Next up we add some event listeners to our &lt;code&gt;audio&lt;/code&gt; element. We add an &lt;code&gt;ondurationchange&lt;/code&gt; listener which updates our &lt;code&gt;maxTime&lt;/code&gt; state whenever the length of the song changes. Then we add an &lt;code&gt;onended&lt;/code&gt; listener which sets our &lt;code&gt;isPlaying&lt;/code&gt; state to false when the playback has ended, and finally we add an &lt;code&gt;ontimeupdate&lt;/code&gt; listener which updates our &lt;code&gt;currentTime&lt;/code&gt; state whenever the current time of the &lt;code&gt;audio&lt;/code&gt; element changes.&lt;/p&gt;

&lt;p&gt;Ok, great, we have set up our initialization logic! We're about halfway done with this file!&lt;/p&gt;

&lt;p&gt;Our next &lt;code&gt;useEffect&lt;/code&gt; will be as follows: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;currentTime&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&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="nf"&gt;setStoredTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentTime&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;currentTime&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;What this simply does is update our &lt;code&gt;storedTime&lt;/code&gt; variable and store it in localStorage whenever &lt;code&gt;currentTime&lt;/code&gt; changes.&lt;/p&gt;

&lt;p&gt;Now let's write the &lt;code&gt;useEffect&lt;/code&gt; for when the &lt;code&gt;src&lt;/code&gt; state changes&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;audio&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;src&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;audio&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;src&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;storedTime&lt;/span&gt; &lt;span class="o"&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="nf"&gt;updateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storedTime&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;setStoredTime&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="nf"&gt;updateTime&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&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;span class="nx"&gt;audio&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;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;src&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;isPlaying&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isMainTab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;audio&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="nf"&gt;play&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;src&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;First of all, we check if our &lt;code&gt;src&lt;/code&gt; is defined and if &lt;code&gt;storedTime&lt;/code&gt; is not 0, in which case we update our current time in accordance to the &lt;code&gt;storedTime&lt;/code&gt;. This will only ever happen when the page first opens, and will allow us to persist our current time between page refreshes. &lt;/p&gt;

&lt;p&gt;Next we check if &lt;code&gt;src&lt;/code&gt; is not an empty string, in which case we set the &lt;code&gt;audio&lt;/code&gt; element's &lt;code&gt;src&lt;/code&gt; property to the new &lt;code&gt;src&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally we check if the player should be playing and if the current tab is the main tab, in which case we have our &lt;code&gt;audio&lt;/code&gt; element play our song.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;audio&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;currentTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;updatedTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;updatedTime&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;updatedTime&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Next we have a &lt;code&gt;useEffect&lt;/code&gt; that runs when &lt;code&gt;updatedTime&lt;/code&gt; changes. We use this &lt;code&gt;updatedTime&lt;/code&gt; state as a proxy to set our &lt;code&gt;currentTime&lt;/code&gt; because we cannot set &lt;code&gt;currentTime&lt;/code&gt; directly. I'll be honest, I forgot the exact reason why we can't set &lt;code&gt;currentTime&lt;/code&gt; directly, but I know that it can cause an infinite loop when syncing tabs and using the slider to change our current time.&lt;/p&gt;

&lt;p&gt;Additionally we update the &lt;code&gt;currentTime&lt;/code&gt; property of the &lt;code&gt;audio&lt;/code&gt; element, which makes it playback at that time.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;isPlaying&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;isMainTab&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// console.log('is main')&lt;/span&gt;
        &lt;span class="nx"&gt;audio&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="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&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;audio&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="nf"&gt;pause&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;isPlaying&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Our final &lt;code&gt;useEffect&lt;/code&gt; will be run when &lt;code&gt;isPlaying&lt;/code&gt; changes. In it, we check if &lt;code&gt;isPlaying&lt;/code&gt; is set to true and if the current tab is the main tab, in which case we play the audio. Otherwise we set the audio on pause.&lt;/p&gt;

&lt;p&gt;Finally, let's return all the relevant functions and state:&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;isPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;updateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;maxTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;setSrc&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And we're done with our &lt;code&gt;useMusicPlayer&lt;/code&gt; hook! &lt;/p&gt;

&lt;p&gt;If you want to see what the final file looks like, refer to &lt;a href="https://github.com/Printy-Studios/react-music-player/blob/main/src/useMusicPlayer.ts" rel="noopener noreferrer"&gt;the file in the source code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's create our &lt;code&gt;worker.ts&lt;/code&gt; file&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;worker.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Our worker will manage the information about the current main tab, and determine which tab should be the main tab. Depending on your configuration, the &lt;code&gt;worker.js&lt;/code&gt; file should go either into the &lt;code&gt;src&lt;/code&gt; folder or the &lt;code&gt;public&lt;/code&gt; folder. In my case I put it into &lt;code&gt;public&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our worker will look like this:&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;ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;main_port&lt;/span&gt; &lt;span class="o"&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;let&lt;/span&gt; &lt;span class="nx"&gt;pinged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Did Main port ping?&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postMessageAll&lt;/span&gt; &lt;span class="o"&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="nx"&gt;exclude_port&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;ports&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;port&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;port&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;exclude_port&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;postMessage&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setMainPort&lt;/span&gt; &lt;span class="o"&gt;=&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;main_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;main_port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set_main_port&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;postMessageAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unset_main_port&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;main_port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Ping check&lt;/span&gt;
&lt;span class="nf"&gt;setInterval&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pinged&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;port_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;_port&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;main_port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port_index&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="nf"&gt;setMainPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ports&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="nx"&gt;pinged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;onconnect&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="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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&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="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ports&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;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setMainPort&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="nx"&gt;port&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;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="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="nx"&gt;e&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;switch&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;type&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ping&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;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;main_port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;pinged&lt;/span&gt; &lt;span class="o"&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="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;First we define some variables that our worker will need to use. &lt;code&gt;ports&lt;/code&gt; stores a list of all our open ports, &lt;code&gt;main_port&lt;/code&gt; stores a reference to our main port/tab, and &lt;code&gt;pinged&lt;/code&gt; tells us whether main port has successfully pinged the worker.&lt;/p&gt;

&lt;p&gt;Next we create a utility function &lt;code&gt;postMessageAll()&lt;/code&gt; which posts a message to all of the ports. Optionally you can pass a port as the second arg, which will exclude that port from being messaged.&lt;/p&gt;

&lt;p&gt;Then we create a function &lt;code&gt;setMainTab&lt;/code&gt; which sets our main tab - it sends a message to the new main tab to let it know that it is now the main tab, and also sends a message to all the other tabs to let them know that they are not the main tab.&lt;/p&gt;

&lt;p&gt;Next up we create an interval that checks every 2 seconds whether the main tab has successfully pinged our worker without a timeout. If the main tab failed to ping the worker, then the worker assumes that the main tab was closed and sets another tab as the main one.&lt;/p&gt;

&lt;p&gt;Finally, in &lt;code&gt;onconnect&lt;/code&gt; we add some initialization logic - if there is only 1 port open, then it should be set to the main port. And we also listen for the &lt;code&gt;ping&lt;/code&gt; message from the tabs to make sure that the main tab has pinged the worker and hasn't been closed.&lt;/p&gt;

&lt;p&gt;That's it for our worker!&lt;/p&gt;

&lt;p&gt;Next up let's create our utility functions in &lt;code&gt;util.ts&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;util.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;util.ts&lt;/code&gt; file will contain some reusable utility functions that will make our code a bit more readable.&lt;/p&gt;

&lt;p&gt;First of all is the &lt;code&gt;secondsToMinutesAndSeconds()&lt;/code&gt; function:&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;secondsToMinutesAndSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&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;seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&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;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;seconds&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;What it does is simply convert seconds to an object storing seconds and minutes.&lt;/p&gt;

&lt;p&gt;Next up is &lt;code&gt;numberToPercent()&lt;/code&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;numberToPercent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max_n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;percentage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&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="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;max_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&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;percentage&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;What this does is tell you how much percent is &lt;code&gt;n&lt;/code&gt; of &lt;code&gt;max_n&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally let's add &lt;code&gt;percentOf()&lt;/code&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;percentOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This function tells us how much is &lt;code&gt;percentage&lt;/code&gt; of &lt;code&gt;n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And our &lt;code&gt;util.ts&lt;/code&gt; file is done!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;SongMetadata.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;SongMetadata.ts&lt;/code&gt; will hold the type for our song metadata object, it's quite simple:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SongsMetadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&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="na"&gt;title&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="na"&gt;author&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;SongsMetadata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;As you can see it just holds some basic info about the song such as its ID, title and author.&lt;/p&gt;

&lt;p&gt;At this point, functionality wise our player is done - we could progammatically invoke this hook and use its functions to play our audio! All that is left is to implement the UI!&lt;/p&gt;

&lt;p&gt;Let's start with the small components first&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;IconButton.tsx&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This will be the component for our icon buttons. It will have some basic animations that you'll see once we get to the &lt;code&gt;style.css&lt;/code&gt; file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;IconButtonProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;src&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="na"&gt;onClick&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="k"&gt;void&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;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;IconButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;IconButtonProps&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;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
            &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&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;img&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;icon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;src&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="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;As you can see it's basically just a button wrapper around an image with an &lt;code&gt;icon&lt;/code&gt; class. the &lt;code&gt;src&lt;/code&gt; prop tells us the image url, and &lt;code&gt;onClick&lt;/code&gt; is the click handler.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;PlayButton.tsx&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;PlayButton&lt;/code&gt; component will just be a basic wrapper around &lt;code&gt;IconButton&lt;/code&gt; with specific icons that will change depending on the &lt;code&gt;playing&lt;/code&gt; prop:&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="nx"&gt;IconButton&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;./IconButton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PlayButtonProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;playing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onClick&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="k"&gt;void&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;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PlayButton&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;playing&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PlayButtonProps&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;&lt;/span&gt;&lt;span class="nx"&gt;IconButton&lt;/span&gt;
            &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;playing&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;icons/pause.svg&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="s1"&gt;icons/play.svg&lt;/span&gt;&lt;span class="dl"&gt;'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;TimeDisplay.tsx&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Time display will be a component that will take a &lt;code&gt;time&lt;/code&gt; (an object of seconds and minutes) prop and display this time.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TimeDisplayProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;TimeDisplay&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;TimeDisplayProps&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;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time-display&lt;/span&gt;&lt;span class="dl"&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minutes&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seconds&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="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&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;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;Alright we've made it this far, we only have 1 component left to go - the &lt;code&gt;MusicPlayer&lt;/code&gt; component itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;MusicPlayer.tsx&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MusicPlayer&lt;/code&gt; component will be the component that will use the &lt;code&gt;useMusicPlayer&lt;/code&gt; hook and the sub-components that we created to display a fully functioning music player.&lt;/p&gt;

&lt;p&gt;Let's start by scaffolding our component:&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;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;MusicPlayer&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;Now let's start first by adding our state.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;musicPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMusicPlayer&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;sliderValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSliderValue&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="mi"&gt;0&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;isSliderActive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsSliderActive&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;currentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentTime&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="na"&gt;minutes&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;seconds&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;maxTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMaxTime&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="na"&gt;minutes&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;seconds&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentSongLocal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;usePersistState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current_song_index&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;setCurrentSong&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;void&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useTabStateControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCurrentSongLocal&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current_song_index&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;songsMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSongsMetadata&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SongsMetadata&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;musicPlayer&lt;/code&gt; is the &lt;code&gt;useMusicPlayer&lt;/code&gt; hook that we wrote earlier.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sliderValue&lt;/code&gt; and &lt;code&gt;isSliderActive&lt;/code&gt; are states that we will use to track and manage our time slider&lt;/p&gt;

&lt;p&gt;&lt;code&gt;currentTime&lt;/code&gt; and &lt;code&gt;maxTime&lt;/code&gt; are the same as their equivalent states in &lt;code&gt;useMusicPlayer&lt;/code&gt;, except they've been converted to seconds/minutes objects.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;currentSong&lt;/code&gt; is the state that will track the index number of the current song in the &lt;code&gt;songsMetadata&lt;/code&gt; array. You can see that we have 2 lines for defining &lt;code&gt;currentSong&lt;/code&gt; and &lt;code&gt;setCurrentSong&lt;/code&gt;. First we create the local state with &lt;code&gt;usePersistState&lt;/code&gt; in order to have the state be stored to localStorage, then we use &lt;code&gt;useTabStateControl&lt;/code&gt; to additionally sync the state across tabs. &lt;code&gt;useTabStateControl&lt;/code&gt; is another hook that I wrote that allows you to combine a state handler with &lt;code&gt;useTabState&lt;/code&gt;. So you can have both persistant state and tab synced state this way. Another cool thing (and the initial reason I made &lt;code&gt;useTabStateControl&lt;/code&gt;) is that this way you can hook up any state management library, like Redux or Zustand to the &lt;code&gt;useTabState&lt;/code&gt; hook.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;songsMetadata&lt;/code&gt; holds an array of the available songs.&lt;/p&gt;

&lt;p&gt;Now let's start writing the functions that the component will use.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onPlayButtonClick&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="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setIsPlaying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&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 the name suggests, this function will handle the clicking of the play button, and simply toggle the music player's &lt;code&gt;isPlaying&lt;/code&gt; state.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onPrevButtonClick&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSong&lt;/span&gt; &lt;span class="o"&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="nf"&gt;setCurrentSong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;songsMetadata&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;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&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="nf"&gt;setCurrentSong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSong&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&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;onNextButtonClick&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSong&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;songsMetadata&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;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setCurrentSong&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setCurrentSong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSong&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;These 2 functions will handle the clicking of the previous/next buttons. What they do is simply increment or decrement the &lt;code&gt;currentSong&lt;/code&gt; index by 1, or have it loop to the first/last index.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchSongsMetadata&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;songs_metadata.json&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;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setSongsMetadata&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;This function simply fetches our songs metadata file and adds the returned array to the &lt;code&gt;songsMetadata&lt;/code&gt; state&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onSliderChange&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="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&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;setSliderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseInt&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="nf"&gt;setIsSliderActive&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;onSliderMouseUp&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;new_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;percentOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sliderValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;new_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setIsSliderActive&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="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;These 2 functions handle the slider. &lt;code&gt;onSliderChange&lt;/code&gt; gets called every time our slider gets dragged around, and it updated our &lt;code&gt;sliderValue&lt;/code&gt; as well as marks the slider as being active (in dragging state). We don't want to update our songs current time every time the slider value changes(this would result in a jagged sound when the slider gets moved around), but only once it has been released. For this we have the &lt;code&gt;onSliderMouseUp&lt;/code&gt; function, which gets called whenever the slider handle has been released. The function updates our music player's current time, and marks the slider as not being dragged anymore.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loadSong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;songsMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSrc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;songs/${songsMetadata[index].id}.wav&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You should replace quotes with backticks here. I used quotes because otherwise the post was not getting formatted correctly.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;loadSong&lt;/code&gt; function simply loads the songs at the given index of &lt;code&gt;songsMetadata&lt;/code&gt; array, by setting the music player's &lt;code&gt;src&lt;/code&gt; to that song's source url.&lt;/p&gt;

&lt;p&gt;Next up, let's add our &lt;code&gt;useEffect&lt;/code&gt;s&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;fetchSongsMetadata&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;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;loadSong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentSong&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;songsMetadata&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;These  2 &lt;code&gt;useEffects&lt;/code&gt; make sure that the song metadata gets downloaded and that the initial song gets loaded.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isSliderActive&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;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setSliderValue&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setSliderValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;numberToPercent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTime&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;setCurrentTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;secondsToMinutesAndSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&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;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTime&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This &lt;code&gt;useEffect&lt;/code&gt; updates the slider value to the current time of the song, so you can see at which point in time the song is currently playing. We also make sure that the slider value doesn't get updated while the slider is being dragged (because this would result in buggy behavior).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;setMaxTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;secondsToMinutesAndSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTime&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;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTime&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Finally we has a &lt;code&gt;useEffect&lt;/code&gt; for the music player's &lt;code&gt;maxTime&lt;/code&gt; which updates the component's &lt;code&gt;maxTime&lt;/code&gt; accordingly.&lt;/p&gt;

&lt;p&gt;Alright, we're almost done! Now all that is left is to build out the markup and css! This is what the markup looks like: &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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;songsMetadata&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;music-player&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;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;music-player-info&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;img&lt;/span&gt; 
                &lt;span class="nx"&gt;src&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="s1"&gt;cover_art/${songsMetadata[currentSong].id}.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
                &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&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;h5&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;songsMetadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSong&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;title&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;/h5&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;h6&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;songsMetadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentSong&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;author&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;/h6&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;/div&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;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;music-player-controls&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;IconButton&lt;/span&gt;
                &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onPrevButtonClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;src&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="s1"&gt;icons/prev.svg&lt;/span&gt;&lt;span class="dl"&gt;'&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;PlayButton&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onPlayButtonClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;playing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;musicPlayer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPlaying&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;TimeDisplay&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;currentTime&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="kd"&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;range&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
                &lt;span class="nx"&gt;min&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&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
                &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100&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;sliderValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
                &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;slider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
                &lt;span class="nx"&gt;onMouseUp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onSliderMouseUp&lt;/span&gt;&lt;span class="p"&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;onSliderChange&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;TimeDisplay&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;maxTime&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;IconButton&lt;/span&gt;
                &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onNextButtonClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;src&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="s1"&gt;icons/next.svg&lt;/span&gt;&lt;span class="dl"&gt;'&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="sr"&gt;/div&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;/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="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;First of all we make sure to render the music player only if we have any songs downloaded.&lt;/p&gt;

&lt;p&gt;Next up we have the &lt;code&gt;music-player-info&lt;/code&gt; element which shows the cover art of the song, the song's title and artist.&lt;/p&gt;

&lt;p&gt;Below that we have &lt;code&gt;music-player-controls&lt;/code&gt; which holds the control elements of our player - the prev/next/play buttons and the slider.&lt;/p&gt;

&lt;p&gt;The first &lt;code&gt;IconButton&lt;/code&gt; component is the prev button, and we make sure to hook up the appropriate event handler.&lt;/p&gt;

&lt;p&gt;Next is the &lt;code&gt;PlayButton&lt;/code&gt; component, where we also make sure to add the event handler.&lt;/p&gt;

&lt;p&gt;Then we have a &lt;code&gt;TimeDisplay&lt;/code&gt; component which displays the current time of the song.&lt;/p&gt;

&lt;p&gt;Then we have our slider element which uses the native HTMLS range input.&lt;/p&gt;

&lt;p&gt;Next we have the time display for our &lt;code&gt;maxTime&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And finally we have our 'next' button.&lt;/p&gt;

&lt;p&gt;We're so close! All that is left now is to add the necessary styles!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;style.css&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is what our CSS looks like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Inter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Avenir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Helvetica&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;



&lt;span class="nc"&gt;.player-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Icons */&lt;/span&gt;

&lt;span class="nc"&gt;.icon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;32px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="m"&gt;0.1s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.icon&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;105%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.icon&lt;/span&gt;&lt;span class="nd"&gt;:active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;70%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Music player */&lt;/span&gt;

&lt;span class="nc"&gt;.music-player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.music-player-controls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Music player info */&lt;/span&gt;

&lt;span class="nt"&gt;h5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h6&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Time display */&lt;/span&gt;

&lt;span class="nc"&gt;.time-display&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Slider */&lt;/span&gt;

&lt;span class="nc"&gt;.slider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;flex-grow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d3d3d3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;outline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;opacity&lt;/span&gt; &lt;span class="m"&gt;.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.slider&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.slider&lt;/span&gt;&lt;span class="nd"&gt;::-webkit-slider-thumb&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;-webkit-appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#04AA6D&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.slider&lt;/span&gt;&lt;span class="nd"&gt;::-moz-range-thumb&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#04AA6D&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&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;I won't be going into details about how the CSS works because I don't think I'd be able to explain it haha. But if you have any questions about any of the code feel free to ask and I will try my best to explain!&lt;/p&gt;

&lt;p&gt;Aaaaaand we're done with our music player!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;App.tsx&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Finally, in our &lt;code&gt;App.tsx&lt;/code&gt;, we will add the following code to display our music player component:&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="nx"&gt;MusicPlayer&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;./MusicPlayer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&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;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;player-wrapper&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;MusicPlayer&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;/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;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now you can go ahead and test your music player and see that it(hopefully) works! You can even add your own songs! Just add the appropriate files (&lt;code&gt;.wav&lt;/code&gt; for the song itself, &lt;code&gt;.jpg&lt;/code&gt; for the cover art and then add the song info to the &lt;code&gt;songs_metadata.json&lt;/code&gt;)&lt;/p&gt;

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

&lt;p&gt;Whew, this was a long one! The longest article I've ever written! If you read through the whole post then congratulations! &lt;/p&gt;

&lt;p&gt;In this post we explored a way on how to build a tab-synced and persistant music player in React. If you're interested in how &lt;code&gt;useTabState&lt;/code&gt; and &lt;code&gt;usePersistState&lt;/code&gt; work, check out the articles linked below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/jorensm/how-to-keep-state-between-page-refreshes-in-react-3801"&gt;How to keep state between page refreshes in React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jorensm/how-to-sync-react-state-across-tabs-with-workers-2mpg"&gt;How to sync React state across tabs with workers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
I hope you learned something new from this article. If you have any questions or feedback, feel free to leave a comment!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're interested in more web dev content, consider subscribing to my &lt;a href="http://eepurl.com/hTqj4j" rel="noopener noreferrer"&gt;bi-weekly newsletter&lt;/a&gt;. There I share with you top web dev articles that I've recently found on the internet.&lt;/p&gt;

&lt;p&gt;If you have any requests or ideas for an article, feel free to share them with me in the comments or at &lt;a href="//mailto:jorensmerenjanu@gmail.com"&gt;jorensmerenjanu@gmail.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Have a great day!&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>music</category>
    </item>
    <item>
      <title>Architecture design of my game My Pixel Plant</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Tue, 02 Jan 2024 06:15:16 +0000</pubDate>
      <link>https://dev.to/jorensm/architecture-design-of-my-game-my-pixel-plant-4p30</link>
      <guid>https://dev.to/jorensm/architecture-design-of-my-game-my-pixel-plant-4p30</guid>
      <description>&lt;p&gt;Hi dear readers!&lt;/p&gt;

&lt;p&gt;Recently I published my small browser game &lt;a href="https://printygames.itch.io/pixel-plant"&gt;My Pixel Plant&lt;/a&gt;. Developing it was lots of fun and so I thought I'd share some insights into how I was making it. I'm willing to guess that many (or at least some) of us here are also game developers, so I thought this would make for an interesting read!&lt;/p&gt;

&lt;p&gt;In this article I will share with you the architecture design behind my game.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Printy-Studios/pixel-plant/"&gt;Source code&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The code is written purely in &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;/&lt;a href="https://www.w3schools.com/html/"&gt;HTML&lt;/a&gt;/&lt;a href="https://www.w3schools.com/css/"&gt;CSS&lt;/a&gt;, and it took me around 30 hours to write it. I'm using &lt;a href="https://webpack.js.org/"&gt;Webpack&lt;/a&gt; to bundle my code. When writing my code, I tried to keep portability in mind to allow for easy porting to other platforms, if needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;So the basic architecture of my game is like so: I have a base Game class, and that Game class has several "manager" component classes that manage different parts of the game.&lt;/p&gt;

&lt;p&gt;The current manager classes are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UIManager&lt;/li&gt;
&lt;li&gt;SaveManager&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am planning to refactor the Game class to also have a PlantManager that manages the plant behavior, as the code for that currently resides in Game, which violates the &lt;a href="https://en.wikipedia.org/wiki/Single_responsibility_principle"&gt;Single Responsibility Principle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I know that the manager pattern has received some critique in the past, but I found that it worked very well in my game as a means of organizing and decoupling code.&lt;/p&gt;

&lt;p&gt;In addition to manager classes, I also have several low-level modules that handle stuff like rendering and cache. These classes don't 'manage' anything, but give access to low-level functionality such as rendering and storage. I want to eventually implement an interface for each of the low-level classes, so that they can be swapped for others, for example for another renderer. This would allow for my game to be more easily portable to other platforms.&lt;/p&gt;

&lt;p&gt;There are the following low-level classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Renderer&lt;/li&gt;
&lt;li&gt;Cache&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, I have a Plant class that holds the data and behavior of the plant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Low-level classes
&lt;/h2&gt;

&lt;p&gt;The low level classes are the classes that handle the low level behavior such as rendering and storage. These classes are cruicial for allowing for easy porting of the game.&lt;/p&gt;

&lt;h3&gt;
  
  
  Renderer
&lt;/h3&gt;

&lt;p&gt;The Renderer class is the one responsible for rendering. It holds methods such as drawPlant, drawSprite, drawRect, drawProgressBar. It uses the HTML canvas for rendering. It should be possible to easily swap it for a different renderer class that uses something other than Canvas (OpenGL/WebGL) for example. I plan to accoplish this by creating a Renderer interface/abstract class that the subclasses must implement, so the game can work with any type of renderer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Printy-Studios/pixel-plant/blob/main/src/Renderer.ts"&gt;Source code for the Renderer class&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;The Storage class is the class responsible for persistent data storage, which is the save data in the case of this game. This class uses the browser's localStorage, but just like Renderer, should ideally be swappable with other types of storage such as file storage&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Printy-Studios/pixel-plant/blob/main/src/MyStorage.ts"&gt;Source code for the Storage class&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache
&lt;/h3&gt;

&lt;p&gt;The Cache class is responsible for storing non-persistent, per-session data. This is so that the game runs faster and doesn't have to re-download an image each time it is needed. Under the hood it is basically just an object that can be accessed through Cache's getter and setter&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Printy-Studios/pixel-plant/blob/main/src/MyCache.ts"&gt;Source code for the Cache class&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Manager classes
&lt;/h2&gt;

&lt;p&gt;The managers manage different parts of the game and its behaviors&lt;/p&gt;

&lt;h3&gt;
  
  
  UIManager
&lt;/h3&gt;

&lt;p&gt;The UIManager class manages the UI of the game, - initializes it, adds event listeners to buttons, and changes it depending on game's data. In order to decouple UIManager from the rest of the code, I used a global event system. When a button is clicked, for example, instead of directly calling the appropriate function, the button click emits an event that can be read by the Game class, and then the Game class reacts appropriately to the event. This has drastically lowered the amount of dependencies that the UIManager requires.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Printy-Studios/pixel-plant/blob/main/src/UIManager.ts"&gt;Source code for the UIManager class&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  SaveManager
&lt;/h3&gt;

&lt;p&gt;The SaveManager class is responsible for handling the saving of data. It uses the Storage class to save the data into localStorage&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Printy-Studios/pixel-plant/blob/main/src/SaveManager.ts"&gt;Source code for the SaveManager class&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Utility classes
&lt;/h2&gt;

&lt;p&gt;Utility classes are the 'unit' classes that are completely independent from the rest of the game, and could potentially be reused in other games.&lt;/p&gt;

&lt;h3&gt;
  
  
  GameObject
&lt;/h3&gt;

&lt;p&gt;The GameObject class is the basic unit of the game. Currently it only holds a single property - position. In hindsight this class may be redundant at the current stage of the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sprite
&lt;/h3&gt;

&lt;p&gt;The Sprite class holds information about a sprite, its dimensions, texture and position. Renderer has a drawSprite() method that allows you to draw a sprite&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Printy-Studios/pixel-plant/blob/main/src/Sprite.ts"&gt;Source code for the Sprite class&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Plant
&lt;/h3&gt;

&lt;p&gt;This is the central, and probably the most interesting class of the game. The Plant class holds information about the plant, its water levels, stages, sprites, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Printy-Studios/pixel-plant/blob/main/src/Plant.ts"&gt;Source code for the Plant class&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Data
&lt;/h2&gt;

&lt;p&gt;Each plant type is stored in a &lt;code&gt;.json&lt;/code&gt; file, which get fetched upon game load. It stores information such as growth rate, water levels, plant name and ID. When creating a new &lt;code&gt;Plant&lt;/code&gt; instance, I pass the plant &lt;code&gt;.json&lt;/code&gt; file to &lt;code&gt;Plant&lt;/code&gt;'s &lt;code&gt;fromJSON()&lt;/code&gt; method, and an instance gets created from that plant file. Sprites for the plants are stored in folders named after the plant IDs, and each sprite has a name such as &lt;code&gt;1.png&lt;/code&gt; or &lt;code&gt;4.png&lt;/code&gt;, the number indicating the growth stage.&lt;/p&gt;

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

&lt;p&gt;In this post I talked about the architecture behind my game &lt;a href="https://printygames.itch.io/pixel-plant"&gt;My Pixel Plant&lt;/a&gt;. I hope it was informative and that you learned something new from it. If you're interested in learning more, feel free to check out the &lt;a href="https://github.com/Printy-Studios/pixel-plant/"&gt;source code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>games</category>
      <category>videogame</category>
      <category>devlog</category>
    </item>
    <item>
      <title>Frontend VS Backend</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Thu, 28 Dec 2023 12:30:57 +0000</pubDate>
      <link>https://dev.to/jorensm/frontend-vs-backend-3i2k</link>
      <guid>https://dev.to/jorensm/frontend-vs-backend-3i2k</guid>
      <description>&lt;p&gt;Hi, in this article I would like to tell you about what are frontend and backend, and the difference between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;A web application can typically be separated into 2 distinguishable parts - frontend and backend. Simply speaking, frontend is the 'presentational' part of the app, the part that is directly exposed to the user, i.e the page that the user interacts with, the UI, etc. Whereas backend is the part of the app that is not directly exposed to the user - the server and the database. The user usually cannot directly interact with the backend - instead they do it by interacting with the frontend and then the frontend passes whatever data is provided to the backend, and optionally the backend passes some data back to the frontend for the user to see.&lt;/p&gt;

&lt;p&gt;Developers are usually also divided into two groups - frontend developer and backend developer respectively. There is also 'full stack developer', who is a developer who does both frontend and backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;The frontend, the presentational part of the app, also often called the 'client', is basically anything that the user has direct access to. For example if we have a web app, then the frontend would be the page that the user is interacting with and the HTML, CSS, JavaScript that runs on the page.&lt;/p&gt;

&lt;p&gt;Frontend developers aim to implement the UI and UX according to the provided design, while also making sure that the code is well-written, semantic and performant. It is important to write semantic frontend code because it is the code that gets exposed to screen readers. And it is also important to write performant code for a great user experience.&lt;/p&gt;

&lt;p&gt;Frontend code usually deals with components/elements, state and styles of the webpage.&lt;/p&gt;

&lt;p&gt;Security on the frontend is practically (or even completely) non-existent, as the user usually has complete control over the code that gets run on the frontend. Still, it's a good idea to secure your frontend as much as possible, but beware that any frontend security can be bypassed. One method of securing frontend code is obfuscating code by running it through a JavaScript bundler.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend frameworks
&lt;/h3&gt;

&lt;p&gt;A frontend framework is a library that eases the development of the frontend of the app by offering features such as reusable components and state management. Some notable examples are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;VueJS&lt;/li&gt;
&lt;li&gt;Angular&lt;/li&gt;
&lt;li&gt;Svelte&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But there are many more frontend frameworks that you can find with a simple Google search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;p&gt;The backend - the server and database, are parts of a web app that the user doesn't have direct access to. The user may communicate with the backend through the frontend, but not directly(usually). The backend deals with things like HTTP requests, REST APIs, authentication.&lt;/p&gt;

&lt;p&gt;Backend developers deal with the implementation of backend features such as handling of routes, database access, authentication, and overall architecture of the backend system.&lt;/p&gt;

&lt;p&gt;Backend code is much more asynchronous in contrast to frontend code, because it usually must deal with many requests at once and perform many actions concurrently. As such, backend code heavily uses things like async functions, multi-threading and events.&lt;/p&gt;

&lt;p&gt;The backend is much more secure (if done properly) than the frontend, and because of this any sensitive information must be stored on the backend. The backend can be secured by using authentication methods such as sessions, JWTs or OAuth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend languages
&lt;/h3&gt;

&lt;p&gt;In web dev, the frontend is almost always written in HTML, CSS and JavaScript, whereas the backend is usually written in some programming language, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;PHP&lt;/li&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend can also be written in JavaScript using runtimes such as NodeJS or Bun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hybrids
&lt;/h2&gt;

&lt;p&gt;There exist a good amount of apps and programs that run only locally, without the need for a server. In this case you could say that the app is frontend-only, but I think it is more useful to still separate this app into frontend and backend. In such an app, the frontend would be the parts of the app that the user directly interacts with, such as the UI, while the backend would be any behind-the-scenes logic such as file writing/reading, mathematical operations, etc.&lt;/p&gt;

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

&lt;p&gt;In this article we explored the concepts of frontend and backend, and the difference between the two.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed reading this article, and I hope you learned something new from it!&lt;/p&gt;

&lt;p&gt;Have a great day!&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>backend</category>
      <category>comparison</category>
      <category>beginners</category>
    </item>
    <item>
      <title>My Developer Journey</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Thu, 28 Dec 2023 09:22:53 +0000</pubDate>
      <link>https://dev.to/jorensm/my-developer-journey-445o</link>
      <guid>https://dev.to/jorensm/my-developer-journey-445o</guid>
      <description>&lt;p&gt;&lt;em&gt;Image by &lt;a href="https://pixabay.com/users/joshuaworoniecki-12734309/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=5089188"&gt;Joshua Woroniecki&lt;/a&gt; from &lt;a href="https://pixabay.com//?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=5089188"&gt;Pixabay&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hello, my name is Jorens.&lt;/p&gt;

&lt;p&gt;In this article I would like to tell you my life journey and how I've become a developer.&lt;/p&gt;

&lt;p&gt;Since I was a very little kid I was very much into gaming, and also wanted to make games myself. When I found out that my uncle is a programmer, I felt inspired to become a game programmer myself.&lt;/p&gt;

&lt;p&gt;My first experience with progamming started with Java, when I was around 8 years old, where I printed out a PDF book for Java beginners. It was amazing writing my first programs and seeing them run. After tinkering with it for a few weeks I got bored and left programming up until I was around 14.&lt;/p&gt;

&lt;p&gt;At 14 I started learning C++ from an old book that my uncle had, as well as online resources. When I discovered libraries I was in awe - so many possibilities! I tinkered around with many libraries such as SDL, SFML and Irrlicht, eventually settling with SFML because it was the simplest and easiest to use. I never managed to make anything worth noting though, only lots of unfinished projects in their early stages of development (I bet most of you can relate). &lt;/p&gt;

&lt;p&gt;After tinkering with C++ for a few years until about 16, I started working with web technologies - HTML, CSS and JavaScript. I mostly did half-finished projects again.&lt;/p&gt;

&lt;p&gt;At 18 years old I stopped doing programming once again, up until around 21.&lt;/p&gt;

&lt;p&gt;At 21, that's when I really started seriously doing coding. I built up a portfolio, found some gigs on Upwork and started earning money. It felt great to earn money doing what you truly love. And that's what I'm still doing, at 24.&lt;/p&gt;

&lt;p&gt;Recently I've started writing articles on coding as I enjoy writing.&lt;/p&gt;

&lt;p&gt;So this is my developer journey.&lt;/p&gt;

&lt;p&gt;What was your developer journey like?&lt;/p&gt;

&lt;p&gt;I hope you enjoyed reading this, have a great day!&lt;/p&gt;

</description>
      <category>life</category>
      <category>story</category>
      <category>career</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to keep state between page refreshes in React</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Tue, 19 Dec 2023 13:15:03 +0000</pubDate>
      <link>https://dev.to/jorensm/how-to-keep-state-between-page-refreshes-in-react-3801</link>
      <guid>https://dev.to/jorensm/how-to-keep-state-between-page-refreshes-in-react-3801</guid>
      <description>&lt;p&gt;Hello dear reader!&lt;/p&gt;

&lt;p&gt;In this article I would like to show you how to keep state between page refreshes in React. Check it out in action:&lt;/p&gt;

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

&lt;p&gt;This sort of thing is called &lt;strong&gt;state persistance&lt;/strong&gt;, and it allows you to keep your state intact even through page refreshes or closing of the browser.&lt;/p&gt;

&lt;p&gt;State persistance has many use cases, for example you might want to keep the filled fields in a form stay even after the user has left the page and then came back later.&lt;/p&gt;

&lt;p&gt;Making state persistant is actually quite easy. We will be utilizing local storage for this.&lt;/p&gt;

&lt;p&gt;This project will be using TypeScript, but you can use JavaScript too, in which case just omit the types.&lt;/p&gt;

&lt;p&gt;So without further ado, let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;We will be creating a hook called &lt;code&gt;usePersistState()&lt;/code&gt; that will be used just like a regular state hook, with the added bonus of being persistant.&lt;/p&gt;

&lt;p&gt;This article assumes that you already know how to set up a project, so I will skip that part. Feel free to use any tool such as CRA, Vite, NextJS, etc. for setting up the project.&lt;/p&gt;

&lt;p&gt;Our project structure will look like this&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;index.tsx - page where we will test our hook&lt;/li&gt;
&lt;li&gt;usePersistState.ts - file where we will hold our hook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now that we have laid out our project structure, we can start coding!&lt;/p&gt;

&lt;h2&gt;
  
  
  usePresistState.ts
&lt;/h2&gt;

&lt;p&gt;First of all, let's scaffold our hook function:&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;usePersistState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;initial_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&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;void&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;So we have declared a function &lt;code&gt;usePersistState&lt;/code&gt; that has a generic type &lt;code&gt;T&lt;/code&gt;. As arguments you can specify the state's initial value, as well as an unique id to identify our state within local storage. Finally we specify it's return type as an array containing the state and the state setter function (just like a regular &lt;code&gt;useState()&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Next let's create a new &lt;code&gt;_initial_state&lt;/code&gt; variable that will determine whether to use the value from local storage or the value passed to the hook.&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;// Set initial value&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;_initial_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&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;local_storage_value_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;state:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// If there is a value stored in localStorage, use that&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;local_storage_value_str&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="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;local_storage_value_str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="c1"&gt;// Otherwise use initial_value that was passed to the function&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;initial_value&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;What we do here is check if localStorage has an item already stored for our state. If yes, then we use the localStorage's value for our initial state. Otherwise we use the &lt;code&gt;initial_value&lt;/code&gt; that was passed as an argument to our hook. We wrap this in &lt;code&gt;useMemo&lt;/code&gt; so the &lt;code&gt;_initial_value&lt;/code&gt; gets set only once, upon mount (because we only need this upon mount).&lt;/p&gt;

&lt;p&gt;Next let's add the state itself that uses the &lt;code&gt;_initial_value&lt;/code&gt; we defined in the previous code snippet.&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;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setState&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="nx"&gt;_initial_value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now let's add add an &lt;code&gt;useEffect&lt;/code&gt; that runs every time our state changes, in order to save the state into local storage:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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;state_str&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;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Stringified state&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;state:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Set stringified state as item in localStorage&lt;/span&gt;
&lt;span class="p"&gt;},&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What this does is simply, whenever our &lt;code&gt;state&lt;/code&gt; changes, store the &lt;code&gt;state&lt;/code&gt; value in localStorage.&lt;/p&gt;

&lt;p&gt;Finally, let's return our &lt;code&gt;state&lt;/code&gt; and &lt;code&gt;setState()&lt;/code&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;return&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;setState&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Aaaand we're done! Our hook is ready, now we can test it out!&lt;/p&gt;

&lt;h2&gt;
  
  
  index.tsx
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;index.tsx&lt;/code&gt;, paste the following code:&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;usePersistState&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;@printy/react-persist-state/src/index&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="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="s1"&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;PersistStateExample&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;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCounter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePersistState&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&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;&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
                &lt;span class="nx"&gt;onClick&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;setCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;counter&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;/button&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;/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;What we do here is create a &lt;code&gt;counter&lt;/code&gt; state using our newly made hook and a button that shows the &lt;code&gt;counter&lt;/code&gt; value as well as increments the counter whenever it is clicked. Try clicking it a few times, then refreshing the page. You will see that the state has persisted. You can even completely close your browser and repoen the page, and the state will have remained!&lt;/p&gt;

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

&lt;p&gt;In this article we learned how to create persistant state in React that doesn't reset when we refresh the page. I hope this article was useful to you and that you will find a way to use this method.&lt;/p&gt;

&lt;p&gt;If you have any questions or feedback, feel free to leave a comment down below!&lt;/p&gt;

&lt;p&gt;Thanks for reading, and have a great day!&lt;/p&gt;

</description>
      <category>state</category>
      <category>react</category>
      <category>persistance</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to sync React state across tabs with workers</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Mon, 18 Dec 2023 12:46:08 +0000</pubDate>
      <link>https://dev.to/jorensm/how-to-sync-react-state-across-tabs-with-workers-2mpg</link>
      <guid>https://dev.to/jorensm/how-to-sync-react-state-across-tabs-with-workers-2mpg</guid>
      <description>&lt;p&gt;Hello, in this article I will show you how you can sync state in React across multiple tabs. We will be using the SharedWorker API to achieve this. If you want to skip the tutorial and just use it, I have an npm package &lt;a href="https://www.npmjs.com/package/@printy/react-tab-state" rel="noopener noreferrer"&gt;react-tab-state&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The tutorial is written in TypeScript (aside from the worker), but you can just as well use JavaScript for this.&lt;/p&gt;

&lt;p&gt;This is what the final result will look like:&lt;/p&gt;

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

&lt;p&gt;So without further ado, let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;I will assume that you already know how to set up a React project. You can use CRA or Vite or even NextJS, just make sure that the environment supports shared workers(it probably does)!&lt;/p&gt;

&lt;p&gt;For this project we will have 3 main files -&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index.tsx&lt;/code&gt;, where our page code will reside&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;useTabState.ts&lt;/code&gt;, where the code for our tab- synced-state hook will reside&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;worker.js&lt;/code&gt;, where the code for our shared worker will reside&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  worker.js
&lt;/h2&gt;

&lt;p&gt;First of all let's write the code of our worker.&lt;/p&gt;

&lt;p&gt;First let's add some variables&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;let&lt;/span&gt; &lt;span class="nx"&gt;ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// All of our ports&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;latest_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// The currently synced states.&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;So first we have &lt;code&gt;ports&lt;/code&gt; which stores all of open ports. There is a single port for each tab. A port is basically a connection between a tab and the shared worker, which allows you to send and receive messages. We need this array so we can send a single message to all ports at once&lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;latest_state&lt;/code&gt; stores the most recent synced state values in an object. Each key in the object will be a unique id for that particular state. This is so we can have multiple states. We will need this variable in order to sync the state of a newly opened tab&lt;/p&gt;

&lt;p&gt;Now let's add a function to post a message to all ports&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;// Post message to all connected ports&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postMessageAll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;excluded_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;ports&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;port&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;// Don't post message to the excluded port, if one has been specified&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;port&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;excluded_port&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;}&lt;/span&gt;
        &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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;What this does is simply post a provided mesage to all ports in the &lt;code&gt;ports&lt;/code&gt; array. You can also optionally provide an &lt;code&gt;excluded_port&lt;/code&gt;, to which the message won't be sent. This is so that when a tab wants to update state in the rest of the tabs, you don't send a message back to the initiator tab.&lt;/p&gt;

&lt;p&gt;Let's write an &lt;code&gt;onconnect&lt;/code&gt; handler. Here we will define message handlers and some initialization logic&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;onconnect&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="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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ports&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="nx"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the code above, we have created an &lt;code&gt;onconnect&lt;/code&gt; handler and also made sure that the newly connected port gets been added to our &lt;code&gt;ports&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;onconnect&lt;/code&gt; handler, let's add handlers for receiving messages&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;port&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;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="c1"&gt;// Sent by a tab to update state in other tabs&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;e&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;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set_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="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Sent by a tab to request the value of current state. Used when initializing the state.&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;e&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;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_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="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;What the code above does is create a message handler for the connected port. So each time a tab sends a message to the worker, this handler will be invoked. Then in the handler we check the type of message and act accordingly.&lt;/p&gt;

&lt;p&gt;Now let's add a handler for each message&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;e&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;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set_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="nx"&gt;latest_state&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;data&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="nf"&gt;postMessageAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;id&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="nx"&gt;latest_state&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This handler will get called when a tab sends a &lt;code&gt;set_state&lt;/code&gt; message, a.k.a when state is updated in one of the tabs. When this happens, we update our &lt;code&gt;latest_state&lt;/code&gt; with the state that the tab has sent us for the key with a matching ID, as well as send the new state to all the other tabs. We exclude the tab that initially sent the message because it already has the new state.&lt;/p&gt;

&lt;p&gt;Next let's add our &lt;code&gt;get_state&lt;/code&gt; handler. It will be called when a tab with our page first opens, to retrieve the up-to-date state value.&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;e&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;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&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;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessageAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;id&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="nx"&gt;latest_state&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;What we do here is simply send a &lt;code&gt;set_state&lt;/code&gt; message to the tab that requested it, with our up-to-date state.&lt;/p&gt;

&lt;p&gt;And that's it for the worker! Pretty simple, right? We're about halfway done.&lt;/p&gt;

&lt;p&gt;Now let's write our &lt;code&gt;useTabState&lt;/code&gt; hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  useTabState.ts
&lt;/h2&gt;

&lt;p&gt;Our useTabState will be used the same way as a regular &lt;code&gt;useState&lt;/code&gt; - you call the hook and it will return an array with the state and a &lt;code&gt;setState()&lt;/code&gt; function&lt;/p&gt;

&lt;p&gt;Let's start writing our function:&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useTabState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;initial_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&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;void&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;localState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLocalState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So far we have made a hook that accepts 2 arguments - &lt;code&gt;initial_state&lt;/code&gt; for the initial state and &lt;code&gt;id&lt;/code&gt; for a unique ID to distinguish between multiple tab states.&lt;/p&gt;

&lt;p&gt;Then we create a state called &lt;code&gt;localState&lt;/code&gt; and set its initial value to &lt;code&gt;null&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You may have noticed that we're using generics here. If you're using TypeScript but don't know how to use generics or don't want to use them, you can remove the &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; and change any occurence of &lt;code&gt;T&lt;/code&gt; to &lt;code&gt;any&lt;/code&gt;. If you're using plain JavaScript then you can remove the &lt;code&gt;&amp;lt;T&amp;gt;&lt;/code&gt; and omit the types completely.&lt;/p&gt;

&lt;p&gt;Next up let's create a function below the &lt;code&gt;localState&lt;/code&gt; that updates the state across all tabs&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;setTabState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&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;setLocalState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;worker&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="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&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="nx"&gt;new_state&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;What this function does is set its localstate as well as sends a &lt;code&gt;set_state&lt;/code&gt; message to the worker, resulting in state being synced across all tabs. This is the function that our hook will return.&lt;/p&gt;

&lt;p&gt;We're almost done! Now all we have to do is create a listener for when the shared worker sends a message to the tab.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;useEffect&lt;/code&gt; and add the following code to it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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;worker&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="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;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;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="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;e&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;type&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;}&lt;/span&gt;

        &lt;span class="k"&gt;switch &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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set_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="k"&gt;if &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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;id&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;e&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;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;setLocalState&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;data&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;setLocalState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initial_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="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;worker&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="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// This is important, otherwise the messages won't be received.&lt;/span&gt;

    &lt;span class="nx"&gt;worker&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="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&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 you can see in the code above, we add a message event listener and update our local state with the up-to-date state when a &lt;code&gt;set_state&lt;/code&gt; message has been received. Also we send a &lt;code&gt;get_state&lt;/code&gt; message upon mount to get the up-to-date state. If the up-to-date state is null, then we use the &lt;code&gt;initial_state&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Finally, let's return our &lt;code&gt;localState&lt;/code&gt; as well as the &lt;code&gt;setTabState&lt;/code&gt; function:&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;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;localState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTabState&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And we're done! Now all that is left is to test our hook!&lt;/p&gt;

&lt;h2&gt;
  
  
  index.tsx
&lt;/h2&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;useTabState&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;./useTabState&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;TabStateExample&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;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCounter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useTabState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&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;&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
                &lt;span class="nx"&gt;onClick&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;setCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;counter&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;/button&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;/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;In the code above, we simply create a button that shows a number, and increments this number each time it is clicked. Try testing this page with multiple tabs!&lt;/p&gt;

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

&lt;p&gt;In this article we explored a simple way how one can add a tab-synced state behavior to their app using the SharedWorker API. I hope you learned something new and that you will find a use for this pattern. If you don't want to go through the hassle of learning and implementing this yourself (though it's quite simple), you can use my NPM package &lt;a href="https://www.npmjs.com/package/@printy/react-tab-state" rel="noopener noreferrer"&gt;react-tab-state&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have any questions or feedback, feel free to leave it in the comments or email me at &lt;a href="//mailto:jorensmerenjanu@gmail.com"&gt;jorensmerenjanu@gmail.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Good luck in your dev journey and your projects!&lt;/p&gt;

</description>
      <category>tab</category>
      <category>sync</category>
      <category>state</category>
      <category>react</category>
    </item>
    <item>
      <title>How to avoid the ripple effect when programming</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Thu, 05 Oct 2023 06:56:16 +0000</pubDate>
      <link>https://dev.to/jorensm/how-to-avoid-the-ripple-effect-when-programming-192n</link>
      <guid>https://dev.to/jorensm/how-to-avoid-the-ripple-effect-when-programming-192n</guid>
      <description>&lt;p&gt;Image by &lt;a href="https://pixabay.com/users/200degrees-2051452/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=1745705"&gt;200 Degrees&lt;/a&gt; from &lt;a href="https://pixabay.com//?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=1745705"&gt;Pixabay&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I would like to start this article by saying that the methods shared here are not the only methods available and there are many other ways how one can avoid the ripple effect.&lt;/p&gt;

&lt;p&gt;Also I would like to say that I am not a senior, but rather a mid-level programmer and as such the tips shared in this article may be lacking in some aspects. Feel free to correct me or suggest improvements. &lt;/p&gt;

&lt;p&gt;And finally I would like to thank you for taking your time to read this article, it means a lot to me!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;
How to avoid the ripple effect when programming

&lt;ul&gt;
&lt;li&gt;The premise&lt;/li&gt;
&lt;li&gt;Think ahead and think thoroughly&lt;/li&gt;
&lt;li&gt;Write abstract code&lt;/li&gt;
&lt;li&gt;Make your code backwards compatible&lt;/li&gt;
&lt;li&gt;Define good interfaces&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to avoid the ripple effect when programming
&lt;/h2&gt;

&lt;p&gt;What is the ripple effect? You have most likely encountered it before when coding, whether you know about it or not. Simply put, in programming, the &lt;strong&gt;ripple effect&lt;/strong&gt; is when you make changes to your existing code, and it causes your codebase to break, requiring you to make even more changes to your code. Let me demonstrate with a simple example:&lt;/p&gt;

&lt;p&gt;Let's say you have a function &lt;code&gt;sum()&lt;/code&gt; which takes two number arguments and returns the sum of these number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function sum(a, b) {
    return a + b
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's say that you start using this method extensively in your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//file a.js...
const variable = sum(1, 5)

//...file b.js...
const another_variable = sum(x1, x2)

//...file g.js...
const yet_another_varible = sum(y1, y2)

//...and so on...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's say that you've decided to update the &lt;code&gt;sum()&lt;/code&gt;function to be able to sum an indefinite amount of numbers, so you change it to accept as an argument a single array and return the sum of the numbers in that array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function sum(numbers) {
    let total = 0
    numbers.forEach( n =&amp;gt; total += n)

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

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(In JavaScript this can be solved by using the spread operator to accept an indefinite amount of args, but for the sake of the example I'm using an array)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Okay, so we've updated our function, great!&lt;/p&gt;

&lt;p&gt;But now we have to go through our code and update each call to &lt;code&gt;sum()&lt;/code&gt; to the new format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//file a.js...
const variable = sum([1, 5])

//...file b.js...
const another_variable = sum([x1, x2])

//...file g.js...
const yet_another_varible = sum([y1, y2])

//...and so on...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the ripple effect in action.&lt;/p&gt;

&lt;p&gt;The ripple effect is not as relevant in a small project where there is not much code (though it's still good to follow best practices), but it is quite relevant in larger  projects where a single change can require us to make many other changes across the codebase.&lt;/p&gt;

&lt;p&gt;So now that we know what the ripple effect is, let's explore how we can prevent it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The premise
&lt;/h3&gt;

&lt;p&gt;The premise upon which we will be solving the ripple effect problem is the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the ripple effect is caused by changes in the outcome, and not the process. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By this I mean that changing the code in a way thay doesn't change its API/interface/return values - but instead only changes only how the outcome is achieved - won't cause the ripple effect. So to prevent the ripple effect, we must minimize the changes in the outcome of our code. Below are some tips to achieve this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Think ahead and think thoroughly
&lt;/h3&gt;

&lt;p&gt;Before you start writing any code, carefully think through where and how it will be used and how it may be used in the future. Consider all the possible use cases of the code that you are going to implement, both the current use cases and possible future use cases. This will ensure that your code will be long lasting and won't require many changes along the way, which will help you avoid the ripple effect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write abstract code
&lt;/h3&gt;

&lt;p&gt;It's not always the case, but oftentimes a function or a class can become more useful and less prone to causing a ripple effect if you make it abstract.&lt;/p&gt;

&lt;p&gt;For example, let's say you have a function that takes a string and a name of a color and outputs the string in the console, in the specified color&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function logWithColor(str, color) {
    switch(color) {
        case 'green':
            //Code for outputting green text
            break
        case 'red':
            //Code for outputting red text
            break
        case 'blue':
            //Code for outputting blue text
            break
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;currently, you can only pass specific names of color strings for the colors, such as 'green', 'red', etc. This may become cumbersome if we decide to update the definition of 'green' to a different hex value, which will make each log using 'green' output a different color than before, which may not be what we want.&lt;/p&gt;

&lt;p&gt;This can be solved by making our function more abstract. We can do this by changing the function to accept a hex color as an argument instead of a specific color name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function logWithColor(str, hexColor) {
    //Code to output text according to the hex color
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we can call it and use a specific hex color, and not worry that the definition of 'color' changes.&lt;/p&gt;

&lt;p&gt;Do note that it is not always favorable to abstract code away, and sometimes it is better to keep it specific.&lt;/p&gt;

&lt;p&gt;We've updated our function to be more abstract, but now we've met with a new problem - any code that used the old format of the function will break because now it accepts a hex string instead of a color name - see the next section for the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make your code backwards compatible
&lt;/h3&gt;

&lt;p&gt;Making code backwards compatible means changing code in such a way that it still supports the old format of accessing it.&lt;br&gt;
Let's take the function from our previous example. After we updated the function to accept hex colors, any code that uses the old format will need to be updated to use the new format. This can be avoided by making the function backwards compatible. We can do this by making the function process both hex values and color names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function logWithColor(str, color) {
    switch(color) {
        case 'green':
            //Code for outputting green text
            break
        case 'red':
            //Code for outputting red text
            break
        case 'blue':
            //Code for outputting blue text
            break
        default:
            //Code for outputting hex
            break
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will ensure that any code that uses the old function's format won't break, and in turn will help us avoid the ripple effect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define good interfaces
&lt;/h3&gt;

&lt;p&gt;Another method to avoid the ripple effect is to write good interfaces.&lt;/p&gt;

&lt;p&gt;What is an interface? An &lt;strong&gt;interface&lt;/strong&gt; is a part of a system that is exposed to the user(also called the client), AKA the &lt;strong&gt;public&lt;/strong&gt; part. The &lt;strong&gt;user&lt;/strong&gt; can be anything that uses the interface - an end-user using an app, a program using a library or making requests to a REST API. In these examples, the interfaces respectively are the user-interface of the app, the methods and classes of the library that the program can access, and the endpoints that the program can call.&lt;/p&gt;

&lt;p&gt;When speaking within the scope of a single program, an interface can be anything that another part of the program uses. For example, if you have a program that calls function A, function A can be said to be an interface that your program is using.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/2866987/what-is-the-definition-of-interface-in-object-oriented-programming"&gt;In OOP, an interface&lt;/a&gt; is a more specific term that describes a specific type of class - a class with the purpose of describing the public parts of it (so the idea is the same). But in these examples an interface can be anything that is used by a client(or user)&lt;/p&gt;

&lt;p&gt;So how do we define a good interface? In my opinion the first step to writing a good interface is writing it in such a way that will make it extensible. &lt;br&gt;
This builds on top of the first tip - think ahead.&lt;/p&gt;

&lt;p&gt;Making an interface extensible is similar to making it backwards compatible. Making an interface extensible means &lt;em&gt;defining it in such a way that any future changes won't require changing the existing parts&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let's say we have the following class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point {
    x = 0
    y = 0
    setPosition(x, y){
        this.x = x
        this.y = y
    }
    getPosition() {
        return {
            x: this.x,
            y: this.y
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This class holds two variables and two methods - &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; for the position, &lt;code&gt;setPosition()&lt;/code&gt; for setting the position variables, and &lt;code&gt;getPosition()&lt;/code&gt; for retrieving them.&lt;/p&gt;

&lt;p&gt;Now let's say we've decided to make this class 3D and support the Z axis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point {
    x = 0
    y = 0
    z = 0

    setPosition(x, y, z) {
        this.x = x
        this.y = y
        this.z = z
    }

    getPosition() {
        return {
            x: this.x,
            y: this.y,
            z: this.z
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the way we had initially written the class, we needed to rewrite the &lt;code&gt;setPosition()&lt;/code&gt; and &lt;code&gt;getPosition()&lt;/code&gt; methods to accommodate the newly added &lt;code&gt;z&lt;/code&gt; variable. This means that the class wasn't extensible, because when we made additions to it, we had to rewrite some of the existing code.&lt;/p&gt;

&lt;p&gt;Let's try writing our class again from the beginning, but this time in such a way that we don't need to rewrite code when making additions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point {
    position = {
      x: 0
      y: 0
    }
    setPosition(position) {
        this.position = position
    }
    getPosition() {
        return this.position
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, this time we've written the class a bit differently - we store the position as a single object and accept an object in the &lt;code&gt;setPosition()&lt;/code&gt; method, and directly return the position object in the &lt;code&gt;getPosition()&lt;/code&gt; method. You'll see why we've done this in a second.&lt;/p&gt;

&lt;p&gt;So now let's try extending our class with the Z position once again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point {
    position = {
      x: 0
      y: 0
      z: 0
    }
    setPosition(position) {
        this.position = position
    }
    getPosition() {
        return this.position
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, this time all we had to do was add a &lt;code&gt;z&lt;/code&gt; property to our position object - we didn't need to alter &lt;code&gt;getPosition()&lt;/code&gt; and &lt;code&gt;setPosition()&lt;/code&gt; at all.&lt;/p&gt;

&lt;p&gt;Be mindful that it's better to write extensible code from the start, otherwise you will still have to face the ripple effect and rewrite the code that uses the old format of the interface (unless you make it backwards compatible)&lt;/p&gt;

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

&lt;p&gt;In this article we explored the root cause of the ripple effect in programming and explored some ways how we can prevent it when writing our code.&lt;/p&gt;

&lt;p&gt;Even with all the tips available from this and other resources, it may not always be evident how you can prevent the ripple effect in your specific cases. Ultimately, it is something that comes with experience and as you code more and more, you will naturally learn to solve problems like these before they've occurred. &lt;/p&gt;

&lt;p&gt;Thank you so much for reading this article and I hope you've learned something from it. If you have any feedback or questions, feel free to leave them in the comments or email me at &lt;a href="//mailto:jorensmerenjanu@gmail.com"&gt;jorensmerenjanu@gmail.com&lt;/a&gt; (I will definitely respond - I'm not a busy person)&lt;/p&gt;

</description>
      <category>bestpractices</category>
      <category>intermediate</category>
      <category>tips</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to make command-line apps accessible?</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Sat, 23 Sep 2023 07:54:18 +0000</pubDate>
      <link>https://dev.to/jorensm/how-to-make-command-line-apps-accessible-10ho</link>
      <guid>https://dev.to/jorensm/how-to-make-command-line-apps-accessible-10ho</guid>
      <description>&lt;p&gt;Hi, recently I started working on a command-line tool and wanted to make it accessible. I posted a question about it on stackoverflow but didn't get any answers, so I've decided to ask you, the DEV community - how does one make their command-line app accessible?&lt;/p&gt;

&lt;p&gt;Also general dicussion about command-line accessibility and accessibility in general.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>terminal</category>
      <category>cli</category>
      <category>a11y</category>
    </item>
    <item>
      <title>How to practice writing</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Sun, 10 Sep 2023 16:08:23 +0000</pubDate>
      <link>https://dev.to/jorensm/how-to-practice-writing-296m</link>
      <guid>https://dev.to/jorensm/how-to-practice-writing-296m</guid>
      <description>&lt;p&gt;Image by &lt;a href="https://pixabay.com/users/6689062-6689062/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=2850091"&gt;David Schwarzenberg&lt;/a&gt; from &lt;a href="https://pixabay.com//?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=2850091"&gt;Pixabay&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article was &lt;a href="https://jorensmerenjanu.medium.com/how-to-practice-writing-a849204fe5a2"&gt;originally published on Medium&lt;/a&gt;, but I thought that it would fit nicely here on DEV because many of us are not just developers, but also writers.&lt;/p&gt;

&lt;p&gt;There is a popular saying - 'practice makes perfect', and I completely agree with it. If you repeat an activity for 10, 100, 1000 times, you are certainly going to get good at it eventually. The same principle applies to writing, too. If you write once every day, you are going to be a better writer than if you had been writing once every week or not at all. Practice certainly helps you improve. So if you want to become a good writer - &lt;em&gt;practice&lt;/em&gt;. Practice every day, or practice every week - it doesn't matter - go at your own pace that feels comfortable to you.&lt;/p&gt;

&lt;p&gt;But how do you practice writing? That is the topic of this article. In this article we will explore some common and effective ways how you can practice your writing to become a better writer, and what aspect of writing each method helps you improve. The methods will focus on improving your article writing skills as opposed to story writing or any other type of writing. But worry not, as these methods can be applied to any type of writing.&lt;/p&gt;

&lt;p&gt;So let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Reading&lt;/li&gt;
&lt;li&gt;Freewriting&lt;/li&gt;
&lt;li&gt;Rewriting others' work in your own words&lt;/li&gt;
&lt;li&gt;Participating in writers' communities&lt;/li&gt;
&lt;li&gt;Keeping a diary/journal&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reading
&lt;/h2&gt;

&lt;p&gt;The first method doesn't actually involve any writing, ironically. Here is a quote from the popular American writer Stephen King:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"if you want to be a writer, you must do two things above all others: read a lot and write a lot.ˮ - &lt;strong&gt;Stephen King&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reading is a very effective way to improve your writing skills. It improves you in every aspect of writing - grammar, vocabulary, originality. But be picky about what you read. Every person's writing habits are more or less a sum of all the work that they have read in the past. So what you read will be what you will write. That is why it's important to carefully choose what you read, as it will reflect on your writing.&lt;/p&gt;

&lt;p&gt;Reading improves you not just as a writer, but as a person - it helps you develop your perspective and helps you gain knowledge.&lt;/p&gt;

&lt;p&gt;Read books, read magazines, read newspaper's - doesn't matter, as long as you read. It is a good idea to read a bit on a topic before starting to write about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freewriting
&lt;/h2&gt;

&lt;p&gt;Freewriting is the act of 'brainstorm-writing', where you write something spontaneously without having thought about it beforehand.&lt;/p&gt;

&lt;p&gt;This type of writing improves your idea making skills and critical thinking. (In regards to writing, critical-thinking means that you can quickly come up with original and fitting paragraphs).&lt;/p&gt;

&lt;p&gt;When freewriting, don't worry about the outcome of your writing - don't worry about grammar, spelling mistakes, etc. The goal of freewriting isn't to improve your grammar, but to improve your originality, so grammar doesn't matter here.&lt;/p&gt;

&lt;p&gt;Freewriting improves you not just in the area of writing, but in general. It helps you develop your creative skills and critical thinking, which you can apply not just to writing, but to pretty much anything in life. Here is an &lt;a href="https://www.grammarly.com/blog/free-writing/"&gt;informative article from Grammarly&lt;/a&gt; on freewriting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewriting others' work in your own words
&lt;/h2&gt;

&lt;p&gt;This is my favorite one, and I do it the most often. Paraphrasing others' work is a rewarding way of practicing writing and at the same time learning something new. When you rewrite others' work, you learn to extract key information from content, which is especially useful if you're writing informative articles. There are some things to keep in mind though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't plagiarize.&lt;/strong&gt; This is very important. Taking someone else's work and passing it as your own is seen as taboo and frowned upon in the writers' community.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't blatantly copy.&lt;/strong&gt; If you copy someone's work word-by-word, you won't learn anything new. The advantage of rewriting someone's work is that you learn to extract information from it, which you won't learn if you just copy word-by-word.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do note that in some cases, it is considered plagiarism even if you paraphrase someone's work. In cases where the article presents original ideas and concepts not seen anywhere else, it is considered plagiarism even to paraphrase the article. So be careful what you paraphrase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Participating in writers' communities
&lt;/h2&gt;

&lt;p&gt;There are many writers' communities, both in the real world and on the internet. Participating in these communities and their events and activities will definitely help you improve your writing skills. You will get a chance to collaborate with others and learn from them. You can find your local or online communities by searching for them on Google. Some notable online writing communities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.reddit.com/r/WritingPrompts/"&gt;Writing Prompts subreddit&lt;/a&gt; on Reddit.com. On this subreddit, people post their prompts and writers try to write a story according to these prompts.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.reddit.com/r/writing/"&gt;Writing subreddit&lt;/a&gt; on Reddit.com. This is a general subreddit where people discuss their writing tools, techniques, ideas and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many more communities online that you can find with a simple Google search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping a diary/journal
&lt;/h2&gt;

&lt;p&gt;Keeping a journal is a good way to practice writing because you will do it every day. Keeping a journal and writing into it regularly will reinforce your habit of writing, which will make you a better writer because, as we discussed earlier, practice makes perfect. Write down your activities, plans and thoughts.&lt;/p&gt;

&lt;p&gt;Keeping a journal is a bit similar to freewriting because you don't think too much ahead of what you write, except that you usually write into a journal regularly and the content is a bit more structured/clean.&lt;/p&gt;

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

&lt;p&gt;In this article we explored some ways how one can practice and improve their writing skills. I hope you learned something from this article and I hope that these methods will help you become a better writer. Do you have a practice method that wasn't mentioned in this article? Let me know in the comments!&lt;/p&gt;

&lt;p&gt;Thank you so much for reading!&lt;/p&gt;

</description>
      <category>writing</category>
      <category>practice</category>
      <category>howto</category>
      <category>learning</category>
    </item>
    <item>
      <title>Creating a form In React Native With Formik</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Thu, 31 Aug 2023 10:35:55 +0000</pubDate>
      <link>https://dev.to/jorensm/creating-forms-in-react-native-with-formik-3hg</link>
      <guid>https://dev.to/jorensm/creating-forms-in-react-native-with-formik-3hg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Forms are an integral part of many mobile applications. Whether it's a sign-up/sign-in form, a 'create post' form or any other type of form, one thing is for sure - forms are important.&lt;/p&gt;

&lt;p&gt;What is a form? You probably already know what a form is, but let me give you a definition anyway. In software, a form is a UI element that allows you to fill out data fields and then store this data or send this data to the software's servers. Forms allow the user to provide information to the app to create new content or to tailor the app experience to the user's preferences, among many other things.&lt;/p&gt;

&lt;p&gt;Do you want to create a form in your React Native app but don't know how? Then this post is for you! In this post I will teach you how to create forms using a library called &lt;a href="https://formik.org" rel="noopener noreferrer"&gt;Formik&lt;/a&gt; , as well as how to integrate non-native form components with Formik. Additionally you will learn how to validate forms using &lt;a href="https://github.com/jquense/yup" rel="noopener noreferrer"&gt;Yup&lt;/a&gt; (which Formik supports out of the box)&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Setup&lt;/li&gt;
&lt;li&gt;Building the components&lt;/li&gt;
&lt;li&gt;Building the form&lt;/li&gt;
&lt;li&gt;Adding validation&lt;/li&gt;
&lt;li&gt;Improving our code - pass props down&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/JorensM/react-native-form" rel="noopener noreferrer"&gt;Source code for this project&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Before we begin working on our project, we must set it up and install all the necessary dependencies. We will be using &lt;a href="https://docs.expo.dev/" rel="noopener noreferrer"&gt;Expo&lt;/a&gt; for this project. Expo is a React Native management tool that handles a lot of heavy stuff for us, such as efficiently compiling code and assets, as well as provides us with a productive development environment. Also we will be using Typescript for this project, but don't worry, the process is practically identical to Javascript, so if you don't know Typescript that's fine. This article assumes some basic understanding of &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt; and &lt;a href="https://reactnative.dev/" rel="noopener noreferrer"&gt;React Native&lt;/a&gt; , such as components, hooks and state. You must also have Node.js installed on your computer.&lt;/p&gt;

&lt;p&gt;First things first, let's set up our Expo project. Navigate to the directory where you want your project folder to reside, and run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-expo-app -t expo-template-blank-typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will set up a fresh expo app with a TypeScript template.&lt;/p&gt;

&lt;p&gt;You may be prompted to install a package, in which case just press enter to confirm its installation. After that, you will be prompted to name your project. You can name it any way you want - I named mine &lt;code&gt;react-native-form&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To test that our app works, run &lt;code&gt;npm run web&lt;/code&gt; or &lt;code&gt;npm run android&lt;/code&gt; or &lt;code&gt;npm run ios&lt;/code&gt;.If you're testing on mobile, make sure to have a device connected. If you're testing on web, you will get an error asking you to install necessary dependencies for web, so go ahead and install them and run the command again.&lt;/p&gt;

&lt;p&gt;Next up, let's install all the necessary dependencies. We will need Formik for form handling, Yup for form validation, &lt;a href="https://www.npmjs.com/package/react-native-element-dropdown" rel="noopener noreferrer"&gt;react-native-element-dropdown&lt;/a&gt; for our dropdown component and &lt;a href="https://github.com/miblanchard/react-native-slider" rel="noopener noreferrer"&gt;react-native-slider&lt;/a&gt; for our slider component. We don't need any libraries for text input because text input is natively supported by React Native.&lt;/p&gt;

&lt;p&gt;To install the aforementioned packages, run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i react-native-element-dropdown formik yup @miblanchard/react-native-slider`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we're ready to start coding!&lt;/p&gt;

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

&lt;p&gt;We will create 4 components for the form: &lt;code&gt;FieldBase&lt;/code&gt;, &lt;code&gt;SliderField&lt;/code&gt;, &lt;code&gt;TextField&lt;/code&gt;, &lt;code&gt;DropdownField&lt;/code&gt;. The latter 3 will extend from first one, so let's start with it!&lt;/p&gt;

&lt;h3&gt;
  
  
  FieldBase
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;FieldBase&lt;/code&gt; component will be used to reuse parts that are common across all field components, such as the label or the field layout.&lt;/p&gt;

&lt;p&gt;First, create a &lt;code&gt;components&lt;/code&gt; folder at the root of your project. In this directory we will hold all of our components.&lt;/p&gt;

&lt;p&gt;Next, in the newly created folder, create a file &lt;code&gt;FieldBase.tsx&lt;/code&gt;, or &lt;code&gt;FieldBase.jsx&lt;/code&gt; if you're using plain Javascript (from now on assume that all &lt;code&gt;.tsx&lt;/code&gt; files should be &lt;code&gt;.jsx&lt;/code&gt; if you're using JS):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FieldBase.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { PropsWithChildren } from 'react'
import { View } from 'react-native'

export type FieldBaseProps = {
    label: string
}

export default function FieldBase({ 
    label,
    children
}: PropsWithChildren&amp;lt;FieldBaseProps&amp;gt;) {
    return (
        &amp;lt;View&amp;gt;
            { children }
        &amp;lt;/View&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is the basic &lt;code&gt;FieldBase&lt;/code&gt; component. Currently all it can do is act as a wrapper for its children. &lt;code&gt;PropsWithChildren&lt;/code&gt; is a function that allows you to use the &lt;code&gt;children&lt;/code&gt; prop in your component (you don't need this function if you're using Javascript). The function accepts a &lt;a href="https://www.typescriptlang.org/docs/handbook/2/generics.html" rel="noopener noreferrer"&gt;generic&lt;/a&gt; that should be the type of your props.&lt;/p&gt;

&lt;p&gt;You may have noticed that we're also exporting &lt;code&gt;FieldBaseProps&lt;/code&gt;. This is so that we can reuse this type in our other components.&lt;/p&gt;

&lt;p&gt;Next, let's add some styles and have our component show a label.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FieldBase.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { PropsWithChildren } from 'react'
import { StyleSheet, View, Text } from 'react-native'

export type FieldBaseProps = {
    label: string
}

export default function FieldBase({ 
    label,
    children
}: PropsWithChildren&amp;lt;FieldBaseProps&amp;gt;) {
    return (
        &amp;lt;View
            style={ styles.container }
        &amp;gt;
            &amp;lt;Text
                style={ styles.label }
            &amp;gt;
                { label }
            &amp;lt;/Text&amp;gt;
            { children }
        &amp;lt;/View&amp;gt;
    )
}

const styles = StyleSheet.create({
    container: {
        flexDirection: 'column'
    },
    label: {
        fontSize: 14,
        color: '#a8a8a8'
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see we have added some styles to the component using the &lt;code&gt;StyleSheet.create()&lt;/code&gt; function. Learn more about &lt;a href="https://reactnative.dev/docs/stylesheet" rel="noopener noreferrer"&gt;StyleSheet&lt;/a&gt;. Additionally we have added a &lt;code&gt;Text&lt;/code&gt; component for our label.&lt;/p&gt;

&lt;p&gt;At this point, if you use this component somewhere in your app's code, like so:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;FieldBase
    label='My field'
  &amp;gt;
    &amp;lt;Text&amp;gt;Field input will be here&amp;lt;/Text&amp;gt;
&amp;lt;/FieldBase&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should see something like this:&lt;/p&gt;

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

&lt;p&gt;Awesome! So this will be the base of our fields that we will extend from. The purpose and advantage of creating such a 'base' component is that you can reuse code and avoid duplicating code.&lt;/p&gt;

&lt;p&gt;Next up is &lt;code&gt;TextField&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  TextField
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;components&lt;/code&gt; folder, create a file called &lt;code&gt;TextField.tsx&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TextField.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import FieldBase, { FieldBaseProps} from './FieldBase';

type TextFieldProps = FieldBaseProps &amp;amp; {
    name: string
}

export default function TextField({ 
    name, 
    label 
}: TextFieldProps) {
    return (
        &amp;lt;FieldBase
            name={name}
            label={label}
        &amp;gt;

        &amp;lt;/FieldBase&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see we've used the &lt;code&gt;FieldBase&lt;/code&gt; in our new component. This allows us to specify a label and show it without needing to rewrite the code for it. Right now the component can't do anything, so let's go ahead and introduce a &lt;code&gt;TextInput&lt;/code&gt; component to it!&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { TextInput, StyleSheet } from 'react-native'
import FieldBase, { FieldBaseProps} from './FieldBase';

type TextFieldProps = FieldBaseProps &amp;amp; {
    name: string
}

export default function TextField({
    name,
    label,
}: TextFieldFieldProps) {
    return (
        &amp;lt;FieldBase
            label={label}
        &amp;gt;
            &amp;lt;TextInput
                style={styles.input}
            /&amp;gt;
        &amp;lt;/FieldBase&amp;gt;
    )
}

const styles = StyleSheet.create({
    input: {
        height: 32,
        width: 200,
        borderWidth: 1,
        borderColor: '#a8a8a8'
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see we've added a new component - &lt;code&gt;TextInput&lt;/code&gt; - &lt;a href="https://reactnative.dev/docs/textinput" rel="noopener noreferrer"&gt;TextInput&lt;/a&gt; is a native React Native component. Also we have applied some basic style to the input.&lt;/p&gt;

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

&lt;p&gt;Right now if we added the &lt;code&gt;TextField&lt;/code&gt; to our app, it would look like this: &lt;/p&gt;

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

&lt;p&gt;You can try entering some text into it to see that it works!&lt;/p&gt;

&lt;p&gt;If we added it to a Formik form though, at this point it wouldn't work - no data would be submitted. We have to integrate our component with Formik, like so:.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TextField.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useField } from 'formik'; // +

/* ... */

const [ value, meta, helpers ] = useField(name) // +

const handleChange = (new_text: string) =&amp;gt; { // +
    helpers.setValue(new_text) // +
} // +

return (
    &amp;lt;FieldBase
        label={label}
    &amp;gt;
        &amp;lt;TextInput
            style={styles.input}
            onChangeText={handleChange} // +
            value={meta.value} // +
        /&amp;gt;
    &amp;lt;/FieldBase&amp;gt;
)

/* ... */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What we have done is made this component work with a Formik form. We did this by making the &lt;code&gt;onChangeText&lt;/code&gt; handler update the input value stored in the Formik form. Now, if we added it to a form, entered a value and hit submit, the values would be passed to the Formik's &lt;code&gt;onSubmit&lt;/code&gt; prop.&lt;/p&gt;

&lt;p&gt;Right now if we tried to use this component in our app, we would receive an error, because now the component expects to reside in a Formik form (which we are yet to build). But before we build our form, let's build the other 2 field components - &lt;code&gt;DropdownField&lt;/code&gt; and &lt;code&gt;SliderField&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  DropdownField
&lt;/h3&gt;

&lt;p&gt;Now let's create a dropdown field component. For this component we will use the library &lt;em&gt;react-native-element-dropdown&lt;/em&gt; that we installed in the Setup chapter.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;components&lt;/code&gt; folder, create a new file &lt;code&gt;DropdownField.tsx&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DropdownField.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useField } from 'formik';
import { Dropdown } from 'react-native-element-dropdown';
import { StyleSheet } from 'react-native'
import FieldBase, { FieldBaseProps} from './FieldBase';

type DropdownFieldProps = FieldBaseProps &amp;amp; {
    name: string
    data: {label: string, value: string}[]
}

export default function DropdownField({ 
    label,
    name,
    data,
}: DropdownFieldProps) {

    const [ value, meta, helpers ] = useField(name)

    const handleChange = (new_value: string) =&amp;gt; {
        helpers.setValue(new_value)
    }

    return (
        &amp;lt;FieldBase
            label={label}
        &amp;gt;
            &amp;lt;Dropdown
                style={styles.dropdown}
                data={data}
                labelField='label'
                valueField='value'
                value={meta.value}
                onChange={(data) =&amp;gt; handleChange(data.value)}
            /&amp;gt;
        &amp;lt;/FieldBase&amp;gt;
    )
}

const styles = StyleSheet.create({
    dropdown: {
        height: 32,
        width: 200,
        borderWidth: 1,
        borderColor: '#a8a8a8',
        paddingLeft: 4
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see the process is practically identical with the previous component - simply make the Formik's saved value update whenever the input's value changes. We have also applied some styles to our dropdown. We also pass a &lt;code&gt;data&lt;/code&gt; some prop down to the &lt;code&gt;Dropdown&lt;/code&gt; component, so that the component can show the options&lt;/p&gt;

&lt;p&gt;If we tried rendering this component, we'd again get an error because we haven't created a form for it yet. Bear with me, we're almost there!&lt;/p&gt;

&lt;h3&gt;
  
  
  SliderField
&lt;/h3&gt;

&lt;p&gt;Finally let's create a &lt;code&gt;SliderField&lt;/code&gt; component that will allow the user to input a value with a slider.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;components&lt;/code&gt; folder, create a file &lt;code&gt;SliderField.tsx&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SliderField.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useField } from 'formik';
import { StyleSheet } from 'react-native'
import FieldBase, { FieldBaseProps} from './FieldBase';
import { Slider } from '@miblanchard/react-native-slider'

type SliderFieldProps = FieldBaseProps &amp;amp; {
    name: string
}

export default function SliderField({ 
    label,
    name
}: SliderFieldProps) {

    const [ value, meta, helpers ] = useField(name)

    const handleChange = (new_value: number[]) =&amp;gt; {
        helpers.setValue(new_value[0])
    }

    return (
        &amp;lt;FieldBase
            label={label}
        &amp;gt;
            &amp;lt;Slider
                containerStyle={styles.slider}
                minimumValue={0}
                maximumValue={100}
                step={1}
                value={meta.value}
                onSlidingComplete={handleChange}
            /&amp;gt;
        &amp;lt;/FieldBase&amp;gt;
    )
}

const styles = StyleSheet.create({
    slider: {
        height: 24,
        width: 200,
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see once again, the process is nearly identical - just update Formik's value when the input's value changes.&lt;/p&gt;

&lt;p&gt;Okay, now that we've created our components, let's create a Formik form and hook them up!&lt;/p&gt;

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

&lt;p&gt;To build our form, we will use Formik's &lt;code&gt;&amp;lt;Formik/&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Form/&amp;gt;&lt;/code&gt; components. First of all, let's add a foundation to our App component in &lt;code&gt;App.tsx&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;App.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { View, StyleSheet } from 'react-native'

export default function App() {

    return (
        &amp;lt;View style={styles.container}&amp;gt;

        &amp;lt;/View&amp;gt;
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        padding: 16
    },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Nothing much to see yet.&lt;/p&gt;

&lt;p&gt;Now, let's define our form's initial values&lt;/p&gt;

&lt;p&gt;&lt;code&gt;App.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* ... */

type FormValues = { // +
    username: string, // +
    profession: 'developer' | 'cook' | 'writer', // +
    coolness: number // +
} // +

export default function App() {

    const initialValues: FormValues = { // +
        username: '', // +
        profession: 'developer', // +
        coolness: 50 // +
    } // +

    /* ... */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;These will be the default values that will be set on the fields when the form loads/reloads. You can specify other values or even properties.&lt;/p&gt;

&lt;p&gt;Now let's add the &lt;code&gt;Formik&lt;/code&gt; and &lt;code&gt;Form&lt;/code&gt; components to the app:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;App.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { View, StyleSheet } from 'react-native'
import { Formik, Form } from 'formik'; // +

/* ... */

export default function App() {

    /* ... */

    return (
        &amp;lt;View style={styles.container}&amp;gt; // +
            &amp;lt;Formik // +
                initialValues={initialValues} // +
                onSubmit={console.log} // +
            &amp;gt; // +
                &amp;lt;Form&amp;gt; // +

                &amp;lt;/Form&amp;gt; // +
            &amp;lt;/Formik&amp;gt; // +
        &amp;lt;/View&amp;gt;
    );
    /* ... */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We have created our Formik component, which accepts an &lt;code&gt;initialValues&lt;/code&gt; prop, which we specify as the &lt;code&gt;initialValues&lt;/code&gt; variable that we created before. Additionally &lt;code&gt;Formik&lt;/code&gt; accepts an &lt;code&gt;onSubmit&lt;/code&gt; prop, which specifies which function to run when the form is submitted. We have made it so that the submitted values simply get logged into the console on submit.&lt;/p&gt;

&lt;p&gt;Now let's add our fields and some styles to the form.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;App.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { View, StyleSheet, Pressable, Text } from 'react-native' // +
import { Formik, Form } from 'formik'; // +
import TextField from './components/TextField'; // +
import DropdownField from './components/DropdownField'; // +
import SliderField from './components/SliderField'; // +

/* ... */

export default function App() {

/* ... */

return (
    &amp;lt;View style={styles.container}&amp;gt;
        &amp;lt;Formik
            initialValues={initialValues}
            onSubmit={console.log}
        &amp;gt;
            {formik =&amp;gt; ( // +
                &amp;lt;Form
                    style={styles.form}
                &amp;gt;
                    &amp;lt;TextField // +
                        label='username' // +
                        name='username' // +
                    /&amp;gt; // +
                    &amp;lt;DropdownField // +
                        label='profession' // +
                        name='profession' // +
                        data={[ // +
                            { // +
                                label: 'Developer', // +
                                value: 'developer' // +
                            }, // +
                            { // +
                                label: 'Cook', // +
                                value: 'cook' // +
                            }, // +
                            { // +
                                label: 'Writer', // +
                                value: 'writer' // +
                            } // +
                        ]} // +
                    /&amp;gt; // +
                    &amp;lt;SliderField // +
                        label='How cool are you?' // +
                        name='coolness' // +
                    /&amp;gt; // +
                    &amp;lt;Pressable // +
                        style={styles.submit_button} // +
                        onPress={() =&amp;gt; formik.submitForm()} // +
                    &amp;gt; // +
                        &amp;lt;Text&amp;gt;Submit&amp;lt;/Text&amp;gt; // +
                    &amp;lt;/Pressable&amp;gt; // +
                &amp;lt;/Form&amp;gt;
            )} // +
        &amp;lt;/Formik&amp;gt;
    &amp;lt;/View&amp;gt;
);

/* ... */

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        padding: 16
    },
    form: { // +
        display: 'flex', // +
        flexDirection: 'column', // +
        gap: 16 // +
    }, // +
    submit_button: { // +
        borderWidth: 1, // +
        borderColor: 'blue', // +
        width: 104, // +
        alignItems: 'center', // +
        padding: 8 // +
    }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Alright, we've added our fields to our form. We've also added a submit button and some style for the form and the button. If you try to submit the form, you will see the values logged to the console. Hooray! We've made our form!&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jt7hkl7qy6as64d5saf.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jt7hkl7qy6as64d5saf.PNG" alt="Console log when submitting form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding validation
&lt;/h2&gt;

&lt;p&gt;The next step is to add validation to our fields. We will do this by using the library Yup, which Formik supports out of the box.&lt;/p&gt;

&lt;p&gt;First of all let's add an &lt;code&gt;ErrorMessage&lt;/code&gt; component to our &lt;code&gt;FieldBase&lt;/code&gt;. We will also need to add a &lt;code&gt;name&lt;/code&gt; prop to our &lt;code&gt;FieldBase&lt;/code&gt; that we can pass to the &lt;code&gt;ErrorMessage&lt;/code&gt; so it know which field's error to display.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FieldBase.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ErrorMessage } from 'formik' // +
import { PropsWithChildren } from 'react'
import { StyleSheet, View, Text } from 'react-native'

export type FieldBaseProps = {
    label: string
    name: string // +
}

export default function FieldBase( { 
    label,
    name, // +
    children
}: PropsWithChildren&amp;lt;FieldBaseProps&amp;gt;) {
    return (
        &amp;lt;View
            style={ styles.container }
        &amp;gt;
            &amp;lt;Text
                style={ styles.label }
            &amp;gt;
                { label }
            &amp;lt;/Text&amp;gt;
            { children }
            &amp;lt;ErrorMessage            // +
                component={Text}     // +
                style={styles.error} // +
                name={name}          // +
            /&amp;gt;                       // +
        &amp;lt;/View&amp;gt;
    )
}

const styles = StyleSheet.create({
    /* ... */
    error: {          // +
        fontSize: 14, // +
        color: 'red'  // +
    }                 // +
})

/* ... */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next we have to update our &lt;code&gt;TextField&lt;/code&gt;, &lt;code&gt;SliderField&lt;/code&gt; and &lt;code&gt;DropdownField&lt;/code&gt; to pass the &lt;code&gt;name&lt;/code&gt; prop down to &lt;code&gt;FieldBase&lt;/code&gt;, like so:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TextField.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function TextField({ 
    label,
    name
}: TextFieldProps) {

/* ... */

return (
    &amp;lt;FieldBase
        label={label}
        name={name} // +
    &amp;gt;
/* ... */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The code above shows how to do it for &lt;code&gt;TextField&lt;/code&gt;, but it's the same process for &lt;code&gt;SliderField&lt;/code&gt; and &lt;code&gt;DropdownField&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now we have to add the validation schema to our Formik form:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;App.tsx&lt;/code&gt; :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { View, StyleSheet, Pressable, Text } from 'react-native'
import { Formik, Form } from 'formik';
import * as Yup from 'yup';
import TextField from './components/TextField';
import DropdownField from './components/DropdownField';
import SliderField from './components/SliderField';

/* ... */

const validationSchema = Yup.object().shape({  // +
    username: Yup.string()                     // +
        .min(2, 'Too short!')                  // +
        .max(16, 'Too long!')                  // +
        .required('Required'),                 // +
    profession: Yup.string()                   // +
        .oneOf(['developer', 'cook', 'writer'], 'Invalid value')                                // +
        .required('Required!'),                // +
    coolness: Yup.number()                     // +
        .min(0, 'Value must be between 0 and 100')// +
        .max(100, 'Value must be between 0 and 100')// +
        .required('Required!')// +
})

return (
    &amp;lt;View style={styles.container}&amp;gt;
    &amp;lt;Formik
        initialValues={initialValues}
        onSubmit={console.log}
        validationSchema={validationSchema} // +
    &amp;gt;
/* ... */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Yay! We added validation to our form! Now try submitting the form with an empty username and you should see an error message pop up.&lt;/p&gt;

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

&lt;p&gt;And we're done with our Formik form!&lt;/p&gt;

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

&lt;p&gt;I hope you learned something new from this post. If you have any questions or comments feel free to leave them here, on the project repo or by emailing me at &lt;a href="//mailto:jorensmerenjanu@gmail.com"&gt;jorensmerenjanu@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>typescript</category>
      <category>formik</category>
      <category>forms</category>
    </item>
    <item>
      <title>Debounce function with a return value using promises</title>
      <dc:creator>JorensM</dc:creator>
      <pubDate>Mon, 29 May 2023 09:29:27 +0000</pubDate>
      <link>https://dev.to/jorensm/debounce-function-with-a-return-value-using-promises-1i14</link>
      <guid>https://dev.to/jorensm/debounce-function-with-a-return-value-using-promises-1i14</guid>
      <description>&lt;p&gt;In this article I will show you how to write a debounce function that allows you to return a value using promises.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a debounce function?
&lt;/h2&gt;

&lt;p&gt;First of all, what is a debounce function? Simply put, a &lt;strong&gt;debounce function&lt;/strong&gt; is a function that prevents another function from being called too often. When you call a debounce function, it sets a timer and runs the target function only after the timer has run out. But if the debounce function gets called again before the timer has run out, the timer gets reset, preventing the target function from being called repeatedly. So you can call the debounce function multiple times in a row, and the target function will only get called once, after the timer runs out.&lt;/p&gt;

&lt;p&gt;This is especially useful in search inputs, where you only want to make a search request after the user has finished typing. Let me explain.&lt;/p&gt;

&lt;p&gt;Without a debounce function, when you type into a search bar, for each character that you type, a HTTP request would be made. For example if you wanted to search for the term 'book', you would type 'b', and then a request would be made. Then you would type 'o', and another request would be made, and so on. Lots of pointless requests!&lt;/p&gt;

&lt;p&gt;Whereas a debounce function allows you to postpone the search request until you have finished typing the search query. You would type 'b', then the debounce function would be called, starting the timer for the target function. Then you would type 'o', and the debounce function would get called once again, resetting the timer and preventing the target function from being called too early. Then you would type 'o', and the timer would get reset again. After typing the last character, the debounce function would get called one last time, and after the timer runs out, it would call your target function - in this case the search request. So only one HTTP request gets made, after the user has finished typing!&lt;/p&gt;

&lt;p&gt;Below are two examples, without a debounce function and with a debounce function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without debounce function:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;With debounce function:&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;As you can see, example #1 updates the results each time you type a letter, whereas in example #2, the results only get updated after the user has finished typing.&lt;/p&gt;

&lt;p&gt;In these examples, mock data is used and the results show up instantly. But if you used real data that gets fetched from a server, example #1 would be making lots of HTTP requests for every typed letter, clogging the queue. Whereas example #2 would only make one HTTP request, after typing in the full query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get coding!
&lt;/h2&gt;

&lt;p&gt;Alright, now that we know what a debounce function is, let's see how it works.&lt;/p&gt;

&lt;p&gt;The debounce function is actually surprisingly simple, and can be written in as little as 9 lines of code(and even less if you try):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function debounce( callback, delay = 300 ){
    let timer;
    return ( ...args ) =&amp;gt; {
      clearTimeout( timer );
      timer = setTimeout( () =&amp;gt; {
        callback( ...args );
      }, delay );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function takes a callback and delay duration as arguments. First it stores an empty reference to the timer, to be used later. Next it returns a function (that we will then store in a variable and call). This function clears the timer, and then sets a timeout with our specified delay, after which our callback will be run. If the returned functions gets called again before the timer has run out, the timer gets reset and the callback function gets prevented from being called until the timer runs out.&lt;/p&gt;

&lt;p&gt;This is how to use the debounce function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function search( term ){
  console.log( `searched for ${term}` );
}

const debouncedSearch = debounce( search, 600 );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can call debouncedSearch() and it will debounce the search function. Let's see it in action.&lt;/p&gt;

&lt;p&gt;First, let's try calling the search function several times in a row(without using debounce) and see what happens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;search( 'book' );
search( 'book' );
search( 'book' );
search( 'book' );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;As you can see, the search function got called 4 times.&lt;/p&gt;

&lt;p&gt;Now let's see what happens if we do the same, but with debounce.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;debouncedSearch( 'book' );
debouncedSearch( 'book' );
debouncedSearch( 'book' );
debouncedSearch( 'book' );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;As you can see, the search function only got called once, even though we called the debounce function 4 times! This is because we called the debounce function quickly one after another, so the callback got debounced (meaning that the timer got reset and the callback was prevented from being called)&lt;/p&gt;

&lt;p&gt;If we waited until the timer ran out and then called the debounce function again, it would call the search function again(because the timer would run out)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setTimeout( () =&amp;gt; {
  debouncedSearch( 'book' );
  setTimeout( () =&amp;gt; {
    debouncedSearch( 'book' );
    debouncedSearch( 'book' );
      setTimeout( () =&amp;gt; {
      debouncedSearch( 'book' );
      debouncedSearch( 'book' );
      debouncedSearch( 'book' );
    }, 1000 );
  }, 1000 );
}, 1000 );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Let's try implementing the search input from the earlier example using the debounce function&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input type='text' id='search-input' placeholder='Search'/&amp;gt;

&amp;lt;h4&amp;gt;Results:&amp;lt;/h4&amp;gt;
&amp;lt;ul id='results'&amp;gt;

&amp;lt;ul/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Mock data
const data = [
  'Book',
  'Book Number One',
  'Book Number Two',
  'Book Number Three',
  'Book Numero Uno',
  'Book Numero Dos',
  'Book Numero Tres',
  'Book #1',
  'Book #2',
  'Book #3'
]

//Results list element
const results_list_element = document.getElementById( 'results' );
const search_input_element = document.getElementById( 'search-input' );

//This function renders specified results into the results element
function renderResults( results ){
  //Clear the results element
  results_list_element.innerHTML = '';
    //Loop through results
  for ( const result of results ) {
      //For each result, append an element to the results list
    results_list_element.insertAdjacentHTML( 'beforeend', `&amp;lt;li&amp;gt;${result}&amp;lt;/li&amp;gt;`);
  }
}

//This function filters data by term and then renders the results
function search( term ){
  const results = data.filter( item =&amp;gt; item.toLowerCase().includes( term.toLowerCase() ) );

  renderResults( results );

}

//Debounce function
function debounce( callback, delay ) {
  let timer;

  return( ...args ) =&amp;gt; {
       clearTimeout(timer);
       timer = setTimeout( () =&amp;gt; {
         callback( ...args );
       }, delay );
  }
}

//Debounced search()
debouncedSearch = debounce(search, 600);

//Show all results before user has typed anything
renderResults( data );

//Add event listener for searching
search_input_element.addEventListener( 'input', e =&amp;gt; {
  //Call debounced search with the input value
  debouncedSearch(e.target.value);
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is a working CodePen:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jorensm/embed/XWxQByV?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;So far we have learned how to implement and use the traditional debounce function.&lt;/p&gt;

&lt;p&gt;A caveat of the traditional deboucne function that we used here is that it does not allow the callback to return a value. This forces us to have to write all the logic right in the callback function, since we can't extract any data from it. But luckily there is a solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Promises to the resuce!
&lt;/h2&gt;

&lt;p&gt;With the help of promises, we can rewrite our deboucne function in such a way that it returns a promise that we can then &lt;code&gt;then()&lt;/code&gt; to access the callback's return value.&lt;/p&gt;

&lt;p&gt;This is what the updated debounce function looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Debounce function with return value using promises
function debounce( callback, delay ) {
  let timer;

  return( ...args ) =&amp;gt; {
    return new Promise( ( resolve, reject ) =&amp;gt; {
      clearTimeout(timer);
      timer = setTimeout( () =&amp;gt; {
          try {
            let output = callback(...args);
            resolve( output );
          } catch ( err ) {
            reject( err );
          }
      }, delay );
    })

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

&lt;/div&gt;



&lt;p&gt;The difference from the previous implementation is that we're returning a promise instead of a regular function, and then resolve/reject it with our callback's return value once the timer runs out. This allows us to add a return value to our callback, which we can then access with the 'then()' function, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//Filters data by term and then returns the results
function search( term ){

  const results = data.filter( item =&amp;gt; item.toLowerCase().includes( term.toLowerCase() ) );

  return results;

}

search_input_element.addEventListener( 'input', e =&amp;gt; {
  //Call debounced search with the input value
  debouncedSearch(e.target.value)
  .then( results =&amp;gt; renderResults( results ) )
  .catch(err =&amp;gt; console.log(err.message));
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is a CodePen with the updated debounce function. Visually speaking it behaves exactly the same way as the previous CodePen, but internally we are extracting the search results from the callback, instead of running all code right in the callback.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jorensm/embed/MWPdgPW?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

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

&lt;p&gt;In this article you learned about the debounce function, its use case and how to implement one.&lt;/p&gt;

&lt;p&gt;If you have any questions, comments or suggestions, feel free to leave them in the comments!&lt;/p&gt;

&lt;p&gt;Thank you very much for reading and I hope you learned something new!&lt;/p&gt;

</description>
      <category>debounce</category>
      <category>javascript</category>
      <category>intermediate</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
