<?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: Meg Meyers</title>
    <description>The latest articles on DEV Community by Meg Meyers (@finalgirl321).</description>
    <link>https://dev.to/finalgirl321</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%2F1236542%2F78473456-755c-4f3b-a862-97fa0604fa8c.png</url>
      <title>DEV Community: Meg Meyers</title>
      <link>https://dev.to/finalgirl321</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/finalgirl321"/>
    <language>en</language>
    <item>
      <title>Making Zustand Persist Play Nice with Async Storage &amp; React Suspense, Part 2/2</title>
      <dc:creator>Meg Meyers</dc:creator>
      <pubDate>Tue, 22 Apr 2025 06:28:02 +0000</pubDate>
      <link>https://dev.to/finalgirl321/making-zustand-persist-play-nice-with-async-storage-react-suspense-part-22-26ik</link>
      <guid>https://dev.to/finalgirl321/making-zustand-persist-play-nice-with-async-storage-react-suspense-part-22-26ik</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp556cwp31h4l5jy614e6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp556cwp31h4l5jy614e6.png" alt="Zustand bear playing guitar singing 'Zustand' with React official logo present" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Welcome to part 2 of my two part tutorial on Zustand persist with async storage and React Suspense. If you haven't seen part 1, you'll need the code from there, so check it out &lt;a href="https://dev.to/finalgirl321/making-zustand-persist-play-nice-with-async-storage-react-suspense-part-12-58l1"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We left off having completed our store with the partial persist to IndexedDB and a merge function to ensure persisted data smoothly integrates with new store creation. Part 2 is about integrating Suspense and making our code even more robust by using onRehydratedStorage from Zustand to return data to the app only when the hydration has occurred. &lt;/p&gt;

&lt;p&gt;First of all, if you aren't using &lt;a href="https://react.dev/reference/react/Suspense" rel="noopener noreferrer"&gt;React Suspense&lt;/a&gt; I highly recommend it. It's actually incredibly easy to use, and by passing your fallback (I prefer skeletons unique to each component) React will automatically show it while the children passed to Suspense are loading. It also encourages use of &lt;a href="https://react.dev/reference/react/lazy" rel="noopener noreferrer"&gt;lazy&lt;/a&gt; imports, since that is one of several ways you can trigger Suspense. Sometimes you might have a component that relies solely on data from the store and isn't using lazy, or lazy is working for the page but you need additional loading indicators for the data. So how would you trigger Suspense in that component? I'm going to show you by further customizing the code from part 1. This is going to be a mimic of the same effect you can get when you use, for example, useSuspenseQuery from Tanstack React Query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useSyncExternalStore} from 'react'; // NEW

export const useStore = create&amp;lt;BingeBoxStore&amp;gt;()(
  persist(
    (set, get) =&amp;gt; ({
      // state
      bookmarks: {},
      previousSearches: [],
      continueWatching: [],

      // suspense-related state THIS IS NEW
      isLoaded: false,
      isLoading: false,
      loadError: null,
      listeners: new Set&amp;lt;() =&amp;gt; void&amp;gt;(),

      // NEW method to subscribe to store changes (for useSyncExternalStore)
      subscribe: (listener) =&amp;gt; {
        const { listeners } = get();
        listeners.add(listener);
        return () =&amp;gt; listeners.delete(listener);
      },

      // NEW method to initialize the store and handle Suspense
      initializeStore: async () =&amp;gt; {
        // Return immediately if already loaded
        if (get().isLoaded) {
          return {
            bookmarks: get().bookmarks,
            previousSearches: get().previousSearches,
            continueWatching: get().continueWatching,
          };
        }

        // If already loading, wait for completion
        if (get().isLoading) {
          return new Promise((resolve, reject) =&amp;gt; {
            const unsubscribe = get().subscribe(() =&amp;gt; {
              if (get().isLoaded) {
                unsubscribe();
                resolve({
                  bookmarks: get().bookmarks,
                  previousSearches: get().previousSearches,
                  continueWatching: get().continueWatching,
                });
              } else if (get().loadError) {
                unsubscribe();
                reject(get().loadError);
              }
            });
          });
        }

        // Start loading process
        set({ isLoading: true });
        get().listeners.forEach((listener) =&amp;gt; listener());

        // Return a promise that resolves when loaded
        return new Promise((resolve, reject) =&amp;gt; {
          const unsubscribe = get().subscribe(() =&amp;gt; {
            if (get().isLoaded) {
              unsubscribe();
              resolve({
                bookmarks: get().bookmarks,
                previousSearches: get().previousSearches,
                continueWatching: get().continueWatching,
              });
            } else if (get().loadError) {
              unsubscribe();
              reject(get().loadError);
            }
          });
        });
      },

           // other state updating methods go here ...
    }),
{
      name:
        process.env.NODE_ENV === 'production'
          ? 'idb-storage'
          : 'idb-storage-dev',
      storage: idbStorage,
      version: 0,
      merge: (persistedState, currentState) =&amp;gt; {
                       ....
      },
//NEW - how we know when hydration is done
      onRehydrateStorage: () =&amp;gt; (_state, error) =&amp;gt; {
        if (error) {
          console.log('An error occurred during hydration', error);
          useStore.setState({
            isLoaded: true,
            isLoading: false,
            loadError: error as Error,
          });
        } else {
          useStore.setState({
            isLoaded: true,
            isLoading: false,
          });
        }

        useStore.getState().listeners.forEach((listener) =&amp;gt; listener());
      },

// other ....

 }
  )
);

// hook to trigger the initializeStore and wait for data while triggering Suspense:
export function useSuspenseStore&amp;lt;T&amp;gt;(selector: (_state: BingeBoxStore) =&amp;gt; T): T {
  const store = useStore();

  // if not loaded and not loading, start the loading process
  if (!store.isLoaded &amp;amp;&amp;amp; !store.isLoading) {
    // This will be caught by React Suspense
    throw store.initializeStore();
  }

  // If currently loading, throw a promise to trigger suspense
  if (store.isLoading) {
    throw new Promise((resolve) =&amp;gt; {
      const unsubscribe = store.subscribe(() =&amp;gt; {
        if (store.isLoaded || store.loadError) {
          unsubscribe();
          resolve(null);
        }
      });
    });
  }

  // If there was an error, throw it
  if (store.loadError) {
    throw store.loadError;
  }

  // Store is loaded, use it
  return useSyncExternalStore(store.subscribe, () =&amp;gt; selector(store));
}

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://react.dev/reference/react/useSyncExternalStore" rel="noopener noreferrer"&gt;uSES&lt;/a&gt; is the key here, allowing us to integrate Zustand into React so that React actually knows about it. Bring that in first, then you should recognize state from part 1, but additional state is now keeping track of loading status, error, and listeners. Two new methods include subscribe code for uSES and initializeStore which will return a Promise that can be thrown by the useSuspenseStore hook to trigger Suspense. onRehydrateStorage is from the Zustand library and lets us properly set loading state based on hydration being complete. If hydration is complete, isLoading is false and isLoaded is true. &lt;/p&gt;

&lt;p&gt;Anytime you don't need to trigger Suspense and you just want to use your store selector, just use the useStore hook that was created with this line of code:&lt;br&gt;
&lt;code&gt;export const useStore = create&amp;lt;BingeBoxStore&amp;gt;()(&lt;/code&gt;&lt;br&gt;
That will likely be the usual way you access your store. This code is for when your 'get' functionality to your store needs to integrate with Suspense to show fallbacks. &lt;/p&gt;

&lt;p&gt;Bonus: From the Zustand docs, one of the costs of using async storage is that async hydration might not be ready when React renders. You can use the isLoaded flag if you need to hold back rendering until some data is hydrated. You can also just use onRehydrateStorage by itself to know when the store is hydrated. The only reason for the initiateStorage and uSES was to integrate Suspense. &lt;/p&gt;

&lt;p&gt;If you want to know about uSES and other ways it can be useful check out this &lt;a href="https://blog.logrocket.com/exploring-usesyncexternalstore-react-hook/" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading and I hope this helped in your understanding of some more advanced concepts of Zustand, Suspense, and async storage. If you have any comments or questions please let me know. If you notice that I am overlooking something I would love to hear from you! This is my own custom solution to this problem so any critique would be welcome. I have purposefully omitted showing the React code for usage of these hooks but that is available in React docs or Zustand docs if you are unsure how to use hooks. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Making Zustand Persist Play Nice with Async Storage &amp; React Suspense, Part 1/2</title>
      <dc:creator>Meg Meyers</dc:creator>
      <pubDate>Tue, 22 Apr 2025 01:33:21 +0000</pubDate>
      <link>https://dev.to/finalgirl321/making-zustand-persist-play-nice-with-async-storage-react-suspense-part-12-58l1</link>
      <guid>https://dev.to/finalgirl321/making-zustand-persist-play-nice-with-async-storage-react-suspense-part-12-58l1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwb3lam55osus5cv8w0ew.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwb3lam55osus5cv8w0ew.jpg" alt="Zustand bear playing guitar and singing 'Zustand'" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find yourself needing to use async storage for your app, and you are using Zustand for your global store, you may already know that Zustand docs warn of an extra 'cost' for that storage type. What you might not know, but will soon, is what that cost is or how to properly set up your store code to deal with it. At the same time, I am going to show you how to make your store trigger React Suspense with useSyncExternalStore and make absolutely certain that your store &lt;strong&gt;&lt;em&gt;never gets accidentally overwritten&lt;/em&gt;&lt;/strong&gt; by the persist middleware, often at redeploy, but potentially on refresh or opening a new tab. Please note that not all browsers behave the same when it comes to this overwrite issue so make sure you are testing your app appropriately.&lt;/p&gt;

&lt;p&gt;This article is not a dive into how to use Zustand. If you need that, I have a blog &lt;a href="https://dev.to/finalgirl321/getting-started-with-zustand-state-management-for-react-5786"&gt;here&lt;/a&gt; that explains the store setup with partial persist to local storage. This article assumes knowledge of basic Zustand, React, Suspense, and what it means to access something asynchronously. &lt;/p&gt;

&lt;p&gt;The application letting us borrow it's code today is an app that allows users to discover, save and watch movies and tv shows. The store persists three things: an object that holds bookmarked item information, an array that holds the user's last 20 searches, and an array that holds basic information about what the user is currently watching. The async storage is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API" rel="noopener noreferrer"&gt;IndexedDB&lt;/a&gt; which means we will need a custom store, so I am using the package &lt;a href="https://www.npmjs.com/package/idb-keyval" rel="noopener noreferrer"&gt;idb-keyval&lt;/a&gt; to easily set up the storage with Zustand. If you don't want to add that package and just want basic code for a custom store, see this &lt;a href="https://gist.github.com/JacobWeisenburger/eabdb5cadc947e9d35f45d6b7cf2710e" rel="noopener noreferrer"&gt;gist&lt;/a&gt;, and note that you will have to finish the code that is started for you there. This code is written in TypeScript but it is quite easy to disregard that if it doesn't fit your needs. &lt;/p&gt;

&lt;p&gt;The store code looks like this to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface BingeBoxStore {
  bookmarks: { [key: string]: { id: string; type: string; dateAdded: number } };
  previousSearches: string[];
  continueWatching: {
    id: number;
    media_type: string;
    lastUpdated: number;
    title: string;
    season?: number;
    episode?: number;
    poster_path: string;
    release_date?: string;
    runtime?: string;
  }[];
 // function definitions for updating state would go here...
}

export const useStore = create&amp;lt;BingeBoxStore&amp;gt;()(
  persist(
    (set, get) =&amp;gt; ({
      // state
      bookmarks: {},
      previousSearches: [],
      continueWatching: [],

      // functions for updating state would go here...

    }),
    {
      name:
        process.env.NODE_ENV === 'production'
          ? 'idb-storage'
          : 'idb-storage-dev',
      storage: idbStorage,
      version: 0,

      partialize: (state) =&amp;gt; ({
        bookmarks: state.bookmarks,
        previousSearches: state.previousSearches,
        continueWatching: state.continueWatching,
      }),
    }
  )
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just basic store code with the store interface, state, methods to update state omitted for brevity, and the storage option pointing to &lt;strong&gt;idbStorage&lt;/strong&gt; that is currently missing. That's the next step - to set up that storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { get, set, del } from 'idb-keyval';

export const idbStorage = {
  getItem: async (name: string) =&amp;gt; {
    const value = await get(name);
    return value ?? null;
  },
  setItem: async (name: string, value: any) =&amp;gt; {
    await set(name, value);
  },
  removeItem: async (name: string) =&amp;gt; {
    await del(name);
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see this is very simple code that gets, sets, and removes items from the storage in an asynchronous manner.&lt;/p&gt;

&lt;p&gt;At this point the code could technically work. If you only wanted to learn how to set up Zustand with IndexedDB you pretty much have it. However, if you stop here you might want to test very carefully that your saved store data is pulled back into Zustand properly after redeploy, refresh, and tab changes and be sure to test across all browsers. Because what could happen here is that Zustand's persist middleware is going to rehydrate data from the store back into memory under all of those conditions, immediately (unless you take over this process), but it will also want to set up a store as part of it's innate code - nothing is going to tell it to wait to &lt;strong&gt;&lt;em&gt;asynchronously&lt;/em&gt;&lt;/strong&gt; access IDB, so it might accidently set up a new, empty store and persist that back to the IDB even as you are pulling in the old data. This creates a race condition where the middleware might win and persist back to IndexedDB a fresh, empty store, or it might not. You can read more &lt;a href="https://zustand.docs.pmnd.rs/integrations/persisting-store-data#hydration-and-asynchronous-storages" rel="noopener noreferrer"&gt;here&lt;/a&gt; about hydration (a fancy term for grabbing persisted data from storage and getting into the application memory for use by the app) and other things that can go wrong from using async storage.&lt;/p&gt;

&lt;p&gt;For me, I don't like risking race conditions, so my recommendation is to add the fix for this, even if you don't notice it to be a problem yet, because the size of IndexedDB or any other hiccups that could slow down async access might one day pop up for your user, and users do not like it when their persisted data is lost. Even just having to log in again when they don't think they should have to is enough to irritate users, but in my app, all of the bookmarks, continue watching, etc. being lost would be very annoying. So here is some code that will make Zustand merge the new store with the old store. This little fix is all it takes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   merge: (persistedState, currentState) =&amp;gt; {
        const persisted = persistedState as Partial&amp;lt;BingeBoxStore&amp;gt; || {};
        return {
          ...currentState,
          bookmarks:
            persisted.bookmarks &amp;amp;&amp;amp; Object.keys(persisted.bookmarks).length &amp;gt; 0
              ? persisted.bookmarks
              : currentState.bookmarks,
          previousSearches:
            persisted.previousSearches &amp;amp;&amp;amp; persisted.previousSearches.length &amp;gt; 0
              ? persisted.previousSearches
              : currentState.previousSearches,
          continueWatching:
            persisted.continueWatching &amp;amp;&amp;amp; persisted.continueWatching.length &amp;gt; 0
              ? persisted.continueWatching
              : currentState.continueWatching,
        };
      },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously your code is going to be different, but the built in &lt;a href="https://zustand.docs.pmnd.rs/integrations/persisting-store-data#merge" rel="noopener noreferrer"&gt;merge&lt;/a&gt; method is what I want to show you (please read about merge to know if you need deepMerge, as this app doesn't use it). This goes in the second set of curly braces, under the name or version, above the partialize... anywhere in that area. Don't try to put it with your methods to change state!&lt;/p&gt;

&lt;p&gt;At this point, you may or may not see a need for merge, but I suggest even more strongly you do it if you want to go on with Suspense integration (part 2). I am going to add complexity with &lt;a href="https://react.dev/reference/react/useSyncExternalStore" rel="noopener noreferrer"&gt;useSyncExternalStore&lt;/a&gt; (uSES) a built in hook from the React library designed to make it easy to integrate your store with React, explain why that integration is important, and I will show you how to make different hooks to access your store based on wanting to throw a promise to Suspense or just have the store load without Suspense (and why you might need both patterns). There will be additions to state such as isLoading, loading, and a custom intializeStore function that will actually wait to return store data until Zustand notifies that hydration is complete. Having the merge function just guarantees the user's data stays intact with so much going on! &lt;/p&gt;

&lt;p&gt;Thanks for reading part I, part II is on the way! If you are ready to increase complexity and make your app even more awesome with Suspense, check it out &lt;a href="https://dev.to/finalgirl321/making-zustand-persist-play-nice-with-async-storage-react-suspense-part-22-26ik"&gt;here!&lt;/a&gt; &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Getting started with Zustand state management for React</title>
      <dc:creator>Meg Meyers</dc:creator>
      <pubDate>Sun, 06 Apr 2025 04:04:48 +0000</pubDate>
      <link>https://dev.to/finalgirl321/getting-started-with-zustand-state-management-for-react-5786</link>
      <guid>https://dev.to/finalgirl321/getting-started-with-zustand-state-management-for-react-5786</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F335825ldf0hypyplp63m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F335825ldf0hypyplp63m.jpg" alt="Zustand bear playing guitar and singing 'Zustand'" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://zustand.docs.pmnd.rs/getting-started/introduction" rel="noopener noreferrer"&gt;Zustand&lt;/a&gt; is a simple, fast "bearbones" (yeah bear, not bare!) state management library for React. It can (and should) be used in place of more complicated libraries like Redux or MobX. Built on top of the Context API, Zustand provides a simple, intuitive and scalable API for managing global state in your React components.&lt;/p&gt;

&lt;p&gt;Today I'm going to show you how to get started with Zustand using code from my latest project, which is an application that, in part, allows you to bookmark tv shows and movies you are interesting in watching. The bookmark icon appears at the top right corner of every item card. An item card has a movie/series poster and some basic information. The same item might appear in many places, including the search page, the trending component, the user's watchlist, the slide show, etc. When an item is bookmarked, the bookmark SVG shows a checkmark otherwise it is an empty bookmark. So the state of the item, bookmarked or not, must be shared across many components and clicking the button must result in instantaneous changes of the SVG across all components. In other words, global state management is now a necessity. &lt;/p&gt;

&lt;p&gt;As with any library, we start by installing it. &lt;br&gt;
&lt;code&gt;npm i zustand&lt;/code&gt;&lt;br&gt;
(I use npm but you can use your preferred package manager, of course)&lt;/p&gt;

&lt;p&gt;Our next step is to set up a store. For any of you who have worked with other state managers, you may be familiar with the concept of slices. Consider your global state a pie, and there are different slices that revolve around different state requirements. You may have a slice for user information, a slice for authentication, a slice for bookmarking functionality, etc. Since today is a basic introduction, I don't want to complicate this with slicing. It's not required anyway, just good practice. &lt;/p&gt;

&lt;p&gt;I have a file called store.ts that I will use for creating my store. Yes, I am using TypeScript (TS), cue the groans, and then let's get on with it! &lt;/p&gt;

&lt;p&gt;You may know that global state is erased from memory if the user reloads the page, so I am going to show you persistence right at the same time as I show you the state management because it is SO EASY and honestly, you should have it anyway. Do you really want your user logged out just because they hit reload? They don't want that either. I'm going to go with local storage for this, but Zustand supports other types, including async storage. I am also going to push things just a bit more by showing you partial storage, because I really think you should know this right up front as well. You might have a large store for your app, but probably you won't want it ALL to persist, especially knowing local storage is quite limited at 5-10 MB depending on the browser. You can set up for partial persistence even if you DO end up saving everything and it won't cause any problems. &lt;/p&gt;

&lt;p&gt;At the top of our store.ts the imports will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create is going to help us create the store, persist will help us persist the data, and createJSONStorage is how you use local storage. I will make the bookmarks an array  and I will need functions to add a bookmark and remove a bookmark (this code is more performant if you use an object instead of an array, but for purposes of this lesson, we will do it this way).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface BookmarkStore {
  bookmarks: { id: string; media_type: string }[];
  addBookmark: (_id: string, _type: string) =&amp;gt; void;
  removeBookmark: (_id: string, _type: string) =&amp;gt; void;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is our TS declaration as to what bookmarks and add/remove functions look like. A bookmark holds the item's id and media type, movie or tv, and it is an array. You can skip this if you aren't using TS. To set up the store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const useBookmarkStore = create&amp;lt;BookmarkStore&amp;gt;()(
  persist(
    (set) =&amp;gt; ({
       bookmarks: [],
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual bookmarks array is declared here as an empty array. Set will update state with new data. There is also "get" that you can add right after set, &lt;code&gt;(set,get)&lt;/code&gt; if you need to look at something in state before you set, or you need to write a getter function. Now for the add and remove functions, added directly under the bookmarks empty array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      addBookmark: (id, media_type) =&amp;gt;
        set((state) =&amp;gt; ({
          bookmarks: [
            ...state.bookmarks,
            { id, media_type, dateAdded: dayjs().unix() },
          ],
        })),

      removeBookmark: (id, media_type) =&amp;gt;
        set((state) =&amp;gt; ({
          bookmarks: state.bookmarks.filter(
            (b) =&amp;gt; !(b.id === id &amp;amp;&amp;amp; b.media_type === media_type),
          ),
        })),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In keeping with our functional style of programming, we create a new array with the added or removed bookmark each time we add or remove one. Note I have added a third key/value pair to the bookmarks array, dateAdded, but you can disregard that. If you do want this same functionality don't forget to install and import &lt;a href="https://day.js.org/" rel="noopener noreferrer"&gt;Day.js&lt;/a&gt;. Next I will add the partial persistence code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  }),
{
      name: 'bookmarks-storage',
      storage: createJSONStorage(() =&amp;gt; localStorage),
      partialize: (state) =&amp;gt; ({
        bookmarks: state.bookmarks,

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

&lt;/div&gt;



&lt;p&gt;This code sets the name of your choosing for the storage. It tells Zustand to use local storage to persist data, and it says which of the above data to store. Now if we add any more state to our store, we can decide to include it in the partialize function or not - we have control. As I said, I just prefer setting things up this way from the outset rather than refactoring later when I have state I don't want persisted.&lt;/p&gt;

&lt;p&gt;That's it for the store! Really simple, yet with some really nice additions, persist and partialize. &lt;/p&gt;

&lt;p&gt;Let's just take a quick look at how to use this, and you'll be all set to give it a try for yourself. &lt;/p&gt;

&lt;p&gt;In a component that requires knowledge of the bookmarks (I'll use the watchlist component), we can gain access to the bookmarks array with a selector and thus gain reactivity to the state changes so the bookmark's SVG is always correctly toggled to the correct view (checkmark if bookmarked else empty).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useBookmarkStore } from '../state/store';

const Watchlist = () =&amp;gt; {
  const bookmarks = useBookmarkStore((state) =&amp;gt; state.bookmarks);

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

&lt;/div&gt;



&lt;p&gt;In my item card, I simply send it a prop isBookmarked={true}.&lt;/p&gt;

&lt;p&gt;It can be a bit more complicated if you are in a component that is not entirely comprised of bookmarked items. You will still import the store and create the selector for the store's state, but to figure out if any one particular item is bookmarked will require some extra work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         allItems.map((item: IItem) =&amp;gt; (
              &amp;lt;ItemCard
                itemType={itemType}
                key={`${itemType}-${item.id}`}
                item={item}
                isBookmarked={bookmarks.some(
                  (a) =&amp;gt; a.id === item.id &amp;amp;&amp;amp; a.type === itemType,
                )}
              /&amp;gt;
           )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now some of you will immediately see why an object is a better choice for the bookmarks state than an array! An object can be accessed in O(1) time by simply using it's key. Here we have to iterate through the array in O(n) time to see if some id is matched in the array. This would be a bottleneck in our performance if the bookmarks array got large. &lt;/p&gt;

&lt;p&gt;A final bit of code to illustrate the add and remove functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; const { addBookmark, removeBookmark } = useBookmarkStore();

 const handleBookmarkToggle = () =&amp;gt; {
    if (isBookmarked) {
      removeBookmark(id, media_type);
    } else {
      addBookmark(id, media_type);
    }
  };

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

&lt;/div&gt;



&lt;p&gt;All we do here is simply destructure the add and remove functions from the store and then use them in an onClick function! (not showing the import statement, but it's the same as you saw before, don't forget it).&lt;/p&gt;

&lt;p&gt;How easy is Zustand? It's my favorite! And did you notice, I'm sure you did, we don't have to stringify and parse the local storage data ourselves? So helpful!&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this getting started tutorial and will check out Zustand for yourselves!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Iframes and Back Navigation - How to Fix the Browser History Bug</title>
      <dc:creator>Meg Meyers</dc:creator>
      <pubDate>Sat, 29 Mar 2025 00:13:51 +0000</pubDate>
      <link>https://dev.to/finalgirl321/iframes-and-back-navigation-how-to-fix-the-browser-history-bug-871</link>
      <guid>https://dev.to/finalgirl321/iframes-and-back-navigation-how-to-fix-the-browser-history-bug-871</guid>
      <description>&lt;p&gt;Many of us have worked with iframes in our web development careers. They are everywhere, and while they are usually fairly straightforward to implement, one little quirk is the back navigation bug. &lt;/p&gt;

&lt;p&gt;Today I was implementing an iframe in a React component and this problem came up. What I'm going to illustrate in this post is going to use ReactJS, however the JS is provided too. &lt;/p&gt;

&lt;p&gt;My component renders one iframe, and there are buttons in the UI that allow the user to switch the iframe's src url. These src changes were state updates, not navigation, however the iframe src change was adding to the browser history as if my app was navigating and this caused my browser's back button to have a bunch of junk history to go back through. My user went from page A to page B to see the iframe and use the buttons to switch the src for different content, and when they hit the browser back button they expect to go straight back to page A. What was actually happening is that every switch in src was adding not one but two entries to the history so the user would have to click, and click, and click, and what is going on....? What they saw was the iframe changing, not changing, changing, not changing. In short, a mess! &lt;/p&gt;

&lt;p&gt;Most users would know that this was a back nav through the iframe changes and think "But why is there an extra blank/nothing click in between? And why do we even need those iframe change navigations? I just want to go back to the previous page!"&lt;/p&gt;

&lt;p&gt;I thought about making a back button on the page that would skip the user directly back to the page they were expecting to see, but that didn't change anything in the history. Eventually they would have to get past that junk unless they completely switch to in-app navigation. That's not ok with me. There has to be a way to fix this right? Read on!&lt;/p&gt;

&lt;p&gt;With Iframes we have a reference to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/contentWindow" rel="noopener noreferrer"&gt;contentWindow&lt;/a&gt;. The content window is the window object of the document inside the iframe. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/location" rel="noopener noreferrer"&gt;Window.location&lt;/a&gt; can be &lt;em&gt;replaced&lt;/em&gt; in history, adding is not required. Can I put these two ideas together and solve my problem? Yes. This worked for me, so please give it a try.&lt;/p&gt;

&lt;p&gt;First, I added a ref to my iframe with the &lt;a href="https://react.dev/reference/react/useRef" rel="noopener noreferrer"&gt;useRef&lt;/a&gt; hook. If you aren't familiar with useRef yet I suggest you check it out, but it is a common way React developers refrain from using the document.getElement methods. I add my ref to my iframe:&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;iframe
                ref={iframeRef}
                id='vidFrame'
                className='absolute top-0 left-0 w-full h-full bg-black'
                width='100%'
                height='100%'
                src={'about:blank'}
                allow='encrypted-media'
                allowFullScreen
              &amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code to switch the src is a simple switch statement in a function triggered by the button clicks. Once the newSrcURL value is updated per the switch, the code that will solve this back navigation problem is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;iframe.current.contentWindow.location.replace(newSrcURL)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When you use the useRef hook, you then access your element with .current. So iframeRef.current above is equal to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;document.getElementById('vidFrame')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then we access the contentWindow and use location.replace to &lt;em&gt;replace&lt;/em&gt; the location instead of pushing to the history.&lt;/p&gt;

&lt;p&gt;The outcome is that your user can go from /page-a to /page-b (with the iframe), switch the src as much as they want, hit the browser back and go straight back to /page-a. Note that my iframe is not directly tied to state anymore. The iframe's jsx has src as 'about:blank.'&lt;/p&gt;

&lt;p&gt;My iframe is using content from third party servers, btw, in case some of you are thinking that this won't work for you for that reason. Good thinking, but it does work! With that said, there could be some conditions under which third party coding of the document in the iframe alters the outcome of this solution. &lt;/p&gt;

&lt;p&gt;I am happy to hear your comments and thoughts and let me know if this works for you!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>What is JavaScript scope &amp; the scope chain?</title>
      <dc:creator>Meg Meyers</dc:creator>
      <pubDate>Tue, 09 Jul 2024 02:37:26 +0000</pubDate>
      <link>https://dev.to/finalgirl321/what-is-javascript-scope-the-scope-chain-bbl</link>
      <guid>https://dev.to/finalgirl321/what-is-javascript-scope-the-scope-chain-bbl</guid>
      <description>&lt;p&gt;Scope is a fancy term to determine where a variable or function is available to be accessed or used. We have four types in JS - global, function, module (not discussed here) and block. &lt;/p&gt;

&lt;p&gt;The global scope is when you define something outside of any functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var name = "meg"

function sayHello() {
        ...more code here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The variable name is not inside any function, therefore it is in the global scope, and it is available to be accessed or used anywhere down the page including inside functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var name = "meg"

function sayHello() {
   console.log(name)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will print out "meg" on the console. The variable name's value is accessible everywhere below the line it is declared. The next type of scope is the function scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function sayHello() {
   var name = "meg";
   console.log(name)    // prints "meg" 
}

console.log(name)   // name is not accessible here, reference error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, name is declared inside the function sayHello, so name can be used from inside that function. However, if we try to access it outside the function, we can't. &lt;/p&gt;

&lt;p&gt;The next type of scope was introduced in es6. It is called block scope, and with it's introduction we received 'let' and 'const' both of which give us block scope. Block scope is when variables are accessible only between the curly braces for which they are declared and is specifically for code like conditional statements and loops.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const names = ["Alice", "Mary", "Tom"]

function sayHelloToAll() {
for (let i =0; i &amp;lt; names.length; i++){
      console.log("hi," + names[i])  // prints "hi, Alice" and so on for each name.
  }
console.log(i)

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

&lt;/div&gt;



&lt;p&gt;The variable i is declared with let inside a for loop and is therefore scoped only to the code within the curly braces of the for loop. If we try to access i outside of the curly braces, even in the same function, we can't access it. &lt;/p&gt;

&lt;p&gt;You will also hear the term "local" scope, which refers to a non-global variable living within a function, aka function scope. Local scope and block scope are &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; the same thing. Block scope is particularly for loops and conditions. &lt;/p&gt;

&lt;p&gt;The scope chain simply means that when JS is interpreted (when you run the code), it will look within the function for the variable and if it can't find it locally, it will look &lt;strong&gt;&lt;em&gt;upwards&lt;/em&gt;&lt;/strong&gt; for the variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const name = "Charlie"

function sayHello(){
  const pet = "cat";
  console.log(name)           // prints "charlie"
    function nameYourPet() {
      console.log(pet);       // prints "cat" 
      console.log(name);      // prints "Charlie"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our code above, the console log of the variable pet will look in the nested nameYourPet function and doesn't find it, so it goes up to the next level and finds it within the sayHello function. The console log of name looks within nameYourPet and doesn't find it, so it goes up to sayHello and still doesn't find it, but then it looks up in the global scope and finds it there. &lt;/p&gt;

&lt;p&gt;The scope chain moves &lt;strong&gt;&lt;em&gt;upward&lt;/em&gt;&lt;/strong&gt; only. &lt;/p&gt;

&lt;p&gt;As a new developer in 2024 it is very important to be able to discuss some of the complexities and oddities of JS for some interviews, so you should be able to talk about why var can be problematic. However, don't code with var anymore. You can do everything you need with let and const. If you or your employer do insist on still using var please turn on strict mode at the top of your js file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;'use strict'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and learn about IIFEs and closures as a few ways to protect your scope.&lt;/p&gt;

&lt;p&gt;In closing let's just look at a common interview question for determining your knowledge of scope and why var is problematic. Look at the following code and explain the outcome of the console logs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// we have four button elements in the html

 const buttonArr = document.querySelectorAll('button');
  for ( var i = 0; i &amp;lt; buttonArr.length; i++) {
    buttonArr[i].onclick = () =&amp;gt; console.log(`you clicked the number ${i+1} button`);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you use var, the console log will say you clicked the number 5. All of them will say you clicked 5 no matter which one you click. &lt;/p&gt;

&lt;p&gt;If you change the var to let, the console log will now correctly state if you clicked button 1, 2, 3, or 4. &lt;/p&gt;

&lt;p&gt;Why? Remember var is function scoped, and by the time the click event is triggered, the loop has completed and i has the value of buttonArr.length. When we switch to let we get a new i for each time through the loop. The event handler created in each iteration retains a reference to the i value from that specific iteration (it's actually a closure, but more on that some other time). &lt;/p&gt;

</description>
    </item>
    <item>
      <title>JavaScript NaN - advanced JS interview question</title>
      <dc:creator>Meg Meyers</dc:creator>
      <pubDate>Sat, 06 Jul 2024 03:31:47 +0000</pubDate>
      <link>https://dev.to/finalgirl321/lets-talk-about-js-nan-advanced-js-interview-question-3b7a</link>
      <guid>https://dev.to/finalgirl321/lets-talk-about-js-nan-advanced-js-interview-question-3b7a</guid>
      <description>&lt;p&gt;In JavaScript, NaN means "not a number." If you run&lt;/p&gt;

&lt;p&gt;&lt;code&gt;typeof NaN   // number&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;you will get "number" because NaN is used to define a number that really isn't a number. &lt;/p&gt;

&lt;p&gt;Many times when we code we need to be sure that the data type we are working with is actually a number. JS has a built in method called isNaN that will accept any input and let you know if it's not a number. Let's take a look at a few examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isNaN(1)    // false   1 is not not a number

isNaN("A")   // true   "A" is not number

isNaN("1")   // false  "1" is not not a number ..... wait what? 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The string "1" is a number? That's what JS is telling us. But anything in quotation marks is for sure Not a Number because anything in quotation marks is a &lt;em&gt;string&lt;/em&gt;. If you run &lt;code&gt;typeof "1"&lt;/code&gt; you will get string. The thing to understand here is that isNaN is doing &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Type_coercion" rel="noopener noreferrer"&gt;implicit coercion&lt;/a&gt; "behind the scenes." It is first calling Number("1"), which turns "1" into 1, and then running isNaN(1).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isNaN(Number("1"))    // false because it is a number now that it's been coerced.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, so basically isNaN isn't going to be very useful unless we are 100% sure of the input type, but in a &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Dynamic_typing" rel="noopener noreferrer"&gt;dynamically typed language&lt;/a&gt;, how can you ever really be sure? That's exactly why we need a foolproof way to check. Is there a way to &lt;strong&gt;&lt;em&gt;guarantee&lt;/em&gt;&lt;/strong&gt; that some input is truly not a number? Yes, and we can really impress our interviewers by showing them our deep understanding of JS. &lt;/p&gt;

&lt;p&gt;Firstly, in JS, anything compared to itself is true.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a = 1;
a === a;   //true 
b = "B";
b === b;   // true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and NaN compared to anything else will be false.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NaN === 1   // false  
NaN === false    // false
NaN === "b"   // false


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

&lt;/div&gt;



&lt;p&gt;Ok. So far so good. This makes perfect sense. So NaN compared to itself is going to be true then, right? Nope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NaN === NaN    //false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What? Ok, that's weird, but could we use this to our advantage to ensure something is a number?  If we know that only in this one situation of something being NaN will the comparison to itself fail, we can simple compare any variable in question to itself with the !== operator and if we get "true" we know that our input is NaN.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a = 1
a !== a  // false because 1 === 1

a = NaN
a !== a   // true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If 'a' is anything other than NaN, the !== will &lt;strong&gt;&lt;em&gt;always&lt;/em&gt;&lt;/strong&gt; be false. But due to the strange truth that NaN === NaN is false, a !== a will be true. &lt;/p&gt;

&lt;p&gt;Your deep knowledge of the inner workings of JS will surely impress! Now go use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN" rel="noopener noreferrer"&gt;Number.isNaN()&lt;/a&gt;. It's far more readable and understandable. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Add Nodemon to your TS projects - both CommonJS &amp; ESM</title>
      <dc:creator>Meg Meyers</dc:creator>
      <pubDate>Fri, 21 Jun 2024 23:13:03 +0000</pubDate>
      <link>https://dev.to/finalgirl321/how-to-add-nodemon-to-your-ts-files-4736</link>
      <guid>https://dev.to/finalgirl321/how-to-add-nodemon-to-your-ts-files-4736</guid>
      <description>&lt;p&gt;The dev package nodemon has been a great help providing hot-reloading as we code in our JavaScript, json, and other files while developing the in the NodeJS environment. To include the benefits of nodemon in your TypeScript projects so that the &lt;strong&gt;&lt;em&gt;unbuilt&lt;/em&gt;&lt;/strong&gt; .ts file hot-reloads as you go takes a bit more configuration, but isn't difficult. &lt;/p&gt;

&lt;p&gt;Start by installing nodemon and ts-node as dev tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -D nodemon ts-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the root of your project (on the same level as your package.json) made a config file called&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The rest of the instructions are split up for CommonJS and ESM. &lt;/p&gt;

&lt;p&gt;CommonJS  (you do not see "type":"module" in your package.json)&lt;br&gt;
In the nodemon.json file, paste the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "watch": ["src"],
    "ext":  "ts",
    "exec": "ts-node ./src/index.ts"
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extensions I am watching with nodemon are simply the .ts files located in the src folder. The package ts-node will be used to execute the file called index.ts in my src folder. Update per your needs. &lt;/p&gt;

&lt;p&gt;Put the following script in your package.json with your other scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"nodemon": "nodemon --watch 'src/**/*.ts' --exec ts-node src/index.ts",

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

&lt;/div&gt;



&lt;p&gt;You will now be able to start your project using the following command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;and as you code in the index.ts, the hot-reloading will update your server.&lt;/p&gt;

&lt;p&gt;ESM ( you do have "type":"module" in your package.json)&lt;br&gt;
Your nodemon.json now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "watch": ["src"],
  "ext": "ts",
  "execMap": {
    "ts": "node --no-warnings=ExperimentalWarning --loader ts-node/esm"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package.json script that used to be "nodemon" above now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   "dev": "nodemon src/index.ts",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;remember "dev" can be anything you want, and please update the actual name of your file if it's not index. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Deploy Express App to Render with MySQL</title>
      <dc:creator>Meg Meyers</dc:creator>
      <pubDate>Fri, 12 Jan 2024 04:33:06 +0000</pubDate>
      <link>https://dev.to/finalgirl321/deploy-express-app-to-render-with-mysql-2fpj</link>
      <guid>https://dev.to/finalgirl321/deploy-express-app-to-render-with-mysql-2fpj</guid>
      <description>&lt;p&gt;Now that Heroku isn't free anymore, we are all looking for new places to host our apps. Today we'll look at Render to host a Node/Express application with a Handlebars frontend and a MySQL database. &lt;/p&gt;

&lt;p&gt;With Heroku, we could provision our databases directly on the website while creating our app. Perhaps some of you used JawsDB, as I did many times. Provisioning it was as simple as clicking the button for a free add-on. Render does not have such an option for MySQL databases. However, we can bring our own database with only slightly more effort than with Heroku/Jaws.&lt;/p&gt;

&lt;p&gt;Today I am using a 5MB (same as Jaws) free database provided by &lt;a href="https://www.freemysqlhosting.net/"&gt;https://www.freemysqlhosting.net/&lt;/a&gt;.&lt;br&gt;
After signing up and verifying my email I was able to request the MySQL database and have credentials emailed to me within seconds. You will need the host name (not in the email, but right there on the website), sql3.freemysqlhosting.net, the DB name, DB password, and DB username for Render. If you don't want to use freemysqlhosting.net, please feel free to create a database anywhere you like. Every site that allows you to host a DB will give you the requisite information to plug in to Render.&lt;/p&gt;

&lt;p&gt;Within the app's code, we are using Sequelize as the ORM for working with our SQL database. We have a connection.js file that holds our code to connect our app to our database. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const Sequelize = require('sequelize');
require('dotenv').config();

let sequelize;

sequelize = new Sequelize(
  process.env.DB_NAME,
  process.env.DB_USER,
  process.env.DB_PASSWORD,

  {
    host: process.env.DB_HOST,
    dialect: 'mysql',
    port: 3306
  }
);


module.exports = sequelize;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a local .env that has the variable values, but we don't need that for our deploy. We have to enter the values of these environment (env) variables directly in Render. We'll get to that momentarily. &lt;/p&gt;

&lt;p&gt;After you create or update this connection code, be sure to push to GitHub. We are going to use the Render direct connection to GitHub to make things easier. &lt;/p&gt;

&lt;p&gt;On the Render website dashboard, &lt;a href="https://dashboard.render.com/"&gt;https://dashboard.render.com/&lt;/a&gt;, click "NEW" and create a new web service. Then choose to deploy from a Git repository. Connect the repository you want to deploy and fill in some basic information, such as a name for the project, the branch you wish to use, the runtime, build command, and start command. These should be filled in for you already, and I all I like to change is the start command, because I use "npm start".&lt;/p&gt;

&lt;p&gt;Make sure you click the Free option, and under the payment options you will see a place to enter Environment Variables. &lt;/p&gt;

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

&lt;p&gt;Fill them out so that they match your connection.js above. Here is the example that matches my connection code above:&lt;/p&gt;

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

&lt;p&gt;(you can disregard the SESS_SEC if you are not using session secrets, that is not a part of our tutorial today)&lt;/p&gt;

&lt;p&gt;Once you have the DB env variables filled out with the actual values given to you in the email from the freemysqlhosting.net, you are ready to create the web service, so hit the create button at the bottom of the page. &lt;/p&gt;

&lt;p&gt;You will be shown the logs of your build/deploy. You will have to solve any issues that come up, of course, but that is outside the scope of this tutorial. If everything works with the database, you will see the SQL that creates the tables on the log just as you do in your node terminal when you start your app on localhost. &lt;/p&gt;

&lt;p&gt;The address for your new deployed app is at the top left of the page, and you should now follow that address and make sure your app is fully functioning. &lt;/p&gt;

&lt;p&gt;Hopefully you have enjoyed this easy way to BYODB to Render and deploy a new web service. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mysql</category>
      <category>deploy</category>
      <category>express</category>
    </item>
  </channel>
</rss>
