<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Javascript &#8211; COMPUTERCOURSESONLINE</title>
	<atom:link href="https://computercoursesonline.com/index.php/category/javascript/feed/" rel="self" type="application/rss+xml" />
	<link>http://computercoursesonline.com</link>
	<description></description>
	<lastBuildDate>Wed, 18 Mar 2026 10:00:37 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.9.2</generator>
	<item>
		<title>Moving From Moment.js To The JS Temporal API</title>
		<link>http://computercoursesonline.com/index.php/2026/03/13/moving-from-moment-js-to-the-js-temporal-api/</link>
					<comments>http://computercoursesonline.com/index.php/2026/03/13/moving-from-moment-js-to-the-js-temporal-api/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Fri, 13 Mar 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=1168</guid>

					<description><![CDATA[Moving From Moment.js To The JS Temporal API Moving From Moment.js To The JS Temporal API Joe Attardi 2026-03-13T13:00:00+00:00 2026-03-18T09:33:12+00:00 Almost any kind of application written in JavaScript works with times or dates in some capacity. In the beginning, this was limited to the built-in Date API. This API includes basic functionality, but is quite...]]></description>
										<content:encoded><![CDATA[<p>              <title>Moving From Moment.js To The JS Temporal API</title></p>
<article>
<header>
<h1>Moving From Moment.js To The JS Temporal API</h1>
<address>Joe Attardi</address>
<p>                  2026-03-13T13:00:00+00:00<br />
                  2026-03-18T09:33:12+00:00<br />
                </header>
<p>Almost any kind of application written in JavaScript works with times or dates in some capacity. In the beginning, this was limited to the built-in <code>Date</code> API. This API includes basic functionality, but is quite limited in what it can do.</p>
<p>Third-party libraries like Moment.js, and later built-in APIs such as the <code>Intl</code> APIs and the new <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal">Temporal API</a>, add much greater flexibility to working with times and dates.</p>
<h2 id="the-rise-and-fall-of-moment-js">The Rise And Fall Of Moment.js</h2>
<p><a href="https://momentjs.com">Moment.js</a> is a JavaScript library with powerful utilities for working with times and dates. It includes missing features from the basic <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date">Date API</a>, such as time zone manipulation, and makes many common operations simpler. Moment also includes functions for formatting dates and times. It became a widely used library in many different applications.</p>
<p>However, Moment also had its share of issues. It’s a large library, and can add significantly to an application’s bundle size. Because the library doesn’t support tree shaking (a feature of modern bundlers that can remove unused parts of libraries), the entire Moment library is included even if you only use one or two of its functions.</p>
<p>Another issue with Moment is the fact that the objects it creates are <em>mutable</em>. Calling certain functions on a Moment object has side effects and mutates the value of that object. This can lead to unexpected behavior or bugs.</p>
<p>In 2020, the maintainers of Moment decided to put the library into maintenance mode. No new feature development is being done, and the maintainers recommend against using it for new projects.</p>
<p>There are other JavaScript date libraries, such as <code>date-fns</code>, but there’s a new player in town, an API built directly into JavaScript: <strong>Temporal</strong>. It’s a new standard that fills in the holes of the original <code>Date</code> API as well as solves some of the limitations found in Moment and other libraries.</p>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="what-is-temporal">What Is Temporal?</h2>
<p>Temporal is a new time and date API being added to the ECMAScript standard, which defines modern JavaScript. As of March 2026, it has reached Stage 4 of the TC39 process (the committee that oversees proposals and additions to the JavaScript language), and will be included in the next version of the ECMAScript specification. It has already been implemented in several browsers: <a href="https://developer.chrome.com/release-notes/144">Chrome</a> <a href="https://developer.chrome.com/release-notes/144">144+</a> and <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/139#javascript">Firefox</a> <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/139#javascript">139+</a>, with <a href="https://bugs.webkit.org/show_bug.cgi?id=223166">Safari expected to follow soon</a>. A <a href="https://github.com/js-temporal/temporal-polyfill">polyfill is also available</a> for unsupported browsers and Node.js.</p>
<p>The Temporal API creates objects that, generally, represent moments in time. These can be full-time and date stamps in a given time zone, or they can be a generic instance of “wall clock” time without any time zone or date information. Some of the main features of Temporal include:</p>
<ul>
<li><strong>Times with or without dates.</strong><br />
A Temporal object can represent a specific time on a specific date, or a time without any date information. A specific date, without a time, can also be represented.</li>
<li><strong>Time zone support.</strong><br />
Temporal objects are fully time zone aware and can be converted across different time zones. Moment supports time zones, too, but it requires the additional <code>moment-timezone</code> library.</li>
<li><strong>Immutability.</strong><br />
Once a Temporal object is created, it cannot be changed. Time arithmetic or time zone conversions do not modify the underlying object. Instead, they generate a new Temporal object.</li>
<li><strong>1-based indexing.</strong><br />
A common source of bugs with the Date API (as well as with Moment) is that months are zero-indexed. This means that January is month <code>0</code>, rather than month <code>1</code> as we all understand in real life. Temporal fixes this by using 1-based indexing &mdash; January is month <code>1</code>.</li>
<li><strong>It’s built into the browser.</strong><br />
Since Temporal is an API in the browser itself, it adds nothing to your application’s bundle size.</li>
</ul>
<p>It’s also important to note that the Date API isn’t going away. While Temporal supersedes this API, it is not being removed or deprecated. Many applications would break if browsers suddenly removed the Date API. However, also keep in mind that Moment is now considered a legacy project in maintenance mode.</p>
<p>In the rest of the article, we’ll look at some “recipes” for migrating Moment-based code to the new Temporal API. Let’s start refactoring!</p>
<h2 id="creating-date-and-time-objects">Creating Date And Time Objects</h2>
<p>Before we can manipulate dates and times, we have to create objects representing them. To create a Moment object representing the current date and time, use the <code>moment</code> function.</p>
<pre><code class="language-javascript">const now = moment();
console.log(now); 
// Moment&lt;2026-02-18T21:26:29-05:00&gt;
</code></pre>
<p>This object can now be formatted or manipulated as needed.</p>
<div class="break-out">
<pre><code class="language-javascript">// convert to UTC
// warning: This mutates the Moment object and puts it in UTC mode!
console.log(now.utc()); 
// Moment&lt;2026-02-19T02:26:29Z&gt;

// print a formatted string - note that it's using the UTC time now
console.log(now.format('MM/DD/YYYY hh:mm:ss a')); 
// 02/19/2026 02:27:07 am
</code></pre>
</div>
<p>The key thing to remember about Moment is that a Moment object always includes information about the time <em>and</em> the date. If you only need to work with time information, this is usually fine, but it can cause unexpected behavior in situations like Daylight Saving Time or leap years, where the date can have an effect on time calculations.</p>
<p>Temporal is more flexible. You can create an object representing the current date and time by creating a <code>Temporal.Instant</code> object. This represents a point in time defined by the time since “the epoch” (midnight UTC on January 1, 1970). Temporal can reference this instant in time with nanosecond-level precision.</p>
<pre><code class="language-javascript">const now = Temporal.Now.instant();

// see raw nanoseconds since the epoch
console.log(now.epochNanoseconds);
// 1771466342612000000n

// format for UTC
console.log(now.toString());
// 2026-02-19T01:55:27.844Z

// format for a particular time zone
console.log(now.toString({ timeZone: 'America/New&#095;York' }));
// 2026-02-18T20:56:57.905-05:00
</code></pre>
<p><code>Temporal.Instant</code> objects can also be created for a specific time and date by using the <code>from</code> static method.</p>
<div class="break-out">
<pre><code class="language-javascript">const myInstant = Temporal.Instant.from('2026-02-18T21:10:00-05:00');

// Format the instant in the local time zone. Note that this only controls
// the formatting - it does not mutate the object like `moment.utc` does.
console.log(myInstant.toString({ timeZone: 'America/New&#095;York' }));
// 2026-02-18T21:10:00-05:00
</code></pre>
</div>
<p>You can also create other types of Temporal objects, including:</p>
<ul>
<li><strong><code>Temporal.PlainDate</code></strong>: A date with no time information.</li>
<li><strong><code>Temporal.PlainTime</code></strong>: A time with no date information.</li>
<li><strong><code>Temporal.ZonedDateTime</code></strong>: A date and time in a specific time zone.</li>
</ul>
<p>Each of these has a <code>from</code> method that can be called with an object specifying the date and/or time, or a date string to parse.</p>
<pre><code class="language-javascript">// Just a date
const today = Temporal.PlainDate.from({
  year: 2026,
  month: 2, // note we're using 2 for February
  day: 18
});
console.log(today.toString());
// 2026-02-18

// Just a time
const lunchTime = Temporal.PlainTime.from({
  hour: 12
});
console.log(lunchTime.toString());
// 12:00:00 

// A date and time in the US Eastern time zone
const dueAt = Temporal.ZonedDateTime.from({
  timeZone: 'America/New&#095;York',
  year: 2026,
  month: 3,
  day: 1,
  hour: 12,
  minute: 0,
  second: 0
});
console.log(dueAt.toString());
// 2026-03-01T12:00:00-05:00[America/New&#095;York]
</code></pre>
<div class="partners__lead-place"></div>
<h2 id="parsing">Parsing</h2>
<p>We’ve covered programmatic creation of date and time information. Now let’s look at parsing. Parsing is one area where Moment is more flexible than the built-in Temporal API.</p>
<p>You can parse a date string by passing it to the <code>moment</code> function. With a single argument, Moment expects an ISO date string, but you can use alternative formats if you provide a second argument specifying the date format being used.</p>
<div class="break-out">
<pre><code class="language-javascript">const isoDate = moment('2026-02-21T09:00:00');
const formattedDate = moment('2/21/26 9:00:00', 'M/D/YY h:mm:ss');

console.log(isoDate);
// Moment&lt;2026-02-21T09:00:00-05:00&gt;

console.log(formattedDate);
// Moment&lt;2026-02-21T09:00:00-05:00&gt;
</code></pre>
</div>
<p>In older versions, Moment would make a best guess to parse any arbitrarily formatted date string. This could lead to unpredictable results. For example, is <code>02-03-2026</code> February 2 or March 3? For this reason, newer versions of Moment display a prominent deprecation warning if it’s called without an ISO formatted date string (unless the second argument with the desired format is also given).</p>
<p>Temporal will only parse a specifically formatted date string. The string must be compliant with the ISO 8601 format or its extension, RFC 9557. If a non-compliant date string is passed to a <code>from</code> method, Temporal will throw a <code>RangeError</code>.</p>
<div class="break-out">
<pre><code class="language-javascript">// Using an RFC 9557 date string
const myDate = Temporal.Instant.from('2026-02-21T09:00:00-05:00[America/New&#095;York]');
console.log(myDate.toString({ timeZone: 'America/New&#095;York' }));
// 2026-02-21T09:00:00-05:00

// Using an unknown date string
const otherDate = Temporal.Instant.from('2/21/26 9:00:00');
// RangeError: Temporal error: Invalid character while parsing year value.
</code></pre>
</div>
<p>The exact requirements of the date string depend on which kind of Temporal object you’re creating. In the above example, <code>Temporal.Instant</code> requires a full ISO 8601 or RFC 9557 date string specifying the date and time with a time zone offset, but you can also create <code>PlainDate</code> or <code>PlainTime</code> objects using just a subset of the date format.</p>
<pre><code class="language-javascript">const myDate = Temporal.PlainDate.from('2026-02-21');
console.log(myDate.toString());
// 2026-02-21

const myTime = Temporal.PlainTime.from('09:00:00');
console.log(myTime.toString());
// 09:00:00
</code></pre>
<p>Note that these strings must still comply with the expected format, or an error will be thrown.</p>
<div class="break-out">
<pre><code class="language-javascript">// Using a non-compliant time strings. These will all throw a RangeError.
Temporal.PlainTime.from('9:00');
Temporal.PlainTime.from('9:00:00 AM');
</code></pre>
</div>
<blockquote><p><strong>Pro tip: Handling non-ISO strings</strong></p>
<p>Because Temporal prioritizes reliability, it won’t try to guess the format of a string like <code>02-01-2026</code>. If your data source uses such strings, you will need to do some string manipulation to rearrange the values into an ISO string like <code>2026-02-01</code> before attempting to use it with Temporal.</p></blockquote>
<h2 id="formatting">Formatting</h2>
<p>Once you have a Moment or Temporal object, you’ll probably want to convert it to a formatted string at some point.</p>
<p>This is an instance where Moment is a bit more terse. You call the object’s <code>format</code> method with a string of tokens that describe the desired date format.</p>
<pre><code class="language-javascript">const date = moment();

console.log(date.format('MM/DD/YYYY'));
// 02/22/2026

console.log(date.format('MMMM Do YYYY, h:mm:ss a'));
// February 22nd 2026, 8:18:30 pm
</code></pre>
<p>On the other hand, Temporal requires you to be a bit more verbose. Temporal objects, such as <code>Instant</code>, have a <code>toLocaleString</code> method that accepts various formatting options specified as properties of an object.</p>
<div class="break-out">
<pre><code class="language-javascript">const date = Temporal.Now.instant();

// with no arguments, we'll get the default format for the current locale
console.log(date.toLocaleString());
// 2/22/2026, 8:23:36 PM (assuming a locale of en-US)

// pass formatting options to generate a custom format string
console.log(date.toLocaleString('en-US', {
  month: 'long',
  day: 'numeric',
  year: 'numeric',
  hour: '2-digit',
  minute: '2-digit'
}));
// February 22, 2026 at 8:23 PM

// only pass the fields you want in the format string
console.log(date.toLocaleString('en-US', {
  month: 'short',
  day: 'numeric'
}));
// Feb 22
</code></pre>
</div>
<p><strong>Temporal date formatting actually uses the <code>Intl.DateTimeFormat</code> API</strong> (which is already readily available in modern browsers) under the hood. That means you can create a reusable <code>DateTimeFormat</code> object with your custom formatting options, then pass Temporal objects to its <code>format</code> method. Because of this, it doesn’t support custom date formats like Moment does. If you need something like <code>'Q1 2026'</code> or other specialized formatting, you may need some custom date formatting code or reach for a third-party library.</p>
<pre><code class="language-javascript">const formatter = new Intl.DateTimeFormat('en-US', {
  month: '2-digit',
  day: '2-digit',
  year: 'numeric'
});

const date = Temporal.Now.instant();
console.log(formatter.format(date));
// 02/22/2026
</code></pre>
<p>Moment’s formatting tokens are simpler to write, but they aren’t locale-friendly. The format strings “hard code” things like month/day order. The advantage of using a configuration object, as Temporal does, is that it will automatically adapt to any given locale and use the correct format.</p>
<pre><code class="language-javascript">const date = Temporal.Now.instant();

const formatOptions = {
  month: 'numeric',
  day: 'numeric',
  year: 'numeric'
};


console.log(date.toLocaleString('en-US', formatOptions));
// 2/22/2026

console.log(date.toLocaleString('en-GB', formatOptions));
// 22/02/2026
</code></pre>
<h2 id="date-calculations">Date calculations</h2>
<p>In many applications, you’ll need to end up performing some calculations on a date. You may want to add or subtract units of time (days, hours, seconds, etc.). For example, if you have the current date, you may want to show the user the date 1 week from now.</p>
<p>Moment objects have methods such as <code>add</code> and <code>subtract</code> that perform these operations. These functions take a value and a unit, for example: <code>add(7, 'days')</code>. One very important difference between Moment and Temporal, however, is that when performing these date calculations, the underlying object is modified and its original value is lost.</p>
<pre><code class="language-javascript">const now = moment();

console.log(now);
// Moment&lt;2026-02-24T20:08:36-05:00&gt;

const nextWeek = now.add(7, 'days');
console.log(nextWeek);
// Moment&lt;2026-03-03T20:08:36-05:00&gt;

// Gotcha - the original object was mutated
console.log(now);
// Moment&lt;2026-03-03T20:08:36-05:00&gt;
</code></pre>
<p>To avoid losing the original date, you can call <code>clone</code> on the Moment object to create a copy.</p>
<pre><code class="language-javascript">const now = moment();
const nextWeek = now.clone().add(7, 'days');

console.log(now);
// Moment&lt;2026-02-24T20:12:55-05:00&gt;

console.log(nextWeek);
// Moment&lt;2026-03-03T20:12:55-05:00&gt;
</code></pre>
<p>On the other hand, Temporal objects are <em>immutable</em>. Once you’ve created an object like an <code>Instant</code>, <code>PlainDate</code>, and so on, the value of that object will never change. Temporal objects also have <code>add</code> and <code>subtract</code> methods.</p>
<p>Temporal is a little picky about which time units can be added to which object types. For example, you can’t add days to an <code>Instant</code>:</p>
<div class="break-out">
<pre><code class="language-javascript">const now = Temporal.Now.instant();
const nextWeek = now.add({ days: 7 });
// RangeError: Temporal error: Largest unit cannot be a date unit
</code></pre>
</div>
<p>This is because <code>Instant</code> objects represent a specific point in time in UTC and are calendar-agnostic. Because the length of a day can change based on time zone rules such as Daylight Saving Time, this calculation isn’t available on an <code>Instant</code>. You <em>can</em>, however, perform this operation on other types of objects, such as a <code>PlainDateTime</code>:</p>
<pre><code class="language-javascript">const now = Temporal.Now.plainDateTimeISO();
console.log(now.toLocaleString());
// 2/24/2026, 8:23:59 PM

const nextWeek = now.add({ days: 7 });

// Note that the original PlainDateTime remains unchanged
console.log(now.toLocaleString());
// 2/24/2026, 8:23:59 PM

console.log(nextWeek.toLocaleString());
// 3/3/2026, 8:23:59 PM
</code></pre>
<p>You can also calculate how much time is between two Moment or Temporal objects.</p>
<p>With Moment’s <code>diff</code> function, you need to provide a unit for granularity, otherwise it will return the difference in milliseconds.</p>
<pre><code class="language-javascript">const date1 = moment('2026-02-21T09:00:00');
const date2 = moment('2026-02-22T10:30:00');

console.log(date2.diff(date1));
// 91800000

console.log(date2.diff(date1, 'days'));
// 1
</code></pre>
<p>To do this with a Temporal object, you can pass another Temporal object to its <code>until</code> or <code>since</code> methods. This returns a <code>Temporal.Duration</code> object containing information about the time difference. The <code>Duration</code> object has properties for each component of the difference, and also can generate an <a href="https://en.wikipedia.org/wiki/ISO_8601#Durations">ISO 8601</a> duration string representing the time difference.</p>
<div class="break-out">
<pre><code class="language-javascript">const date1 = Temporal.PlainDateTime.from('2026-02-21T09:00:00');
const date2 = Temporal.PlainDateTime.from('2026-02-22T10:30:00');

// largestUnit specifies the largest unit of time to represent
// in the duration calculation
const diff = date2.since(date1, { largestUnit: 'day' });

console.log(diff.days);
// 1

console.log(diff.hours);
// 1

console.log(diff.minutes);
// 30

console.log(diff.toString());
// P1DT1H30M
// (ISO 8601 duration string: 1 day, 1 hour, 30 minutes)
</code></pre>
</div>
<h2 id="comparing-dates-and-times">Comparing Dates And Times</h2>
<p>Moment and Temporal both let you compare dates and times to determine which comes before the other, but take different approaches with the API.</p>
<p>Moment provides methods such as <code>isBefore</code>, <code>isAfter</code>, and <code>isSame</code> to compare two Moment objects.</p>
<pre><code class="language-javascript">const date1 = moment('2026-02-21T09:00:00');
const date2 = moment('2026-02-22T10:30:00');

console.log(date1.isBefore(date2));
// true
</code></pre>
<p>Temporal uses a static <code>compare</code> method to perform a comparison between two objects of the same type. It returns <code>-1</code> if the first date comes before the second, <code>0</code> if they are equal, or <code>1</code> if the first date comes after the second. The following example shows how to compare two <code>PlainDate</code> objects. Both arguments to <code>Temporal.PlainDate.compare</code> must be <code>PlainDate</code> objects.</p>
<div class="break-out">
<pre><code class="language-javascript">const date1 = Temporal.PlainDate.from({ year: 2026, month: 2, day: 24 });
const date2 = Temporal.PlainDate.from({ year: 2026, month: 3, day: 24 });

// date1 comes before date2, so -1
console.log(Temporal.PlainDate.compare(date1, date2));

// Error if we try to compare two objects of different types
console.log(Temporal.PlainDate.compare(date1, Temporal.Now.instant()));
// TypeError: Temporal error: Invalid PlainDate fields provided.
</code></pre>
</div>
<p>In particular, this makes it easy to sort an array of Temporal objects chronologically.</p>
<pre><code class="language-javascript">// An array of Temporal.PlainDate objects
const dates = [ ... ];

// use Temporal.PlainDate.compare as the comparator function
dates.sort(Temporal.PlainDate.compare);
</code></pre>
<h2 id="time-zone-conversions">Time Zone Conversions</h2>
<p>The core Moment library doesn’t support time zone conversions. If you need this functionality, you also need to install the <code>moment-timezone</code> package. This package is not tree-shakable, and therefore can add significantly to your bundle size. Once you’ve installed <code>moment-timezone</code>, you can convert Moment objects to different time zones with the <code>tz</code> method. As with other Moment operations, this mutates the underlying object.</p>
<pre><code class="language-javascript">// Assuming US Eastern time
const now = moment();
console.log(now);
// Moment&lt;2026-02-28T20:08:20-05:00&gt;

// Convert to Pacific time.
// The original Eastern time is lost.
now.tz('America/Los&#095;Angeles');
console.log(now);
// Moment&lt;2026-02-28T17:08:20-08:00&gt;
</code></pre>
<p>Time zone functionality is built into the Temporal API when using a <code>Temporal.ZonedDateTime</code> object. These objects include a <code>withTimeZone</code> method that returns a new <code>ZonedDateTime</code> representing the same moment in time, but in the specified time zone.</p>
<pre><code class="language-javascript">// Again, assuming US Eastern time
const now = Temporal.Now.zonedDateTimeISO();
console.log(now.toLocaleString());
// 2/28/2026, 8:12:02 PM EST

// Convert to Pacific time
const nowPacific = now.withTimeZone('America/Los&#095;Angeles');
console.log(nowPacific.toLocaleString());
// 2/28/2026, 5:12:02 PM PST

// Original object remains unchanged
console.log(now.toLocaleString());
// 2/28/2026, 8:12:02 PM EST
</code></pre>
<p><strong>Note:</strong> <em>The formatted values returned by <code>toLocaleString</code> are, as the name implies, locale-dependent. The sample code was developed in the <code>en-US</code> locale, so the format is like this: <code>2/28/2026, 5:12:02 PM PST</code>. In another locale, this may be different. For example, in the <code>en-GB</code> locale, you would get something like <code>28/2/2026, 17:12:02 GMT-8</code>.</em></p>
<div class="partners__lead-place"></div>
<h2 id="a-real-world-refactoring">A Real-world Refactoring</h2>
<p>Suppose we’re building an app for scheduling events across time zones. Part of this app is a function, <code>getEventTimes</code>, which takes an ISO 8601 string representing the time and date of the event, a local time zone, and a target time zone. The function creates formatted time and date strings for the event in both time zones.</p>
<p>If the function is given an input string that’s not a valid time/date string, it will throw an error.</p>
<p>Here’s the original implementation, using Moment (also requiring use of the <code>moment-timezone</code> package).</p>
<div class="break-out">
<pre><code class="language-javascript">import moment from 'moment-timezone';

function getEventTimes(inputString, userTimeZone, targetTimeZone) {
  const timeFormat = 'MMM D, YYYY, h:mm:ss a z';

  // 1. Create the initial moment in the user's time zone
  const eventTime = moment.tz(
    inputString,
    moment.ISO&#095;8601, // Expect an ISO 8601 string
    true, // Strict parsing
    userTimeZone
  );
  
  // Throw an error if the inputString did not represent a valid date
  if (!eventTime.isValid()) {
    throw new Error('Invalid date/time input');
  }

  // 2. Calculate the target time
  // CRITICAL: We must clone, or 'eventTime' changes forever!
  const targetTime = eventTime.clone().tz(targetTimeZone);

  return {
    local: eventTime.format(timeFormat),
    target: targetTime.format(timeFormat),
  };
}

const schedule = getEventTimes(
  '2026-03-05T15:00-05:00',
  'America/New&#095;York',
  'Europe/London',
);

console.log(schedule.local);
// Mar 5, 2026, 3:00:00 pm EST

console.log(schedule.target); 
// Mar 5, 2026, 8:00:00 pm GMT
</code></pre>
</div>
<p>In this example, we’re using an expected date format of ISO 8601, which is helpfully built into Moment. We’re also using strict parsing, which means Moment won’t try to guess with a date string that doesn’t match the format. If a non-ISO date string is passed, it will result in an invalid date object, and we throw an error.</p>
<p>The Temporal implementation looks similar, but has a few key differences.</p>
<div class="break-out">
<pre><code class="language-javascript">function getEventTimes(inputString, userTimeZone, targetTimeZone) {
  // 1. Parse the input directly into an Instant, then create
  // a ZonedDateTime in the user's zone.
  const instant = Temporal.Instant.from(inputString);
  const eventTime = instant.toZonedDateTimeISO(userTimeZone);

  // 2. Convert to the target zone
  // This automatically returns a NEW object; 'eventTime' is safe.
  const targetTime = eventTime.withTimeZone(targetTimeZone);

  // 3. Format using Intl (built-in)
  const options = {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
    hour: 'numeric',
    minute: '2-digit',
    second: '2-digit',
    timeZoneName: 'short'
  };

  return {
    local: eventTime.toLocaleString(navigator.language, options),
    target: targetTime.toLocaleString(navigator.language, options)
  };
}

const schedule = getEventTimes(
  '2026-03-05T15:00-05:00',
  'America/New&#095;York',
  'Europe/London',
);

console.log(schedule.local);
// Mar 5, 2026, 3:00:00 PM EST

console.log(schedule.target);
// Mar 5, 2026, 8:00:00 PM GMT
</code></pre>
</div>
<p>With Moment, we have to explicitly specify a format string for the resulting date strings. Regardless of the user’s location or locale, the event times will always be formatted as <code>Mar 5, 2026, 3:00:00 pm EST</code>.</p>
<p>Also, we don’t have to explicitly throw an exception. If an invalid string is passed to <code>Temporal.Instant.from</code>, Temporal will throw the exception for us. One thing to note is that even with strict parsing, the Moment version is still more lenient. Temporal requires the time zone offset at the end of the string.</p>
<p>You should also note that since we’re using <code>navigator.language</code>, this code will only run in a browser environment, as <code>navigator</code> is not defined in a Node.js environment.</p>
<p>The Temporal implementation uses the browser’s current locale (<code>navigator.language</code>), so the user will automatically get event times formatted in their local time format. In the <code>en-US</code> locale, this is <code>Mar 5, 2026, 3:00:00 pm EST</code>. However, if the user is in London, for example, the event times will be formatted as <code>5 Mar 2026, 15:00:00 GMT-5</code>.</p>
<h2 id="summary">Summary</h2>
<table class="tablesaw break-out">
<thead>
<tr>
<th>Action</th>
<th>Moment.js</th>
<th>Temporal</th>
</tr>
</thead>
<tbody>
<tr>
<td>Current time</td>
<td><code>moment()</code></td>
<td><code>Temporal.Now.zonedDateTimeISO()</code></td>
</tr>
<tr>
<td>Parsing ISO</td>
<td><code>moment(str)</code></td>
<td><code>Temporal.Instant.from(str)</code></td>
</tr>
<tr>
<td>Add time</td>
<td><code>.add(7, 'days')</code> (mutates)</td>
<td><code>.add({ days: 7 })</code> (new object)</td>
</tr>
<tr>
<td>Difference</td>
<td><code>.diff(other, 'hours')</code></td>
<td><code>.since(other).hours</code></td>
</tr>
<tr>
<td>Time zone</td>
<td><code>.tz('Zone/Name')</code></td>
<td><code>.withTimeZone('Zone/Name')</code></td>
</tr>
</tbody>
</table>
<p>At first glance, the difference may be slightly different (and in the case of Temporal, sometimes more verbose and more strict) syntax, but there are several key advantages to using Temporal over Moment.js:</p>
<ul>
<li>Being more explicit means <strong>fewer surprises and unintended bugs</strong>. Moment may appear to be more lenient, but it involves “guesswork,” which can sometimes result in incorrect dates. If you give Temporal something invalid, it throws an error. If the code runs, you know you’ve got a valid date.</li>
<li>Moment can add significant size to the application’s bundle, particularly if you’re using the <code>moment-timezone</code> package. Temporal adds nothing (once it’s shipped in your target browsers).</li>
<li><strong>Immutability</strong> gives you the confidence that you’ll never lose or overwrite data when performing date conversions and operations.</li>
<li><strong>Different representations of time</strong> (<code>Instant</code>, <code>PlainDateTime</code>, <code>ZonedDateTime</code>) depending on your requirements, where Moment is always a wrapper around a UTC timestamp.</li>
<li>Temporal uses the <strong><code>Intl</code> APIs for date formatting</strong>, which means you can have locale-aware formatting without having to explicitly specify tokens.</li>
</ul>
<h2 id="notes-on-the-polyfill">Notes On The Polyfill</h2>
<p>As mentioned earlier, there is a Temporal polyfill available, distributed as an npm package named <code>@js-temporal/polyfill</code>. If you want to use Temporal today, you’ll need this polyfill to support browsers like Safari that haven’t shipped the API yet. The bad news with this is that it will add to your bundle size. The good news is that it still adds significantly less than <code>moment</code> or <code>moment-timezone</code>. Here is a comparison of the bundle sizes as reported by Bundlephobia.com, a website that presents information on npm package sizes (click on each package name to see the Bundlephobia analysis):</p>
<table class="tablesaw break-out">
<thead>
<tr>
<th>Package</th>
<th>Minified</th>
<th>Minified &amp; gzipped</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://bundlephobia.com/package/@js-temporal/polyfill"><code>@js-temporal/polyfill</code></a></td>
<td>154.1 kB</td>
<td>44.1 kB</td>
</tr>
<tr>
<td><a href="https://bundlephobia.com/package/moment"><code>moment</code></a></td>
<td>294.4 kB</td>
<td>75.4 kB</td>
</tr>
<tr>
<td><a href="https://bundlephobia.com/package/moment-timezone"><code>moment-timezone</code></a></td>
<td>1 MB</td>
<td>114.2 kB</td>
</tr>
</tbody>
</table>
<p>The polyfill also has historically had some performance issues around memory usage, and at the time of writing, it’s considered to be in an alpha state. Because of this, you may not want to use it in production until it reaches a more mature state.</p>
<p>The other good news is that hopefully the polyfill won’t be needed much longer (unless you need to support older browsers, of course). At the time of writing, Temporal has shipped in Chrome, Edge, and Firefox. It’s not quite ready in Safari yet, though it appears to be available with a runtime flag on the latest Technology Preview.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2026/03/13/moving-from-moment-js-to-the-js-temporal-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building Dynamic Forms In React And Next.js</title>
		<link>http://computercoursesonline.com/index.php/2026/03/10/building-dynamic-forms-in-react-and-next-js/</link>
					<comments>http://computercoursesonline.com/index.php/2026/03/10/building-dynamic-forms-in-react-and-next-js/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Tue, 10 Mar 2026 13:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=1170</guid>

					<description><![CDATA[Building Dynamic Forms In React And Next.js Building Dynamic Forms In React And Next.js Sunil Sandhu 2026-03-10T13:00:00+00:00 2026-03-18T09:33:12+00:00 This article is sponsored by SurveyJS There’s a mental model most React developers share without ever discussing it out loud. That forms are always supposed to be components. This means a stack like: React Hook Form for...]]></description>
										<content:encoded><![CDATA[<p>              <title>Building Dynamic Forms In React And Next.js</title></p>
<article>
<header>
<h1>Building Dynamic Forms In React And Next.js</h1>
<address>Sunil Sandhu</address>
<p>                  2026-03-10T13:00:00+00:00<br />
                  2026-03-18T09:33:12+00:00<br />
                </header>
<p>This article is sponsored by <b>SurveyJS</b></p>
<p>There’s a mental model most React developers share without ever discussing it out loud. That forms are <em>always</em> supposed to be components. This means a stack like:</p>
<ul>
<li><strong>React Hook Form</strong> for local state (minimal re-renders, ergonomic field registration, imperative interaction).</li>
<li><strong>Zod</strong> for validation (input correctness, boundary validation, type-safe parsing).</li>
<li><strong>React Query</strong> for backend: submission, retries, caching, server sync, and so on.</li>
</ul>
<p>And for the vast majority of forms &mdash; your login screens, your settings pages, your CRUD modals &mdash; this works really well. Each piece does its job, they compose cleanly, and you can move on to the parts of your application that actually differentiate your product.</p>
<p>But every once in a while, a form starts accumulating things like visibility rules that depend on earlier answers, or derived values that cascade through three fields. Maybe even entire pages that should be skipped or shown based on a running total.</p>
<p>You handle the first conditional with a <code>useWatch</code> and an inline branch, which is fine. Then another. <a href="https://zod.dev/api#superrefine">Then you’re reaching for <code>superRefine</code></a> to encode cross-field rules that your Zod schema can’t express in the normal way. Then, step navigation starts leaking business logic. At some point, you look at what you’ve built and realize that the form isn’t really UI anymore. It’s more of a decision process, and the component tree is just where you happened to store it.</p>
<p>This is where I think the mental model for forms in React breaks down, and it’s really nobody’s fault. The RHF + Zod stack is excellent at what it was designed for. <strong>The issue is that we tend to keep using it past the point where its abstractions match the problem</strong> because the alternative requires a different way of thinking about forms entirely.</p>
<p>This article is about that alternative. To show this, we’ll build the exact same multi-step form twice:</p>
<ol>
<li>With React Hook Form + Zod wired to React Query for submission,</li>
<li>With SurveyJS, which treats a form as data &mdash; a simple JSON schema &mdash; rather than a component tree.</li>
</ol>
<p>Same requirements, same conditional logic, same API call at the end. Then we’ll map exactly what moved and what stayed, and lay out a practical way to decide which model you should use, and when.</p>
<p><strong>The form we’re building:</strong></p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png"></p>
<p>    <img loading="lazy" width="800" height="798" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Multi-step dynamic form" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/building-dynamic-forms-react-next-js/1-dynamic-form.png">Large preview</a>)<br />
    </figcaption></figure>
<p>This form will use a 4-step flow:</p>
<p><strong>Step 1: Details</strong></p>
<ul>
<li>First name (required),</li>
<li>Email (required, valid format).</li>
</ul>
<p><strong>Step 2: Order</strong></p>
<ul>
<li>Unit price,</li>
<li>Quantity,</li>
<li>Tax rate,</li>
<li>Derived:
<ul>
<li>Subtotal,</li>
<li>Tax,</li>
<li>Total.</li>
</ul>
</li>
</ul>
<p><strong>Step 3: Account &amp; Feedback</strong></p>
<ul>
<li>Do you have an account? (Yes/No)
<ul>
<li>If Yes → username + password, both required.</li>
<li>If No → email already collected in step 1.</li>
</ul>
</li>
<li>Satisfaction rating (1–5)
<ul>
<li>If ≥ 4 → ask “What did you like?”</li>
<li>If ≤ 2 → ask “What can we improve?”</li>
</ul>
</li>
</ul>
<p><strong>Step 4: Review</strong></p>
<ul>
<li>Only appears if <code>total &gt;= 100</code></li>
<li>Final submission.</li>
</ul>
<p>This is not extreme. But it’s enough to expose architectural differences.</p>
<h2 id="part-1-component-driven-react-hook-form-zod">Part 1: Component-Driven (React Hook Form + Zod)</h2>
<h3 id="installation">Installation</h3>
<pre><code class="language-bash">npm install react-hook-form zod @hookform/resolvers @tanstack/react-query
</code></pre>
<h3 id="zod-schema">Zod Schema</h3>
<p>Let’s start with the Zod schema, because that’s usually where the shape of the form gets established. For the first two steps &mdash; personal details and order inputs &mdash; everything is straightforward: required strings, numbers with minimums, and an enum. The interesting part starts when you try to express the conditional rules.</p>
<div class="break-out">
<pre><code class="language-typescript">import { z } from "zod";

export const formSchema = z.object({  
  firstName: z.string().min(1, "Required"),  
  email: z.string().email("Invalid email"),  
  price: z.number().min(0),  
  quantity: z.number().min(1),  
  taxRate: z.number(),  
  hasAccount: z.enum(["Yes", "No"]),  
  username: z.string().optional(),  
  password: z.string().optional(),  
  satisfaction: z.number().min(1).max(5),  
  positiveFeedback: z.string().optional(),  
  improvementFeedback: z.string().optional(),  
}).superRefine((data, ctx) =&gt; {  
  if (data.hasAccount === "Yes") {  
    if (!data.username) {  
      ctx.addIssue({ code: "custom", path: ["username"], message: "Required" });  
    }  
    if (!data.password || data.password.length &lt; 6) {  
      ctx.addIssue({ code: "custom", path: ["password"], message: "Min 6 characters" });  
    }  
  }

  if (data.satisfaction &gt;= 4 &amp;&amp; !data.positiveFeedback) {  
    ctx.addIssue({ code: "custom", path: ["positiveFeedback"], message: "Please share what you liked" });  
  }

  if (data.satisfaction &lt;= 2 &amp;&amp; !data.improvementFeedback) {  
    ctx.addIssue({ code: "custom", path: ["improvementFeedback"], message: "Please tell us what to improve" });  
  }  
});

export type FormData = z.infer&lt;typeof formSchema&gt;;
</code></pre>
</div>
<p>Notice that <code>username</code> and <code>password</code> are typed as <code>optional()</code> even though they’re conditionally required because Zod’s type-level schema describes the <em>shape</em> of the object, not the rules governing when fields matter.</p>
<p>The conditional requirement has to live inside <code>superRefine</code>, which runs after the shape is validated and has access to the full object. That separation is not a flaw; it’s just what the tool is designed for: <code>superRefine</code> is where cross-field logic goes when it can’t be expressed in the schema structure itself.</p>
<p>What’s also notable here is what this schema <em>doesn’t</em> express. It has no concept of pages, no concept of which fields are visible at which point, and no concept of navigation. All of that will live somewhere else.</p>
<h3 id="form-component">Form Component</h3>
<div class="break-out">
<pre><code class="language-typescript">import { useForm, useWatch } from "react-hook-form";  
import { zodResolver } from "@hookform/resolvers/zod";  
import { useMutation } from "@tanstack/react-query";  
import { useState, useMemo } from "react";  
import { formSchema, type FormData } from "./schema";

const STEPS = ["details", "order", "account", "review"];

type OrderPayload = FormData &amp; { subtotal: number; tax: number; total: number };

export function RHFMultiStepForm() {  
  const [step, setStep] = useState(0);

  const mutation = useMutation({
    mutationFn: async (payload: OrderPayload) =&gt; {
      const res = await fetch("/api/orders", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });
      if (!res.ok) throw new Error("Failed to submit");
      return res.json();
    },
  });

  const {  
    register,  
    control,  
    handleSubmit,  
    formState: { errors },  
  } = useForm&lt;FormData&gt;({  
    resolver: zodResolver(formSchema),  
    defaultValues: {  
      price: 0,  
      quantity: 1,  
      taxRate: 0.1,  
      satisfaction: 3,  
      hasAccount: "No",  
    },  
  });  

  const price = useWatch({ control, name: "price" });  
  const quantity = useWatch({ control, name: "quantity" });  
  const taxRate = useWatch({ control, name: "taxRate" });  
  const hasAccount = useWatch({ control, name: "hasAccount" });  
  const satisfaction = useWatch({ control, name: "satisfaction" });  

  const subtotal = useMemo(() =&gt; (price ?? 0) &#042; (quantity ?? 1), [price, quantity]);  
  const tax = useMemo(() =&gt; subtotal &#042; (taxRate ?? 0), [subtotal, taxRate]);  
  const total = useMemo(() =&gt; subtotal + tax, [subtotal, tax]);  

  const onSubmit = (data: FormData) =&gt; mutation.mutate({ ...data, subtotal, tax, total });  

  const showSubmit = (step === 2 &amp;&amp; total &lt; 100) || (step === 3 &amp;&amp; total &gt;= 100)

  return (  
    &lt;form onSubmit={handleSubmit(onSubmit)}&gt;  
      {step === 0 &amp;&amp; (  
        &lt;&gt;  
          &lt;input {...register("firstName")} placeholder="First Name" /&gt;  
          &lt;input {...register("email")} placeholder="Email" /&gt;  
        &lt;/&gt;  
      )}

      {step === 1 &amp;&amp; (  
        &lt;&gt;  
          &lt;input type="number" {...register("price", { valueAsNumber: true })} /&gt;  
          &lt;input type="number" {...register("quantity", { valueAsNumber: true })} /&gt;  
          &lt;select {...register("taxRate", { valueAsNumber: true })}&gt;  
            &lt;option value="0.05"&gt;5%&lt;/option&gt;  
            &lt;option value="0.1"&gt;10%&lt;/option&gt;  
            &lt;option value="0.15"&gt;15%&lt;/option&gt;  
          &lt;/select&gt;

          &lt;div&gt;Subtotal: {subtotal}&lt;/div&gt;  
          &lt;div&gt;Tax: {tax}&lt;/div&gt;  
          &lt;div&gt;Total: {total}&lt;/div&gt;  
        &lt;/&gt;  
      )}

      {step === 2 &amp;&amp; (  
        &lt;&gt;  
          &lt;select {...register("hasAccount")}&gt;  
            &lt;option value="Yes"&gt;Yes&lt;/option&gt;  
            &lt;option value="No"&gt;No&lt;/option&gt;  
          &lt;/select&gt;

          {hasAccount === "Yes" &amp;&amp; (  
            &lt;&gt;  
              &lt;input {...register("username")} placeholder="Username" /&gt;  
              &lt;input {...register("password")} placeholder="Password" /&gt;  
            &lt;/&gt;  
          )}

          &lt;input type="number" {...register("satisfaction", { valueAsNumber: true })} /&gt;

          {satisfaction &gt;= 4 &amp;&amp; (  
            &lt;textarea {...register("positiveFeedback")} /&gt;  
          )}

          {satisfaction &lt;= 2 &amp;&amp; (  
            &lt;textarea {...register("improvementFeedback")} /&gt;  
          )}  
        &lt;/&gt;  
      )}

      {step === 3 &amp;&amp; total &gt;= 100 &amp;&amp; &lt;div&gt;Review and submit&lt;/div&gt;}

      &lt;div&gt;  
        {step &gt; 0 &amp;&amp; &lt;button type="button" onClick={() =&gt; setStep(step - 1)}&gt;Back&lt;/button&gt;}  
        {showSubmit ? (  
          &lt;button type="submit" disabled={mutation.isPending}&gt;  
            {mutation.isPending ? "Submitting…" : "Submit"}  
          &lt;/button&gt;  
        ) : step &lt; STEPS.length - 1 ? (  
          &lt;button type="button" onClick={() =&gt; setStep(step + 1)}&gt;Next&lt;/button&gt;  
        ) : null}  
      &lt;/div&gt;  
      {mutation.isError &amp;&amp; &lt;div&gt;Error: {mutation.error.message}&lt;/div&gt;}  
    &lt;/form&gt;  
  );  
}
</code></pre>
</div>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="gbwwmNO" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [SurveyJS-03-RHF [forked]](https://codepen.io/smashingmag/pen/gbwwmNO) by <a href="https://codepen.io/sixthextinction">sixthextinction</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/gbwwmNO">SurveyJS-03-RHF [forked]</a> by <a href="https://codepen.io/sixthextinction">sixthextinction</a>.</figcaption></figure>
<p>There’s quite a lot happening here, and it’s worth slowing down to notice where things ended up.</p>
<ul>
<li>The derived values &mdash; <code>subtotal</code>, <code>tax</code>, <code>total</code> &mdash; are computed in the component via <code>useWatch</code> and <code>useMemo</code> because they depend on live field values and there’s no other natural place for them.</li>
<li>The visibility rules for <code>username</code>, <code>password</code>, <code>positiveFeedback</code>, and <code>improvementFeedback</code> live in JSX as inline conditionals.</li>
<li>The step-skipping logic &mdash; the review page only appearing when <code>total &gt;= 100</code> &mdash; is embedded into the <code>showSubmit</code> variable and the render condition on step 3.</li>
<li>Navigation itself is just a <code>useState</code> counter that we’re manually incrementing.</li>
<li>React Query handles retries, caching, and invalidation. The form just calls <code>mutation.mutate</code> with validated data.</li>
</ul>
<p>None of this is <em>wrong,</em> per se. This is still idiomatic React, and the component is quite performant thanks to how RHF isolates re-renders.</p>
<p>But if you were to hand this to someone who hadn’t written it and ask them to explain <em>under what conditions the review page appears</em>, they’d have to trace through <code>showSubmit</code>, the step 3 render condition, and the nav button logic &mdash; three separate places &mdash; to reconstruct a rule that could have been stated in one line.</p>
<p><strong>The form works, yes, but the behavior isn’t really inspectable as a system.</strong> It has to be executed mentally.</p>
<p>More importantly, changing it requires engineering involvement. Even a small tweak, like adjusting when the review step shows up, means editing the component, updating validation, opening a pull request, waiting for review, and deploying again.</p>
<h2 id="part-2-schema-driven-surveyjs">Part 2: Schema-Driven (SurveyJS)</h2>
<p>Now let’s build the same flow using a schema.</p>
<h3 id="installation-1">Installation</h3>
<pre><code class="language-bash">npm install survey-core survey-react-ui @tanstack/react-query
</code></pre>
<ul>
<li><code>survey-core</code><br />
The MIT-licensed platform-independent runtime engine that powers SurveyJS’s form rendering &mdash; the part we care about here. It takes a JSON schema, builds an internal model from it, and handles everything that would otherwise live in your React component: evaluating visibility expressions, computing derived values, managing page state, tracking validation, and deciding what “complete” means given which pages were actually shown.</li>
<li><code>survey-react-ui</code><br />
The UI / rendering layer that connects that model to React. It’s essentially a <code>&lt;Survey model={model} /&gt;</code> component that re-renders whenever the engine’s state changes. SurveyJS UI libraries are also available for <a href="https://www.npmjs.com/package/survey-angular">Angular</a>, <a href="https://www.npmjs.com/package/survey-vue3-ui">Vue3</a>, and many other frameworks.</li>
</ul>
<p>Together, they give you a fully functional, multi-page form runtime without writing a single line of control flow.</p>
<p>The schema format itself is, as said before, just a JSON &mdash; no DSL or anything proprietary. You can inline it, import it from a file, fetch it from an API, or store it in a database column and hydrate it at runtime.</p>
<h3 id="the-same-form-as-data">The Same Form, As Data</h3>
<p>Here’s the same form, this time expressed as a JSON object. The schema defines everything: structure, validation, visibility rules, derived calculations, page navigation &mdash; and hands it to a <code>Model</code> that evaluates it at runtime. Here’s what that looks like in full:</p>
<div class="break-out">
<pre><code class="language-javascript">export const surveySchema = {  
  title: "Order Flow",  
  showProgressBar: "top",  
  pages: [  
    {  
      name: "details",  
      elements: [  
        { type: "text", name: "firstName", isRequired: true },  
        { type: "text", name: "email", inputType: "email", isRequired: true, validators: [{ type: "email", text: "Invalid email" }] }  
      ]  
    },  
    {  
      name: "order",  
      elements: [  
        { type: "text", name: "price", inputType: "number", defaultValue: 0 },  
        { type: "text", name: "quantity", inputType: "number", defaultValue: 1 },  
        {  
          type: "dropdown",  
          name: "taxRate",  
          defaultValue: 0.1,  
          choices: [  
            { value: 0.05, text: "5%" },  
            { value: 0.1, text: "10%" },  
            { value: 0.15, text: "15%" }  
          ]  
        },  
        {  
          type: "expression",  
          name: "subtotal",  
          expression: "{price} * {quantity}"  
        },  
        {  
          type: "expression",  
          name: "tax",  
          expression: "{subtotal} * {taxRate}"  
        },  
        {  
          type: "expression",  
          name: "total",  
          expression: "{subtotal} + {tax}"  
        }  
      ]  
    },  
    {  
      name: "account",  
      elements: [  
        {  
          type: "radiogroup",  
          name: "hasAccount",  
          choices: ["Yes", "No"]  
        },  
        {  
          type: "text",  
          name: "username",  
          visibleIf: "{hasAccount} = 'Yes'",  
          isRequired: true  
        },  
        {  
          type: "text",  
          name: "password",  
          inputType: "password",  
          visibleIf: "{hasAccount} = 'Yes'",  
          isRequired: true,  
          validators: [{ type: "text", minLength: 6, text: "Min 6 characters" }]  
        },  
        {  
          type: "rating",  
          name: "satisfaction",  
          rateMin: 1,  
          rateMax: 5  
        },  
        {  
          type: "comment",  
          name: "positiveFeedback",  
          visibleIf: "{satisfaction} &gt;= 4"  
        },  
        {  
          type: "comment",  
          name: "improvementFeedback",  
          visibleIf: "{satisfaction} &lt;= 2"  
        }  
      ]  
    },  
    {  
      name: "review",  
      visibleIf: "{total} &gt;= 100",  
      elements: []  
    }  
  ]  
};
</code></pre>
</div>
<p>Compare this to the RHF version for a moment.</p>
<ul>
<li>The <code>superRefine</code> block that conditionally required <code>username</code> and <code>password</code> is gone. <code>visibleIf: &quot;{hasAccount} = 'Yes'&quot;</code> combined with <code>isRequired: true</code> handles both concerns together, on the field itself, where you&rsquo;d expect to find them.</li>
<li>The <code>useWatch</code> + <code>useMemo</code> chain that computed <code>subtotal</code>, <code>tax</code>, and <code>total</code> is replaced by three <code>expression</code> fields that reference each other by name.</li>
<li>The review page condition, which in the RHF version was reconstructable only by tracing through <code>showSubmit</code>, the step 3 render branch.</li>
<li>And finally, the nav button logic is a single <code>visibleIf</code> property on the page object.</li>
</ul>
<p>The same logic is there. It’s just that the schema gives it a place to live where it’s visible in isolation, rather than spread across the component.</p>
<p>Also, note that the schema uses <code>type: 'expression'</code> for subtotal, tax, and total. <a href="https://surveyjs.io/form-library/documentation/api-reference/expression-model">Expression</a> is read-only and used mainly to display calculated values. SurveyJS also supports <code>type: 'html'</code> for static content, but for calculated values, <code>expression</code> is the right choice.</p>
<p>Now for the React side.</p>
<h3 id="rendering-and-submission">Rendering And Submission</h3>
<p>Very simple. Wire <code>onComplete</code> to your API the same way &mdash; via <code>useMutation</code> or plain <code>fetch</code>:</p>
<div class="break-out">
<pre><code class="language-javascript">import { useState, useEffect, useRef } from "react";  
import { useMutation } from "@tanstack/react-query";  
import { Model } from "survey-core";  
import { Survey } from "survey-react-ui";  
import "survey-core/survey-core.css";

export function SurveyForm() {  
  const [model] = useState(() =&gt; new Model(surveySchema));

  const mutation = useMutation({
    mutationFn: async (data) =&gt; {
      const res = await fetch("/api/orders", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(data),
      });
      if (!res.ok) throw new Error("Failed to submit");
      return res.json();
    },
  });

  const mutationRef = useRef(mutation);
  mutationRef.current = mutation;
  useEffect(() =&gt; {  
    const handler = (sender) =&gt; mutationRef.current.mutate(sender.data);  
    model.onComplete.add(handler);  
    return () =&gt; model.onComplete.remove(handler);  
  }, [model]); // ref avoids re-registering handler every render (mutation object identity changes)

  return (
    &lt;&gt;
      &lt;Survey model={model} /&gt;  
      {mutation.isError &amp;&amp; &lt;div&gt;Error: {mutation.error.message}&lt;/div&gt;}
    &lt;/&gt;
  );
}
</code></pre>
</div>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="emddWNV" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [SurveyJS-03-SurveyJS [forked]](https://codepen.io/smashingmag/pen/emddWNV) by <a href="https://codepen.io/sixthextinction">sixthextinction</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/emddWNV">SurveyJS-03-SurveyJS [forked]</a> by <a href="https://codepen.io/sixthextinction">sixthextinction</a>.</figcaption></figure>
<ul>
<li><code>onComplete</code> fires when the user reaches the end of the last <em>visible</em> page. So if <code>total</code> never crosses 100 and the review page is skipped, it still fires correctly because SurveyJS evaluates visibility before deciding what “last page” means.</li>
<li>Then, <code>sender.data</code> contains all answers along with the calculated values (<code>subtotal</code>, <code>tax</code>, <code>total</code>) as first-class fields, so the API payload is identical to what the RHF version assembled manually in <code>onSubmit</code>.</li>
<li>The <code>mutationRef</code> pattern is the same one you’d reach for anywhere you need a stable event handler over a value that changes on every render &mdash; nothing SurveyJS-specific about it.</li>
</ul>
<p>The React component no longer contains any business logic at all. There’s no <code>useWatch</code>, no conditional JSX, no step counter, no <code>useMemo</code> chain, no <code>superRefine</code>. React is doing what it’s actually good at: rendering a component and wiring it to an API call.</p>
<h2 id="what-moved-out-of-react">What Moved Out Of React?</h2>
<table class="tablesaw break-out">
<thead>
<tr>
<th>Concern</th>
<th>RHF Stack</th>
<th>SurveyJS</th>
</tr>
</thead>
<tbody>
<tr>
<td>Visibility</td>
<td>JSX branches</td>
<td><code>visibleIf</code></td>
</tr>
<tr>
<td>Derived values</td>
<td><code>useWatch</code> / <code>useMemo</code></td>
<td><code>expression</code></td>
</tr>
<tr>
<td>Cross-field rules</td>
<td><code>superRefine</code></td>
<td>Schema conditions</td>
</tr>
<tr>
<td>Navigation</td>
<td><code>step</code> state</td>
<td>Page <code>visibleIf</code></td>
</tr>
<tr>
<td>Rule location</td>
<td>Distributed across files</td>
<td>Centralized in the schema</td>
</tr>
</tbody>
</table>
<p>What stays in React is layout, styling, submission wiring, and app integration, which is to say, <strong>the things React is actually designed for</strong>.</p>
<p>Everything else moved into the schema, and because the schema is just a JSON object, it can be stored in a database, versioned independently of your application code, or edited through internal tooling without requiring a deploy.</p>
<p>A product manager who needs to change the threshold that triggers the review page can do that without touching the component. That’s a meaningful operational difference for teams where form behavior evolves frequently and isn’t always driven by engineers.</p>
<h2 id="when-to-use-each-approach">When To Use Each Approach?</h2>
<p>Here’s a good rule of thumb that works for me: <strong>imagine deleting the form entirely</strong>. What would you lose?</p>
<ul>
<li>If it’s screens, you want component-driven forms.</li>
<li>If it’s business logic, like thresholds, branching rules, and conditional requirements that encode real decisions, you want a schema engine.</li>
</ul>
<p>Similarly, if the changes coming your way are mostly about labels, fields, and layout, RHF will serve you fine. If they’re about conditions, outcomes, and rules that your ops or legal team might need to adjust on a Tuesday afternoon without filing a ticket, the schema model with SurveyJS is the more honest fit.</p>
<p><strong>These two approaches are not really in competition with each other.</strong> They address different classes of problems, and the mistake worth avoiding is mismatching the abstraction to the weight of the logic &mdash; treating a rule system like a component because that’s the familiar tool, or reaching for a policy engine because a form grew to three steps and acquired a conditional field.</p>
<p>The form we built here sits near the boundary deliberately, complex enough to expose the difference but not so extreme that the comparison feels rigged. Most real forms that have gotten unwieldy in your codebase probably sit near that same boundary, and the question is usually just whether anyone has named what they actually are.</p>
<p><strong>Use React Hook Form + Zod when:</strong></p>
<ul>
<li>Forms are CRUD-oriented;</li>
<li>Logic is shallow and UI-driven;</li>
<li>Engineers own all behavior;</li>
<li>Backend remains the source of truth.</li>
</ul>
<p><strong>Use SurveyJS when:</strong></p>
<ul>
<li>Forms encode business decisions;</li>
<li>Rules evolve independently of UI;</li>
<li>Logic must be visible, auditable, or versioned;</li>
<li>Non-engineers influence behavior;</li>
<li>The same form must run across multiple frontends.</li>
</ul>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2026/03/10/building-dynamic-forms-in-react-and-next-js/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>JavaScript For Everyone: Iterators</title>
		<link>http://computercoursesonline.com/index.php/2025/10/27/javascript-for-everyone-iterators/</link>
					<comments>http://computercoursesonline.com/index.php/2025/10/27/javascript-for-everyone-iterators/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Mon, 27 Oct 2025 13:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=1102</guid>

					<description><![CDATA[JavaScript For Everyone: Iterators JavaScript For Everyone: Iterators Mat Marquis 2025-10-27T13:00:00+00:00 2025-10-30T20:33:06+00:00 Hey, I’m Mat, but “Wilto” works too &#8212; I’m here to teach you JavaScript. Well, not here-here; technically, I’m over at Piccalil.li’s JavaScript for Everyone course to teach you JavaScript. The following is an excerpt from the Iterables and Iterators module: the lesson...]]></description>
										<content:encoded><![CDATA[<p>              <title>JavaScript For Everyone: Iterators</title></p>
<article>
<header>
<h1>JavaScript For Everyone: Iterators</h1>
<address>Mat Marquis</address>
<p>                  2025-10-27T13:00:00+00:00<br />
                  2025-10-30T20:33:06+00:00<br />
                </header>
<p>Hey, I’m Mat, but “Wilto” works too &mdash; I’m here to teach you JavaScript. Well, not <em>here</em>-here; technically, I’m over at <a href="https://piccalil.li/javascript-for-everyone">Piccalil.li’s <em>JavaScript for Everyone</em></a> course to teach you JavaScript. The following is an excerpt from the <strong>Iterables and Iterators</strong> module: the lesson on Iterators.</p>
<p>Iterators are one of JavaScript’s more linguistically confusing topics, sailing <em>easily</em> over what is already a pretty high bar. There are <em>iterables</em> &mdash; array, Set, Map, and string &mdash; all of which follow the <strong>iterable protocol</strong>. To follow said protocol, an object must implement the <strong>iterable interface</strong>. In practice, that means that the object needs to include a <code>[Symbol.iterator]()</code> method somewhere in its prototype chain. Iterable protocol is one of two <strong>iteration protocols</strong>. The other iteration protocol is the <strong>iterator protocol</strong>.</p>
<p>See what I mean about this being linguistically fraught? Iterables implement the iterable iteration interface, and iterators implement the iterator iteration interface! If you can say that five times fast, then you’ve pretty much got the gist of it; easy-peasy, right?</p>
<p>No, listen, by the time you reach the end of this lesson, I promise it won’t be half as confusing as it might sound, especially with the context you’ll have from the lessons that precede it.</p>
<p>An <strong>iterable</strong> object follows the iterable protocol, which just means that the object has a conventional method for making iterators. The elements that it contains can be looped over with <code>for</code>…<code>of</code>.</p>
<p>An <strong>iterator</strong> object follows the iterator protocol, and the elements it contains can be accessed <em>sequentially</em>, one at a time.</p>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="/printed-books/touch-design-for-mobile-interfaces/">Touch Design for Mobile Interfaces</a></strong>, Steven Hoober’s brand-new guide on <strong>designing for mobile</strong> with proven, universal, human-centric guidelines. <strong>400 pages</strong>, jam-packed with in-depth user research and <strong>best practices</strong>.</p>
<p><a data-instant href="https://www.smashingmagazine.com/printed-books/touch-design-for-mobile-interfaces/" class="btn btn--green btn--large">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/touch-design-for-mobile-interfaces/" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="480" height="697" data-src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/b14658fc-bb2d-41a6-8d1a-70eaaf1b8ec8/touch-design-book-shop-opt.png"></p>
</div>
<p></a>
</div>
</aside>
</div>
<p>To <em>reiterate</em> &mdash; a play on words for which I do not forgive myself, nor expect you to forgive me &mdash; an <strong>iterator</strong> object follows iterator protocol, and the elements it contains can be accessed <em>sequentially</em>, one at a time. Iterator protocol defines a standard way to produce a sequence of values, and optionally <code>return</code> a value once all possible values have been generated.</p>
<p>In order to follow the iterator protocol, an object has to &mdash; you guessed it &mdash; implement the <strong>iterator interface</strong>. In practice, that once again means that a certain method has to be available somewhere on the object&rsquo;s prototype chain. In this case, it’s the <code>next()</code> method that advances through the elements it contains, one at a time, and returns an object each time that method is called.</p>
<p>In order to meet the iterator interface criteria, the returned object must contain two properties with specific keys: one with the key <code>value</code>, representing the value of the current element, and one with the key <code>done</code>, a Boolean value that tells us if the iterator has advanced beyond the final element in the data structure. That’s not an awkward phrasing the editorial team let slip through: the value of that <code>done</code> property is <code>true</code> only when a call to <code>next()</code> results in an attempt to access an element <em>beyond</em> the final element in the iterator, not upon accessing the final element in the iterator. Again, a lot in print, but it’ll make more sense when you see it in action.</p>
<p>You’ve seen an example of a built-in iterator before, albeit briefly:</p>
<pre><code class="language-jsx">const theMap = new Map([ [ "aKey", "A value." ] ]);

console.log( theMap.keys() );
// Result: Map Iterator { constructor: Iterator() }
</code></pre>
<p>That’s right: while a Map object itself is an iterable, Map’s built-in methods <code>keys()</code>, <code>values()</code>, and <code>entries()</code> all return Iterator objects. You’ll also remember that I looped through those using <code>forEach</code> (a relatively recent addition to the language). Used that way, an iterator is indistinguishable from an iterable:</p>
<pre><code class="language-jsx">const theMap = new Map([ [ "key", "value " ] ]);

theMap.keys().forEach( thing =&gt; {
  console.log( thing );
});
// Result: key
</code></pre>
<p>All iterators are iterable; they all implement the iterable interface:</p>
<pre><code class="language-jsx">const theMap = new Map([ [ "key", "value " ] ]);

theMap.keys()[ Symbol.iterator ];
// Result: function Symbol.iterator()
</code></pre>
<p>And if you’re angry about the increasing blurriness of the line between iterators and iterables, wait until you get a load of this “top ten anime betrayals” video candidate: I’m going to demonstrate how to interact with an iterator by using an array.</p>
<p>“BOO,” you surely cry, having been so betrayed by one of your oldest and most indexed friends. “Array is an itera<em>ble</em>, not an itera<em>tor</em>!” You are both right to yell at me in general, and right about array in specific &mdash; an array <em>is</em> an iterable, not an iterator. In fact, while all iterators are iterable, none of the built-in iterables are iterators.</p>
<p>However, when you call that <code>[ Symbol.iterator ]()</code> method &mdash; the one that defines an object as an iterable &mdash; it returns an iterator object created from an iterable data structure:</p>
<pre><code class="language-jsx">const theIterable = [ true, false ];
const theIterator = theIterable[ Symbol.iterator ]();

theIterable;
// Result: Array [ true, false ]

theIterator;
// Result: Array Iterator { constructor: Iterator() }
</code></pre>
<p>The same goes for Set, Map, and &mdash; yes &mdash; even strings:</p>
<pre><code class="language-jsx">const theIterable = "A string."
const theIterator = theIterable[ Symbol.iterator ]();

theIterator;
// Result: String Iterator { constructor: Iterator() }
</code></pre>
<p>What we’re doing here manually &mdash; creating an iterator from an iterable using <code>%Symbol.iterator%</code> &mdash; is precisely how iterable objects work internally, and why they have to implement <code>%Symbol.iterator%</code> in order to <em>be</em> iterables. Any time you loop through an array, you’re actually looping through an iterator created from that Array. All built-in iterators <em>are</em> iterable. All built-in iterables can be used to <em>create</em> iterators.</p>
<p>Alternately &mdash; <em>preferably</em>, even, since it doesn’t require you to graze up against <code>%Symbol.iterator%</code> directly &mdash; you can use the built-in <code>Iterator.from()</code> method to create an iterator object from any iterable:</p>
<pre><code class="language-jsx">const theIterator = Iterator.from([ true, false ]);

theIterator;
// Result: Array Iterator { constructor: Iterator() }
</code></pre>
<p>You remember how I mentioned that an iterator has to provide a <code>next()</code> method (that returns a very specific Object)? Calling that <code>next()</code> method steps through the elements that the iterator contains one at a time, with each call returning an instance of that Object:</p>
<pre><code class="language-jsx">const theIterator = Iterator.from([ 1, 2, 3 ]);

theIterator.next();
// Result: Object { value: 1, done: false }

theIterator.next();
// Result: Object { value: 2, done: false }

theIterator.next();
// Result: Object { value: 3, done: false }

theIterator.next();
// Result: Object { value: undefined, done: true }
</code></pre>
<p>You can think of this as a more controlled form of traversal than the traditional “wind it up and watch it go” <code>for</code> loops you’re probably used to &mdash; a method of accessing elements one step at a time, as-needed. Granted, you don’t <em>have</em> to step through an iterator in this way, since they have their very own <code>Iterator.forEach</code> method, which works exactly like you would expect &mdash; to a point:</p>
<pre><code class="language-jsx">const theIterator = Iterator.from([ true, false ]);

theIterator.forEach( element =&gt; console.log( element ) );
/&#042; Result:
true
false
&#042;/
</code></pre>
<p>But there’s another big difference between iterables and iterators that we haven’t touched on yet, and for my money, it actually goes a long way toward making <em>linguistic</em> sense of the two. You might need to humor me for a little bit here, though.</p>
<p>See, an iterable object is an object that is iterable. No, listen, stay with me: you can iterate over an Array, and when you’re done doing so, you can still iterate over that Array. It is, by definition, an object that can be iterated over; it is the essential nature of an iterable to be iterable:</p>
<pre><code class="language-jsx">const theIterable = [ 1, 2 ];

theIterable.forEach( el =&gt; {
  console.log( el );
});
/&#042; Result:
1
2
&#042;/

theIterable.forEach( el =&gt; {
  console.log( el );
});
/&#042; Result:
1
2
&#042;/
</code></pre>
<p>In a way, an iterator object represents the singular <em>act</em> of iteration. Internal to an iterable, it is the mechanism by which the iterable is iterated over, each time that iteration is performed. As a stand-alone iterator object &mdash; whether you step through it using the <code>next</code> method or loop over its elements using <code>forEach</code> &mdash; once iterated over, that iterator is <em>past tense</em>; it is <em>iterated</em>. Because they maintain an internal state, the essential nature of an iterator is to be iterated over, singular:</p>
<pre><code class="language-jsx">const theIterator = Iterator.from([ 1, 2 ]);

theIterator.next();
// Result: Object { value: 1, done: false }

theIterator.next();
// Result: Object { value: 2, done: false }

theIterator.next();
// Result: Object { value: undefined, done: true }

theIterator.forEach( el =&gt; console.log( el ) );
// Result: undefined
</code></pre>
<p>That makes for neat work when you&rsquo;re using the Iterator constructor’s built-in methods to, say, filter or extract part of an Iterator object:</p>
<div class="break-out">
<pre><code class="language-jsx">const theIterator = Iterator.from([ "First", "Second", "Third" ]);

// Take the first two values from `theIterator`:
theIterator.take( 2 ).forEach( el =&gt; {
  console.log( el );
});
/&#042; Result:
"First"
"Second"
&#042;/

// theIterator now only contains anything left over after the above operation is complete:
theIterator.next();
// Result: Object { value: "Third", done: false }
</code></pre>
</div>
<p>Once you reach the end of an iterator, the act of iterating over it is complete. Iterated. Past-tense.</p>
<p>And so too is your time in this lesson, you might be relieved to hear. I know this was kind of a rough one, but the good news is: this course is iterable, not an iterator. This step in your iteration through it &mdash; this lesson &mdash; may be over, but the essential nature of this course is that you can iterate through it again. Don’t worry about committing all of this to memory right now &mdash; you can come back and revisit this lesson anytime.</p>
<div class="partners__lead-place"></div>
<h2 id="conclusion">Conclusion</h2>
<p>I stand by what I wrote there, unsurprising as that probably is: this lesson is a tricky one, but listen, <em>you got this</em>. <a href="https://piccalil.li/javascript-for-everyone">JavaScript for Everyone</a> is designed to take you inside JavaScript’s head. Once you’ve started seeing how the gears mesh &mdash; seen the fingerprints left behind by the people who built the language, and the good, bad, and sometimes baffling decisions that went into that &mdash; no <em>itera-</em>, whether <em>-ble</em> or <em>-tor</em> will be able to stand in your way.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://piccalil.li/javascript-for-everyone"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Javascript for everyone course announcement" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/javascript-for-everyone-iterators/1-javascript-for-everyone.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://piccalil.li/javascript-for-everyone">JavaScript for Everyone</a> is now available and the launch price runs until midnight, October 28. Save £60 off the full price of £249 and get it for £189! (<a href="https://files.smashing.media/articles/javascript-for-everyone-iterators/1-javascript-for-everyone.png">Large preview</a>)<br />
    </figcaption></figure>
<p>My goal is to teach you the <em>deep magic</em> &mdash; the <em>how</em> and the <em>why</em> of JavaScript, using the syntaxes you’re most likely to encounter in your day-to-day work, at your pace and on your terms. If you’re new to the language, you’ll walk away from this course with a foundational understanding of JavaScript worth hundreds of hours of trial-and-error. If you’re a junior developer, you’ll finish this course with a depth of knowledge to rival any senior.</p>
<p>I hope to see you there.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/10/27/javascript-for-everyone-iterators/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>The Power Of The Intl API: A Definitive Guide To Browser-Native Internationalization</title>
		<link>http://computercoursesonline.com/index.php/2025/08/08/the-power-of-the-intl-api-a-definitive-guide-to-browser-native-internationalization/</link>
					<comments>http://computercoursesonline.com/index.php/2025/08/08/the-power-of-the-intl-api-a-definitive-guide-to-browser-native-internationalization/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Fri, 08 Aug 2025 10:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=1025</guid>

					<description><![CDATA[The Power Of The &#60;code&#62;Intl&#60;/code&#62; API: A Definitive Guide To Browser-Native Internationalization The Power Of The &#60;code&#62;Intl&#60;/code&#62; API: A Definitive Guide To Browser-Native Internationalization Fuqiao Xue 2025-08-08T10:00:00+00:00 2025-08-14T20:32:44+00:00 It’s a common misconception that internationalization (i18n) is simply about translating text. While crucial, translation is merely one facet. One of the complexities lies in adapting information...]]></description>
										<content:encoded><![CDATA[<p>              <title>The Power Of The &lt;code&gt;Intl&lt;/code&gt; API: A Definitive Guide To Browser-Native Internationalization</title></p>
<article>
<header>
<h1>The Power Of The &lt;code&gt;Intl&lt;/code&gt; API: A Definitive Guide To Browser-Native Internationalization</h1>
<address>Fuqiao Xue</address>
<p>                  2025-08-08T10:00:00+00:00<br />
                  2025-08-14T20:32:44+00:00<br />
                </header>
<p>It’s a common misconception that internationalization (i18n) is simply about translating text. While crucial, translation is merely one facet. One of the complexities lies in <strong>adapting information for diverse cultural expectations</strong>: How do you display a date in Japan versus Germany? What’s the correct way to pluralize an item in Arabic versus English? How do you sort a list of names in various languages?</p>
<p>Many developers have relied on weighty third-party libraries or, worse, custom-built formatting functions to tackle these challenges. These solutions, while functional, often come with significant overhead: increased bundle size, potential performance bottlenecks, and the constant struggle to keep up with evolving linguistic rules and locale data.</p>
<p>Enter the <strong>ECMAScript Internationalization API</strong>, more commonly known as the <code>Intl</code> object. This silent powerhouse, built directly into modern JavaScript environments, is an often-underestimated, yet incredibly <strong>potent, native, performant, and standards-compliant solution</strong> for handling data internationalization. It’s a testament to the web’s commitment to being <em>worldwide</em>, providing a unified and efficient way to format numbers, dates, lists, and more, according to specific locales.</p>
<h2 id="intl-and-locales-more-than-just-language-codes"><code>Intl</code> And Locales: More Than Just Language Codes</h2>
<p>At the heart of <code>Intl</code> lies the concept of a <strong>locale</strong>. A locale is far more than just a two-letter language code (like <code>en</code> for English or <code>es</code> for Spanish). It encapsulates the complete context needed to present information appropriately for a specific cultural group. This includes:</p>
<ul>
<li><strong>Language</strong>: The primary linguistic medium (e.g., <code>en</code>, <code>es</code>, <code>fr</code>).</li>
<li><strong>Script</strong>: The script (e.g., <code>Latn</code> for Latin, <code>Cyrl</code> for Cyrillic). For example, <code>zh-Hans</code> for Simplified Chinese, vs. <code>zh-Hant</code> for Traditional Chinese.</li>
<li><strong>Region</strong>: The geographic area (e.g., <code>US</code> for United States, <code>GB</code> for Great Britain, <code>DE</code> for Germany). This is crucial for variations within the same language, such as <code>en-US</code> vs. <code>en-GB</code>, which differ in date, time, and number formatting.</li>
<li><strong>Preferences/Variants</strong>: Further specific cultural or linguistic preferences. See <a href="https://www.w3.org/International/questions/qa-choosing-language-tags">“Choosing a Language Tag”</a> from W3C for more information.</li>
</ul>
<p>Typically, you’ll want to choose the locale according to the language of the web page. This can be determined from the <code>lang</code> attribute:</p>
<div class="break-out">
<pre><code class="language-javascript">// Get the page's language from the HTML lang attribute
const pageLocale = document.documentElement.lang || 'en-US'; // Fallback to 'en-US'
</code></pre>
</div>
<p>Occasionally, you may want to override the page locale with a specific locale, such as when displaying content in multiple languages:</p>
<div class="break-out">
<pre><code class="language-javascript">// Force a specific locale regardless of page language
const tutorialFormatter = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' });

console.log(`Chinese example: ${tutorialFormatter.format(199.99)}`); // Output: ¥199.99
</code></pre>
</div>
<p>In some cases, you might want to use the user’s preferred language:</p>
<div class="break-out">
<pre><code class="language-javascript">// Use the user's preferred language
const browserLocale = navigator.language || 'ja-JP';

const formatter = new Intl.NumberFormat(browserLocale, { style: 'currency', currency: 'JPY' });
</code></pre>
</div>
<p>When you instantiate an <code>Intl</code> formatter, you can optionally pass one or more locale strings. The API will then select the most appropriate locale based on availability and preference.</p>
<h2 id="core-formatting-powerhouses">Core Formatting Powerhouses</h2>
<p>The <code>Intl</code> object exposes several constructors, each for a specific formatting task. Let’s delve into the most frequently used ones, along with some powerful, often-overlooked gems.</p>
<h3 id="1-intl-datetimeformat-dates-and-times-globally">1. <code>Intl.DateTimeFormat</code>: Dates and Times, Globally</h3>
<p>Formatting dates and times is a classic i18n problem. Should it be MM/DD/YYYY or DD.MM.YYYY? Should the month be a number or a full word? <code>Intl.DateTimeFormat</code> handles all this with ease.</p>
<div class="break-out">
<pre><code class="language-javascript">const date = new Date(2025, 6, 27, 14, 30, 0); // June 27, 2025, 2:30 PM

// Specific locale and options (e.g., long date, short time)
const options = {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: 'numeric',
  minute: 'numeric',
  timeZoneName: 'shortOffset' // e.g., "GMT+8"
};

console.log(new Intl.DateTimeFormat('en-US', options).format(date));

// "Friday, June 27, 2025 at 2:30 PM GMT+8"
console.log(new Intl.DateTimeFormat('de-DE', options).format(date));

// "Freitag, 27. Juni 2025 um 14:30 GMT+8"

// Using dateStyle and timeStyle for common patterns
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' }).format(date));

// "Friday 27 June 2025 at 14:30"

console.log(new Intl.DateTimeFormat('ja-JP', { dateStyle: 'long', timeStyle: 'short' }).format(date));

// "2025年6月27日 14:30"
</code></pre>
</div>
<p>The flexibility of <code>options</code> for <code>DateTimeFormat</code> is vast, allowing control over year, month, day, weekday, hour, minute, second, time zone, and more.</p>
<h3 id="2-intl-numberformat-numbers-with-cultural-nuance">2. <code>Intl.NumberFormat</code>: Numbers With Cultural Nuance</h3>
<p>Beyond simple decimal places, numbers require careful handling: thousands separators, decimal markers, currency symbols, and percentage signs vary wildly across locales.</p>
<div class="break-out">
<pre><code class="language-javascript">const price = 123456.789;

// Currency formatting
console.log(new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price));

// "$123,456.79" (auto-rounds)

console.log(new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price));

// "123.456,79 €"

// Units
console.log(new Intl.NumberFormat('en-US', { style: 'unit', unit: 'meter', unitDisplay: 'long' }).format(100));

// "100 meters"

console.log(new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'kilogram', unitDisplay: 'short' }).format(5.5));

// "5,5 kg"
</code></pre>
</div>
<p>Options like <code>minimumFractionDigits</code>, <code>maximumFractionDigits</code>, and <code>notation</code> (e.g., <code>scientific</code>, <code>compact</code>) provide even finer control.</p>
<h3 id="3-intl-listformat-natural-language-lists">3. <code>Intl.ListFormat</code>: Natural Language Lists</h3>
<p>Presenting lists of items is surprisingly tricky. English uses “and” for conjunction and “or” for disjunction. Many languages have different conjunctions, and some require specific punctuation.</p>
<p>This API simplifies a task that would otherwise require complex conditional logic:</p>
<div class="break-out">
<pre><code class="language-javascript">const items = ['apples', 'oranges', 'bananas'];

// Conjunction ("and") list
console.log(new Intl.ListFormat('en-US', { type: 'conjunction' }).format(items));

// "apples, oranges, and bananas"

console.log(new Intl.ListFormat('de-DE', { type: 'conjunction' }).format(items));

// "Äpfel, Orangen und Bananen"

// Disjunction ("or") list
console.log(new Intl.ListFormat('en-US', { type: 'disjunction' }).format(items));

// "apples, oranges, or bananas"

console.log(new Intl.ListFormat('fr-FR', { type: 'disjunction' }).format(items));

// "apples, oranges ou bananas"
</code></pre>
</div>
<h3 id="4-intl-relativetimeformat-human-friendly-timestamps">4. <code>Intl.RelativeTimeFormat</code>: Human-Friendly Timestamps</h3>
<p>Displaying “2 days ago” or “in 3 months” is common in UI, but localizing these phrases accurately requires extensive data. <code>Intl.RelativeTimeFormat</code> automates this.</p>
<div class="break-out">
<pre><code class="language-javascript">const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });

console.log(rtf.format(-1, 'day'));    // "yesterday"
console.log(rtf.format(1, 'day'));     // "tomorrow"
console.log(rtf.format(-7, 'day'));    // "7 days ago"
console.log(rtf.format(3, 'month'));   // "in 3 months"
console.log(rtf.format(-2, 'year'));   // "2 years ago"

// French example:
const frRtf = new Intl.RelativeTimeFormat('fr-FR', { numeric: 'auto', style: 'long' });

console.log(frRtf.format(-1, 'day'));    // "hier"
console.log(frRtf.format(1, 'day'));     // "demain"
console.log(frRtf.format(-7, 'day'));    // "il y a 7 jours"
console.log(frRtf.format(3, 'month'));   // "dans 3 mois"
</code></pre>
</div>
<p>The <code>numeric: 'always'</code> option would force “1 day ago” instead of “yesterday”.</p>
<h3 id="5-intl-pluralrules-mastering-pluralization">5. <code>Intl.PluralRules</code>: Mastering Pluralization</h3>
<p>This is arguably one of the most critical aspects of i18n. Different languages have vastly different pluralization rules (e.g., English has singular/plural, Arabic has zero, one, two, many&hellip;). <code>Intl.PluralRules</code> allows you to determine the “plural category” for a given number in a specific locale.</p>
<pre><code class="language-javascript">const prEn = new Intl.PluralRules('en-US');

console.log(prEn.select(0));    // "other" (for "0 items")
console.log(prEn.select(1));    // "one"   (for "1 item")
console.log(prEn.select(2));    // "other" (for "2 items")

const prAr = new Intl.PluralRules('ar-EG');

console.log(prAr.select(0));    // "zero"
console.log(prAr.select(1));    // "one"
console.log(prAr.select(2));    // "two"
console.log(prAr.select(10));   // "few"
console.log(prAr.select(100));  // "other"
</code></pre>
<p>This API doesn’t pluralize text directly, but it provides the essential classification needed to select the correct translation string from your message bundles. For example, if you have message keys like <code>item.one</code>, <code>item.other</code>, you’d use <code>pr.select(count)</code> to pick the right one.</p>
<h3 id="6-intl-displaynames-localized-names-for-everything">6. <code>Intl.DisplayNames</code>: Localized Names For Everything</h3>
<p>Need to display the name of a language, a region, or a script in the user’s preferred language? <code>Intl.DisplayNames</code> is your comprehensive solution.</p>
<div class="break-out">
<pre><code class="language-javascript">// Display language names in English
const langNamesEn = new Intl.DisplayNames(['en'], { type: 'language' });

console.log(langNamesEn.of('fr'));      // "French"
console.log(langNamesEn.of('es-MX'));   // "Mexican Spanish"

// Display language names in French
const langNamesFr = new Intl.DisplayNames(['fr'], { type: 'language' });

console.log(langNamesFr.of('en'));      // "anglais"
console.log(langNamesFr.of('zh-Hans')); // "chinois (simplifié)"

// Display region names
const regionNamesEn = new Intl.DisplayNames(['en'], { type: 'region' });

console.log(regionNamesEn.of('US'));    // "United States"
console.log(regionNamesEn.of('DE'));    // "Germany"

// Display script names
const scriptNamesEn = new Intl.DisplayNames(['en'], { type: 'script' });

console.log(scriptNamesEn.of('Latn'));  // "Latin"
console.log(scriptNamesEn.of('Arab'));  // "Arabic"
</code></pre>
</div>
<p>With <code>Intl.DisplayNames</code>, you avoid hardcoding countless mappings for language names, regions, or scripts, keeping your application robust and lean.</p>
<h2 id="browser-support">Browser Support</h2>
<p>You might be wondering about browser compatibility. The good news is that <code>Intl</code> has excellent support across modern browsers. All major browsers (Chrome, Firefox, Safari, Edge) fully support the core functionality discussed (<code>DateTimeFormat</code>, <code>NumberFormat</code>, <code>ListFormat</code>, <code>RelativeTimeFormat</code>, <code>PluralRules</code>, <code>DisplayNames</code>). You can confidently use these APIs without polyfills for the majority of your user base.</p>
<h2 id="conclusion-embrace-the-global-web-with-intl">Conclusion: Embrace The Global Web With <code>Intl</code></h2>
<p>The <code>Intl</code> API is a cornerstone of modern web development for a global audience. It empowers front-end developers to deliver <strong>highly localized user experiences</strong> with minimal effort, leveraging the browser’s built-in, optimized capabilities.</p>
<p>By adopting <code>Intl</code>, you <strong>reduce dependencies</strong>, <strong>shrink bundle sizes</strong>, and <strong>improve performance</strong>, all while ensuring your application respects and adapts to the diverse linguistic and cultural expectations of users worldwide. Stop wrestling with custom formatting logic and embrace this standards-compliant tool!</p>
<p>It’s important to remember that <code>Intl</code> handles the <em>formatting</em> of data. While incredibly powerful, it doesn’t solve every aspect of internationalization. Content translation, bidirectional text (RTL/LTR), locale-specific typography, and deep cultural nuances beyond data formatting still require careful consideration. (I may write about these in the future!) However, for presenting dynamic data accurately and intuitively, <code>Intl</code> is the browser-native answer.</p>
<h3 id="further-reading-resources">Further Reading &amp; Resources</h3>
<ul>
<li>MDN Web Docs:
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl"><code>Intl namespace object</code></a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat"><code>Intl.DateTimeFormat</code></a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat"><code>Intl.NumberFormat</code></a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat"><code>Intl.ListFormat</code></a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat"><code>Intl.RelativeTimeFormat</code></a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules"><code>Intl.PluralRules</code></a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames"><code>Intl.DisplayNames</code></a></li>
</ul>
</li>
<li>ECMAScript Internationalization API Specification: <a href="https://tc39.es/ecma402/">The official ECMA-402 Standard</a></li>
<li><a href="https://www.w3.org/International/questions/qa-choosing-language-tags">Choosing a Language Tag</a></li>
</ul>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/08/08/the-power-of-the-intl-api-a-definitive-guide-to-browser-native-internationalization/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Handling JavaScript Event Listeners With Parameters</title>
		<link>http://computercoursesonline.com/index.php/2025/07/21/handling-javascript-event-listeners-with-parameters/</link>
					<comments>http://computercoursesonline.com/index.php/2025/07/21/handling-javascript-event-listeners-with-parameters/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Mon, 21 Jul 2025 10:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=1001</guid>

					<description><![CDATA[Handling JavaScript Event Listeners With Parameters Handling JavaScript Event Listeners With Parameters Amejimaobari Ollornwi 2025-07-21T10:00:00+00:00 2025-07-24T20:33:36+00:00 JavaScript event listeners are very important, as they exist in almost every web application that requires interactivity. As common as they are, it is also essential for them to be managed properly. Improperly managed event listeners can lead to...]]></description>
										<content:encoded><![CDATA[<p>              <title>Handling JavaScript Event Listeners With Parameters</title></p>
<article>
<header>
<h1>Handling JavaScript Event Listeners With Parameters</h1>
<address>Amejimaobari Ollornwi</address>
<p>                  2025-07-21T10:00:00+00:00<br />
                  2025-07-24T20:33:36+00:00<br />
                </header>
<p>JavaScript event listeners are very important, as they exist in almost every web application that requires interactivity. As common as they are, it is also essential for them to be managed properly. Improperly managed event listeners can lead to memory leaks and can sometimes cause performance issues in extreme cases.</p>
<p>Here’s the real problem: <strong>JavaScript event listeners are often not removed after they are added.</strong> And when they are added, they do not require parameters most of the time &mdash; except in rare cases, which makes them a little trickier to handle.</p>
<p>A common scenario where you may need to use parameters with event handlers is when you have a dynamic list of tasks, where each task in the list has a “Delete” button attached to an event handler that uses the task’s ID as a parameter to remove the task. In a situation like this, it is a good idea to remove the event listener once the task has been completed to ensure that the deleted element can be successfully cleaned up, a process known as <a href="https://javascript.info/garbage-collection">garbage collecti</a><a href="https://javascript.info/garbage-collection">on</a>.</p>
<h2 id="a-common-mistake-when-adding-event-listeners">A Common Mistake When Adding Event Listeners</h2>
<p>A very common mistake when adding parameters to event handlers is calling the function with its parameters inside the <code>addEventListener()</code> method. This is what I mean:</p>
<pre><code class="language-javascript">button.addEventListener('click', myFunction(param1, param2));
</code></pre>
<p>The browser responds to this line by immediately calling the function, irrespective of whether or not the click event has happened. In other words, the function is invoked right away instead of being deferred, so it never fires when the click event actually occurs.</p>
<p>You may also receive the following console error in some cases:</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/handling-javascript-event-listeners-parameters/1-uncaught-typeerror.png"></p>
<p>    <img loading="lazy" width="800" height="75" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Uncaught TypeError" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/handling-javascript-event-listeners-parameters/1-uncaught-typeerror.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Uncaught TypeError: Failed to execute. <code>addEventListener</code> on <code>EventTarget</code>: parameter is not of type <code>Object</code>. (<a href="https://files.smashing.media/articles/handling-javascript-event-listeners-parameters/1-uncaught-typeerror.png">Large preview</a>)<br />
    </figcaption></figure>
<p>This error makes sense because the second parameter of the <code>addEventListener</code> method <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#listener">can only accept</a> a JavaScript function, an object with a <code>handleEvent()</code> method, or simply <code>null</code>. A quick and easy way to avoid this error is by changing the second parameter of the <code>addEventListener</code> method to an arrow or anonymous function.</p>
<pre><code class="language-javascript">button.addEventListener('click', (event) =&gt; {
  myFunction(event, param1, param2); // Runs on click
});
</code></pre>
<p>The only hiccup with using arrow and anonymous functions is that they cannot be removed with the traditional <code>removeEventListener()</code> method; you will have to make use of <code>AbortController</code>, which may be overkill for simple cases. <code>AbortController</code> shines when you have multiple event listeners to remove at once.</p>
<p>For simple cases where you have just one or two event listeners to remove, the <code>removeEventListener()</code> method still proves useful. However, in order to make use of it, you’ll need to store your function as a reference to the listener.</p>
<h2 id="using-parameters-with-event-handlers">Using Parameters With Event Handlers</h2>
<p>There are several ways to include parameters with event handlers. However, for the purpose of this demonstration, we are going to constrain our focus to the following two:</p>
<h3 id="option-1-arrow-and-anonymous-functions">Option 1: Arrow And Anonymous Functions</h3>
<p>Using arrow and anonymous functions is the fastest and easiest way to get the job done.</p>
<p>To add an event handler with parameters using arrow and anonymous functions, we’ll first need to call the function we’re going to create inside the arrow function attached to the event listener:</p>
<pre><code class="language-javascript">const button = document.querySelector("#myButton");

button.addEventListener("click", (event) =&gt; {
  handleClick(event, "hello", "world");
});
</code></pre>
<p>After that, we can create the function with parameters:</p>
<pre><code class="language-javascript">function handleClick(event, param1, param2) {
  console.log(param1, param2, event.type, event.target);
}
</code></pre>
<p>Note that with this method, removing the event listener requires the <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController"><code>AbortController</code></a>. To remove the event listener, we create a new <code>AbortController</code> object and then retrieve the <code>AbortSignal</code> object from it:</p>
<pre><code class="language-javascript">const controller = new AbortController();
const { signal } = controller;
</code></pre>
<p>Next, we can pass the <code>signal</code> from the <code>controller</code> as an option in the <code>removeEventListener()</code> method:</p>
<pre><code class="language-javascript">button.addEventListener("click", (event) =&gt; {
  handleClick(event, "hello", "world");
}, { signal });
</code></pre>
<p>Now we can remove the event listener by calling <code>AbortController.abort()</code>:</p>
<pre><code class="language-javascript">controller.abort()
</code></pre>
<h3 id="option-2-closures">Option 2: Closures</h3>
<p>Closures in JavaScript are another feature that can help us with event handlers. Remember the mistake that produced a type error? That mistake can also be corrected with closures. Specifically, with closures, a function can access variables from its outer scope.</p>
<p>In other words, we can access the parameters we need in the event handler from the outer function:</p>
<div class="break-out">
<pre><code class="language-javascript">function createHandler(message, number) {
  // Event handler
  return function (event) {
  console.log(`${message} ${number} - Clicked element:`, event.target);
    };
  }

  const button = document.querySelector("&#035;myButton");
  button.addEventListener("click", createHandler("Hello, world!", 1));
}
</code></pre>
</div>
<p>This establishes a function that returns another function. The function that is created is then called as the second parameter in the <code>addEventListener()</code> method so that the inner function is returned as the event handler. And with the power of closures, the parameters from the outer function will be made available for use in the inner function.</p>
<p>Notice how the <code>event</code> object is made available to the inner function. This is because the inner function is what is being attached as the event handler. The event object is passed to the function automatically because it’s the event handler.</p>
<p>To remove the event listener, we can use the <code>AbortController</code> like we did before. However, this time, let’s see how we can do that using the <code>removeEventListener()</code> method instead.</p>
<p>In order for the <code>removeEventListener</code> method to work, a reference to the <code>createHandler</code> function needs to be stored and used in the <code>addEventListener</code> method:</p>
<div class="break-out">
<pre><code class="language-javascript">function createHandler(message, number) {
  return function (event) {
    console.log(`${message} ${number} - Clicked element:`, event.target);
  };
}
const handler = createHandler("Hello, world!", 1);
button.addEventListener("click", handler);
</code></pre>
</div>
<p>Now, the event listener can be removed like this:</p>
<pre><code class="language-javascript">button.removeEventListener("click", handler);
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>It is good practice to always remove event listeners whenever they are no longer needed to prevent memory leaks. Most times, event handlers do not require parameters; however, in rare cases, they do. Using JavaScript features like closures, <code>AbortController</code>, and <code>removeEventListener</code>, handling parameters with event handlers is both possible and well-supported.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/07/21/handling-javascript-event-listeners-with-parameters/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Creating The &#8220;Moving Highlight&#8221; Navigation Bar With JavaScript And CSS</title>
		<link>http://computercoursesonline.com/index.php/2025/06/11/creating-the-moving-highlight-navigation-bar-with-javascript-and-css/</link>
					<comments>http://computercoursesonline.com/index.php/2025/06/11/creating-the-moving-highlight-navigation-bar-with-javascript-and-css/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Wed, 11 Jun 2025 13:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=941</guid>

					<description><![CDATA[Creating The &#38;ldquo;Moving Highlight&#38;rdquo; Navigation Bar With JavaScript And CSS Creating The &#38;ldquo;Moving Highlight&#38;rdquo; Navigation Bar With JavaScript And CSS Blake Lundquist 2025-06-11T13:00:00+00:00 2025-06-12T20:33:08+00:00 I recently came across an old jQuery tutorial demonstrating a “moving highlight” navigation bar and decided the concept was due for a modern upgrade. With this pattern, the border around the...]]></description>
										<content:encoded><![CDATA[<p>              <title>Creating The &amp;ldquo;Moving Highlight&amp;rdquo; Navigation Bar With JavaScript And CSS</title></p>
<article>
<header>
<h1>Creating The &amp;ldquo;Moving Highlight&amp;rdquo; Navigation Bar With JavaScript And CSS</h1>
<address>Blake Lundquist</address>
<p>                  2025-06-11T13:00:00+00:00<br />
                  2025-06-12T20:33:08+00:00<br />
                </header>
<p>I recently came across an old jQuery tutorial demonstrating a <strong>“moving highlight” navigation bar</strong> and decided the concept was due for a modern upgrade. With this pattern, the border around the active navigation item animates directly from one element to another as the user clicks on menu items. In 2025, we have much better tools to manipulate the DOM via vanilla JavaScript. New features like the <a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API">View Transition API</a> make progressive enhancement more easily achievable and handle a lot of the animation minutiae.</p>
<figure><a href="https://computercoursesonline.com/wp-content/uploads/2025/06/1-moving-highlight-navigation-bar.gif"><img loading="lazy" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" width="800" height="131" alt="An example of a “moving highlight” navigation bar" class="lazyload" data-src="https://computercoursesonline.com/wp-content/uploads/2025/06/1-moving-highlight-navigation-bar.gif"></a><figcaption>(<a href="https://computercoursesonline.com/wp-content/uploads/2025/06/1-moving-highlight-navigation-bar.gif">Large preview</a>)</figcaption></figure>
<p>In this tutorial, I will demonstrate two methods of creating the “moving highlight” navigation bar using plain JavaScript and CSS. The first example uses the <code>getBoundingClientRect</code> method to explicitly animate the border between navigation bar items when they are clicked. The second example achieves the same functionality using the new View Transition API.</p>
<h2 id="the-initial-markup">The Initial Markup</h2>
<p>Let’s assume that we have a single-page application where content changes without the page being reloaded. The starting HTML and CSS are your standard navigation bar with an additional <code>div</code> element containing an <code>id</code> of <code>#highlight</code>. We give the first navigation item a class of <code>.active</code>.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="EajQyBW" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Moving Highlight Navbar Starting Markup [forked]](https://codepen.io/smashingmag/pen/EajQyBW) by <a href="https://codepen.io/blakeeric">Blake Lundquist</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/EajQyBW">Moving Highlight Navbar Starting Markup [forked]</a> by <a href="https://codepen.io/blakeeric">Blake Lundquist</a>.</figcaption></figure>
<p>For this version, we will position the <code>#highlight</code> element around the element with the <code>.active</code> class to create a border. We can utilize <code>absolute</code> positioning and animate the element across the navigation bar to create the desired effect. We’ll hide it off-screen initially by adding <code>left: -200px</code> and include <code>transition</code> styles for all properties so that any changes in the position and size of the element will happen gradually.</p>
<pre><code class="language-css">&#035;highlight {
  z-index: 0;
  position: absolute;
  height: 100%;
  width: 100px;
  left: -200px;
  border: 2px solid green;
  box-sizing: border-box;
  transition: all 0.2s ease;
}
</code></pre>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="/printed-books/touch-design-for-mobile-interfaces/">Touch Design for Mobile Interfaces</a></strong>, Steven Hoober’s brand-new guide on <strong>designing for mobile</strong> with proven, universal, human-centric guidelines. <strong>400 pages</strong>, jam-packed with in-depth user research and <strong>best practices</strong>.</p>
<p><a data-instant href="https://www.smashingmagazine.com/printed-books/touch-design-for-mobile-interfaces/" class="btn btn--green btn--large">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/touch-design-for-mobile-interfaces/" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="480" height="697" data-src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/b14658fc-bb2d-41a6-8d1a-70eaaf1b8ec8/touch-design-book-shop-opt.png"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="add-a-boilerplate-event-handler-for-click-interactions">Add A Boilerplate Event Handler For Click Interactions</h2>
<p>We want the highlight element to animate when a user changes the <code>.active</code> navigation item. Let’s add a <code>click</code> event handler to the <code>nav</code> element, then filter for events caused only by elements matching our desired selector. In this case, we only want to change the <code>.active</code> nav item if the user clicks on a link that does not already have the <code>.active</code> class.</p>
<p>Initially, we can call <code>console.log</code> to ensure the handler fires only when expected:</p>
<div class="break-out">
<pre><code class="language-javascript">const navbar = document.querySelector('nav');

navbar.addEventListener('click', function (event) {
  // return if the clicked element doesn't have the correct selector
  if (!event.target.matches('nav a:not(active)')) {
    return;
  }
  
  console.log('click');
});
</code></pre>
</div>
<p>Open your browser console and try clicking different items in the navigation bar. You should only see <code>&quot;click&quot;</code> being logged when you select a new item in the navigation bar.</p>
<p>Now that we know our event handler is working on the correct elements let’s add code to move the <code>.active</code> class to the navigation item that was clicked. We can use the object passed into the event handler to find the element that initialized the event and give that element a class of <code>.active</code> after removing it from the previously active item.</p>
<div class="break-out">
<pre><code class="language-javascript">const navbar = document.querySelector('nav');

navbar.addEventListener('click', function (event) {
  // return if the clicked element doesn't have the correct selector
  if (!event.target.matches('nav a:not(active)')) {
    return;
  }
  
-  console.log('click');
+  document.querySelector('nav a.active').classList.remove('active');
+  event.target.classList.add('active');
  
});
</code></pre>
</div>
<p>Our <code>#highlight</code> element needs to move across the navigation bar and position itself around the active item. Let’s write a function to calculate a new position and width. Since the <code>#highlight</code> selector has <code>transition</code> styles applied, it will move gradually when its position changes.</p>
<p>Using <code>getBoundingClientRect</code>, we can get information about the position and size of an element. We calculate the width of the active navigation item and its offset from the left boundary of the parent element. Then, we assign styles to the highlight element so that its size and position match.</p>
<div class="break-out">
<pre><code class="language-javascript">// handler for moving the highlight
const moveHighlight = () =&gt; {
  const activeNavItem = document.querySelector('a.active');
  const highlighterElement = document.querySelector('#highlight');
  
  const width = activeNavItem.offsetWidth;

  const itemPos = activeNavItem.getBoundingClientRect();
  const navbarPos = navbar.getBoundingClientRect()
  const relativePosX = itemPos.left - navbarPos.left;

  const styles = {
    left: `${relativePosX}px`,
    width: `${width}px`,
  };

  Object.assign(highlighterElement.style, styles);
}
</code></pre>
</div>
<p>Let’s call our new function when the click event fires:</p>
<div class="break-out">
<pre><code class="language-javascript">navbar.addEventListener('click', function (event) {
  // return if the clicked element doesn't have the correct selector
  if (!event.target.matches('nav a:not(active)')) {
    return;
  }
  
  document.querySelector('nav a.active').classList.remove('active');
  event.target.classList.add('active');
  
+  moveHighlight();
});
</code></pre>
</div>
<p>Finally, let’s also call the function immediately so that the border moves behind our initial active item when the page first loads:</p>
<pre><code class="language-javascript">// handler for moving the highlight
const moveHighlight = () =&gt; {
 // ...
}

// display the highlight when the page loads
moveHighlight();
</code></pre>
<p>Now, the border moves across the navigation bar when a new item is selected. Try clicking the different navigation links to animate the navigation bar.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="WbvMxqV" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Moving Highlight Navbar [forked]](https://codepen.io/smashingmag/pen/WbvMxqV) by <a href="https://codepen.io/blakeeric">Blake Lundquist</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/WbvMxqV">Moving Highlight Navbar [forked]</a> by <a href="https://codepen.io/blakeeric">Blake Lundquist</a>.</figcaption></figure>
<p>That only took a few lines of vanilla JavaScript and could easily be extended to account for other interactions, like <code>mouseover</code> events. In the next section, we will explore refactoring this feature using the View Transition API.</p>
<div class="partners__lead-place"></div>
<h2 id="using-the-view-transition-api">Using The View Transition API</h2>
<p>The View Transition API provides functionality to create animated transitions between website views. Under the hood, the API creates snapshots of “before” and “after” views and then handles transitioning between them. View transitions are useful for creating animations between documents, providing the <strong>native-app-like user experience</strong> featured in frameworks like <a href="https://docs.astro.build/en/guides/view-transitions/">Astro</a>. However, the API also provides handlers meant for <strong>SPA-style applications</strong>. We will use it to reduce the JavaScript needed in our implementation and more easily create fallback functionality.</p>
<p>For this approach, we no longer need a separate <code>#highlight</code> element. Instead, we can style the <code>.active</code> navigation item directly using pseudo-selectors and let the View Transition API handle the animation between the before-and-after UI states when a new navigation item is clicked.</p>
<p>We’ll start by getting rid of the <code>#highlight</code> element and its associated CSS and replacing it with styles for the <code>nav a::after</code> pseudo-selector:</p>
<pre><code class="language-html">&lt;nav&gt;
  - &lt;div id="highlight"&gt;&lt;/div&gt;
  &lt;a href="#" class="active"&gt;Home&lt;/a&gt;
  &lt;a href="#services"&gt;Services&lt;/a&gt;
  &lt;a href="#about"&gt;About&lt;/a&gt;
  &lt;a href="#contact"&gt;Contact&lt;/a&gt;
&lt;/nav&gt;
</code></pre>
<pre><code class="language-css">- &#035;highlight {
-  z-index: 0;
-  position: absolute;
-  height: 100%;
-  width: 0;
-  left: 0;
-  box-sizing: border-box;
-  transition: all 0.2s ease;
- }

+ nav a::after {
+  content: " ";
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  border: none;
+  box-sizing: border-box;
+ }
</code></pre>
<p>For the <code>.active</code> class, we include the <code>view-transition-name</code> property, thus unlocking the magic of the View Transition API. Once we trigger the view transition and change the location of the <code>.active</code> navigation item in the DOM, “before” and “after” snapshots will be taken, and the browser will animate the border across the bar. We’ll give our view transition the name of <code>highlight</code>, but we could theoretically give it any name.</p>
<pre><code class="language-css">nav a.active::after {
  border: 2px solid green;
  view-transition-name: highlight;
}
</code></pre>
<p>Once we have a selector that contains a <code>view-transition-name</code> property, the only remaining step is to trigger the transition using the <code>startViewTransition</code> method and pass in a callback function.</p>
<div class="break-out">
<pre><code class="language-javascript">const navbar = document.querySelector('nav');

// Change the active nav item on click
navbar.addEventListener('click', async  function (event) {

  if (!event.target.matches('nav a:not(.active)')) {
    return;
  }
  
  document.startViewTransition(() =&gt; {
    document.querySelector('nav a.active').classList.remove('active');

    event.target.classList.add('active');
  });
});
</code></pre>
</div>
<p>Above is a revised version of the <code>click</code> handler. Instead of doing all the calculations for the size and position of the moving border ourselves, the View Transition API handles all of it for us. We only need to call <code>document.startViewTransition</code> and pass in a callback function to change the item that has the <code>.active</code> class!</p>
<div class="partners__lead-place"></div>
<h2 id="adjusting-the-view-transition">Adjusting The View Transition</h2>
<p>At this point, when clicking on a navigation link, you’ll notice that the transition works, but some strange sizing issues are visible.</p>
<figure><a href="https://files.smashing.media/articles/creating-moving-highlight-navigation-bar-vanilla-javascript/2-view-transition-sizing-issues.gif"><img loading="lazy" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" width="800" height="163" alt="The view transition with sizing issues" class="lazyload" data-src="https://computercoursesonline.com/wp-content/uploads/2025/06/2-view-transition-sizing-issues-800px.gif"></a><figcaption>(<a href="https://files.smashing.media/articles/creating-moving-highlight-navigation-bar-vanilla-javascript/2-view-transition-sizing-issues.gif">Large preview</a>)</figcaption></figure>
<p>This sizing inconsistency is caused by aspect ratio changes during the course of the view transition. We won’t go into detail here, but <a href="https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/">Jake Archibald has a detailed explanation you can read</a> for more information. In short, to ensure the height of the border stays uniform throughout the transition, we need to declare an explicit <code>height</code> for the <code>::view-transition-old</code> and <code>::view-transition-new</code> pseudo-selectors representing a static snapshot of the old and new view, respectively.</p>
<pre><code class="language-css">::view-transition-old(highlight) {
  height: 100%;
}

::view-transition-new(highlight) {
  height: 100%;
}
</code></pre>
<p>Let’s do some final refactoring to tidy up our code by moving the callback to a separate function and adding a fallback for when view transitions aren’t supported:</p>
<div class="break-out">
<pre><code class="language-javascript">const navbar = document.querySelector('nav');

// change the item that has the .active class applied
const setActiveElement = (elem) =&gt; {
  document.querySelector('nav a.active').classList.remove('active');
  elem.classList.add('active');
}

// Start view transition and pass in a callback on click
navbar.addEventListener('click', async  function (event) {
  if (!event.target.matches('nav a:not(.active)')) {
    return;
  }

  // Fallback for browsers that don't support View Transitions:
  if (!document.startViewTransition) {
    setActiveElement(event.target);
    return;
  }
  
  document.startViewTransition(() =&gt; setActiveElement(event.target));
});
</code></pre>
</div>
<p>Here’s our view transition-powered navigation bar! Observe the smooth transition when you click on the different links.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="ogXELKE" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Moving Highlight Navbar with View Transition [forked]](https://codepen.io/smashingmag/pen/ogXELKE) by <a href="https://codepen.io/blakeeric">Blake Lundquist</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ogXELKE">Moving Highlight Navbar with View Transition [forked]</a> by <a href="https://codepen.io/blakeeric">Blake Lundquist</a>.</figcaption></figure>
<h2 id="conclusion">Conclusion</h2>
<p>Animations and transitions between website UI states used to require many kilobytes of external libraries, along with verbose, confusing, and error-prone code, but vanilla JavaScript and CSS have since incorporated features to achieve <strong>native-app-like interactions without breaking the bank</strong>. We demonstrated this by implementing the “moving highlight” navigation pattern using two approaches: CSS transitions combined with the <code>getBoundingClientRect()</code> method and the View Transition API.</p>
<h3 id="resources">Resources</h3>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect"><code>getBoundingClientRect()</code> method documentation</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API">View Transition API documentation</a></li>
<li>“<a href="https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/">View Transitions: Handling Aspect Ratio Changes</a>” by Jake Archibald</li>
</ul>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/06/11/creating-the-moving-highlight-navigation-bar-with-javascript-and-css/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building An Offline-Friendly Image Upload System</title>
		<link>http://computercoursesonline.com/index.php/2025/04/23/building-an-offline-friendly-image-upload-system/</link>
					<comments>http://computercoursesonline.com/index.php/2025/04/23/building-an-offline-friendly-image-upload-system/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Wed, 23 Apr 2025 10:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=400</guid>

					<description><![CDATA[Building An Offline-Friendly Image Upload System Building An Offline-Friendly Image Upload System Amejimaobari Ollornwi 2025-04-23T10:00:00+00:00 2025-05-01T20:32:48+00:00 So, you’re filling out an online form, and it asks you to upload a file. You click the input, select a file from your desktop, and are good to go. But something happens. The network drops, the file disappears,...]]></description>
										<content:encoded><![CDATA[<p>              <title>Building An Offline-Friendly Image Upload System</title></p>
<article>
<header>
<h1>Building An Offline-Friendly Image Upload System</h1>
<address>Amejimaobari Ollornwi</address>
<p>                  2025-04-23T10:00:00+00:00<br />
                  2025-05-01T20:32:48+00:00<br />
                </header>
<p>So, you’re filling out an online form, and it asks you to upload a file. You click the input, select a file from your desktop, and are good to go. But something happens. The network drops, the file disappears, and you’re stuck having to re-upload the file. <strong>Poor network connectivity</strong> can lead you to spend an unreasonable amount of time trying to upload files successfully.</p>
<p>What ruins the user experience stems from having to constantly check network stability and retry the upload several times. While we may not be able to do much about network connectivity, as developers, we can always do something to ease the pain that comes with this problem.</p>
<p>One of the ways we can solve this problem is by tweaking image upload systems in a way that enables users to upload images offline &mdash; <strong>eliminating the need for a reliable network connection</strong>, and then having the system retry the upload process when the network becomes stable, without the user intervening.</p>
<p>This article is going to focus on explaining how to build <strong>an offline-friendly image upload system</strong> using PWA (progressive web application) technologies such as <code>IndexedDB</code>, service workers, and the Background Sync API. We will also briefly cover tips for improving the user experience for this system.</p>
<h2 id="planning-the-offline-image-upload-system">Planning The Offline Image Upload System</h2>
<p>Here’s a flow chart for an offline-friendly image upload system.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/building-offline-friendly-image-upload-system/1-upload-system-flow-chart.png"></p>
<p>    <img loading="lazy" width="800" height="678" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Flow chart of an offline-friendly image upload system" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/building-offline-friendly-image-upload-system/1-upload-system-flow-chart.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Flow chart of an offline-friendly image upload system (<a href="https://files.smashing.media/articles/building-offline-friendly-image-upload-system/1-upload-system-flow-chart.png">Large preview</a>)<br />
    </figcaption></figure>
<p>As shown in the flow chart, the process unfolds as follows:</p>
<ol>
<li><strong>The user selects an image.</strong><br />
The process begins by letting the user select their image.</li>
<li><strong>The image is stored locally in <code>IndexedDB</code>.</strong><br />
Next, the system checks for network connectivity. If network connectivity is available, the system uploads the image directly, avoiding unnecessary local storage usage. However, if the network is not available, the image will be stored in <code>IndexedDB</code>.</li>
<li><strong>The service worker detects when the network is restored.</strong><br />
With the image stored in <code>IndexedDB</code>, the system waits to detect when the network connection is restored to continue with the next step.</li>
<li><strong>The background sync processes pending uploads.</strong><br />
The moment the connection is restored, the system will try to upload the image again.</li>
<li><strong>The file is successfully uploaded</strong>.<br />
The moment the image is uploaded, the system will remove the local copy stored in <code>IndexedDB</code>.</p>
<p></li>
</ol>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Roll up your sleeves and <strong>boost your UX skills</strong>! Meet <strong><a data-instant href="https://smart-interface-design-patterns.com/">Smart Interface Design Patterns</a></strong>&nbsp;<img src="https://s.w.org/images/core/emoji/13.1.0/72x72/1f363.png" alt="🍣" class="wp-smiley" style="height: 1em; max-height: 1em;" />, a 10h video library by Vitaly Friedman. <strong>100s of real-life examples</strong> and live UX training. <a href="https://www.youtube.com/watch?v=3mwZztmGgbE">Free preview</a>.</p>
<p><a data-instant href="https://smart-interface-design-patterns.com/" class="btn btn--green btn--large">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://smart-interface-design-patterns.com/" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="690" height="790" data-src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/8c98e7f9-8e62-4c43-b833-fc6bf9fea0a9/video-course-smart-interface-design-patterns-vitaly-friedman.jpg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="implementing-the-system">Implementing The System</h2>
<p>The first step in the system implementation is allowing the user to select their images. There are different ways you can achieve this:</p>
<ul>
<li>You can use a simple <code>&lt;input type=&quot;file&quot;&gt;</code> element;</li>
<li>A drag-and-drop interface.</li>
</ul>
<p>I would advise that you use both. Some users prefer to use the drag-and-drop interface, while others think the only way to upload images is through the <code>&lt;input type=&quot;file&quot;&gt;</code> element. Having both options will help improve the user experience. You can also consider allowing users to paste images directly in the browser using the Clipboard API.</p>
<h3 id="registering-the-service-worker">Registering The Service Worker</h3>
<p>At the heart of this solution is the <a href="https://www.smashingmagazine.com/2016/02/making-a-service-worker/">service worker</a>. Our service worker is going to be responsible for retrieving the image from the <code>IndexedDB</code> store, uploading it when the internet connection is restored, and clearing the <code>IndexedDB</code> store when the image has been uploaded.</p>
<p>To use a service worker, you first have to register one:</p>
<div class="break-out">
<pre><code class="language-javascript">if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js')
    .then(reg =&gt; console.log('Service Worker registered', reg))
    .catch(err =&gt; console.error('Service Worker registration failed', err));
}
</code></pre>
</div>
<h3 id="checking-for-network-connectivity">Checking For Network Connectivity</h3>
<p>Remember, the problem we are trying to solve is caused by <strong>unreliable network connectivity</strong>. If this problem does not exist, there is no point in trying to solve anything. Therefore, once the image is selected, we need to check if the user has a reliable internet connection before registering a sync event and storing the image in <code>IndexedDB</code>.</p>
<pre><code class="language-javascript">function uploadImage() {
  if (navigator.onLine) {
    // Upload Image
  } else {
    // register Sync Event
    // Store Images in IndexedDB
  }
}
</code></pre>
<p><strong>Note</strong>: I’m only using the <code>navigator.onLine</code> property here to demonstrate how the system would work. The <code>navigator.onLine</code> property is <strong>unreliable</strong>, and I would suggest you come up with a custom solution to check whether the user is connected to the internet or not. One way you can do this is by sending a ping request to a server endpoint you’ve created.</p>
<h3 id="registering-the-sync-event">Registering The Sync Event</h3>
<p>Once the network test fails, the next step is to register a sync event. The sync event needs to be registered at the point where the system fails to upload the image due to a poor internet connection.</p>
<pre><code class="language-javascript">async function registerSyncEvent() {
  if ('SyncManager' in window) {
    const registration = await navigator.serviceWorker.ready;
    await registration.sync.register('uploadImages');
    console.log('Background Sync registered');
  }
}
</code></pre>
<p>After registering the sync event, you need to listen for it in the service worker.</p>
<pre><code class="language-javascript">self.addEventListener('sync', (event) =&gt; {
  if (event.tag === 'uploadImages') {
    event.waitUntil(sendImages());
  }
});
</code></pre>
<p>The <code>sendImages</code> function is going to be an asynchronous process that will retrieve the image from <code>IndexedDB</code> and upload it to the server. This is what it’s going to look like:</p>
<pre><code class="language-javascript">async function sendImages() {
  try {
    // await image retrieval and upload
  } catch (error) {
    // throw error
  }
}
</code></pre>
<h3 id="opening-the-database">Opening The Database</h3>
<p>The first thing we need to do in order to store our image locally is to open an <code>IndexedDB</code> store. As you can see from the code below, we are creating <strong>a global variable to store the database instance</strong>. The reason for doing this is that, subsequently, when we want to retrieve our image from <code>IndexedDB</code>, we wouldn’t need to write the code to open the database again.</p>
<div class="break-out">
<pre><code class="language-javascript">let database; // Global variable to store the database instance

function openDatabase() {
  return new Promise((resolve, reject) =&gt; {
    if (database) return resolve(database); // Return existing database instance 

    const request = indexedDB.open("myDatabase", 1);

    request.onerror = (event) =&gt; {
      console.error("Database error:", event.target.error);
      reject(event.target.error); // Reject the promise on error
    };

    request.onupgradeneeded = (event) =&gt; {
        const db = event.target.result;
        // Create the "images" object store if it doesn't exist.
        if (!db.objectStoreNames.contains("images")) {
          db.createObjectStore("images", { keyPath: "id" });
        }
        console.log("Database setup complete.");
    };

    request.onsuccess = (event) =&gt; {
      database = event.target.result; // Store the database instance globally
      resolve(database); // Resolve the promise with the database instance
    };
  });
}
</code></pre>
</div>
<div class="partners__lead-place"></div>
<h3 id="storing-the-image-in-indexeddb">Storing The Image In IndexedDB</h3>
<p>With the <code>IndexedDB</code> store open, we can now store our images.</p>
<blockquote><p>Now, you may be wondering why an easier solution like <code>localStorage</code> wasn’t used for this purpose.</p>
<p>The reason for that is that <code>IndexedDB</code> operates asynchronously and doesn’t block the main JavaScript thread, whereas <code>localStorage</code> runs synchronously and can block the JavaScript main thread if it is being used.</p></blockquote>
<p>Here’s how you can store the image in <code>IndexedDB</code>:</p>
<div class="break-out">
<pre><code class="language-javascript">async function storeImages(file) {
  // Open the IndexedDB database.
  const db = await openDatabase();
  // Create a transaction with read and write access.
  const transaction = db.transaction("images", "readwrite");
  // Access the "images" object store.
  const store = transaction.objectStore("images");
  // Define the image record to be stored.
  const imageRecord = {
    id: IMAGE&#095;ID,   // a unique ID
    image: file     // Store the image file (Blob)
  };
  // Add the image record to the store.
  const addRequest = store.add(imageRecord);
  // Handle successful addition.
  addRequest.onsuccess = () =&gt; console.log("Image added successfully!");
  // Handle errors during insertion.
  addRequest.onerror = (e) =&gt; console.error("Error storing image:", e.target.error);
}
</code></pre>
</div>
<p>With the images stored and the background sync set, the system is ready to upload the image whenever the network connection is restored.</p>
<h3 id="retrieving-and-uploading-the-images">Retrieving And Uploading The Images</h3>
<p>Once the network connection is restored, the sync event will fire, and the service worker will retrieve the image from <code>IndexedDB</code> and upload it.</p>
<div class="break-out">
<pre><code class="language-javascript">async function retrieveAndUploadImage(IMAGE&#095;ID) {
  try {
    const db = await openDatabase(); // Ensure the database is open
    const transaction = db.transaction("images", "readonly");
    const store = transaction.objectStore("images");
    const request = store.get(IMAGE&#095;ID);
    request.onsuccess = function (event) {
      const image = event.target.result;
      if (image) {
        // upload Image to server here
      } else {
        console.log("No image found with ID:", IMAGE&#095;ID);
      }
    };
    request.onerror = () =&gt; {
        console.error("Error retrieving image.");
    };
  } catch (error) {
    console.error("Failed to open database:", error);
  }
}
</code></pre>
</div>
<h3 id="deleting-the-indexeddb-database">Deleting The IndexedDB Database</h3>
<p>Once the image has been uploaded, the <code>IndexedDB</code> store is no longer needed. Therefore, it should be deleted along with its content to free up storage.</p>
<div class="break-out">
<pre><code class="language-javascript">function deleteDatabase() {
  // Check if there's an open connection to the database.
  if (database) {
    database.close(); // Close the database connection
    console.log("Database connection closed.");
  }

  // Request to delete the database named "myDatabase".
  const deleteRequest = indexedDB.deleteDatabase("myDatabase");

  // Handle successful deletion of the database.
  deleteRequest.onsuccess = function () {
    console.log("Database deleted successfully!");
  };

  // Handle errors that occur during the deletion process.
  deleteRequest.onerror = function (event) {
    console.error("Error deleting database:", event.target.error);
  };

  // Handle cases where the deletion is blocked (e.g., if there are still open connections).
  deleteRequest.onblocked = function () {
    console.warn("Database deletion blocked. Close open connections and try again.");
  };
}
</code></pre>
</div>
<p>With that, the entire process is complete!</p>
<div class="partners__lead-place"></div>
<h2 id="considerations-and-limitations">Considerations And Limitations</h2>
<p>While we’ve done a lot to help improve the experience by supporting offline uploads, the system is not without its limitations. I figured I would specifically call those out because it’s worth knowing where this solution might fall short of your needs.</p>
<ul>
<li><strong>No Reliable Internet Connectivity Detection</strong><br />
JavaScript does not provide a foolproof way to detect online status. For this reason, you need to come up with a custom solution for detecting online status.</li>
<li><strong>Chromium-Only Solution</strong><br />
The Background Sync API is currently <a href="https://developer.mozilla.org/en-US/docs/Web/API/Background_Synchronization_API#browser_compatibility">limited to Chromium-based browsers</a>. As such, this solution is only supported by Chromium browsers. That means you will need a more robust solution if you have the majority of your users on non-Chromium browsers.</li>
<li><strong><code>IndexedDB</code> Storage Policies</strong><br />
Browsers impose storage limitations and eviction policies for <code>IndexedDB</code>. For instance, in Safari, data stored in <code>IndexedDB</code> has a lifespan of seven days if the user doesn’t interact with the website. This is something you should bear in mind if you do come up with an alternative for the background sync API that supports Safari.</li>
</ul>
<h2 id="enhancing-the-user-experience">Enhancing The User Experience</h2>
<p>Since the entire process happens in the background, we need a way to inform the users when images are stored, waiting to be uploaded, or have been successfully uploaded. Implementing certain <strong>UI elements</strong> for this purpose will indeed enhance the experience for the users. These UI elements may include toast notifications, upload status indicators like spinners (to show active processes), progress bars (to show state progress), network status indicators, or buttons to provide retry and cancel options.</p>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>Poor internet connectivity can disrupt the user experience of a web application. However, by leveraging PWA technologies such as <code>IndexedDB</code>, service workers, and the Background Sync API, developers can help improve the reliability of web applications for their users, especially those in areas with unreliable internet connectivity.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/04/23/building-an-offline-friendly-image-upload-system/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Creating An Effective Multistep Form For Better User Experience</title>
		<link>http://computercoursesonline.com/index.php/2024/12/03/creating-an-effective-multistep-form-for-better-user-experience/</link>
					<comments>http://computercoursesonline.com/index.php/2024/12/03/creating-an-effective-multistep-form-for-better-user-experience/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Tue, 03 Dec 2024 10:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=402</guid>

					<description><![CDATA[Creating An Effective Multistep Form For Better User Experience Creating An Effective Multistep Form For Better User Experience Amejimaobari Ollornwi 2024-12-03T10:00:00+00:00 2025-05-01T20:32:48+00:00 For a multistep form, planning involves structuring questions logically across steps, grouping similar questions, and minimizing the number of steps and the amount of required information for each step. Whatever makes each step...]]></description>
										<content:encoded><![CDATA[<p>              <title>Creating An Effective Multistep Form For Better User Experience</title></p>
<article>
<header>
<h1>Creating An Effective Multistep Form For Better User Experience</h1>
<address>Amejimaobari Ollornwi</address>
<p>                  2024-12-03T10:00:00+00:00<br />
                  2025-05-01T20:32:48+00:00<br />
                </header>
<p>For a multistep form, planning involves structuring questions logically across steps, grouping similar questions, and minimizing the number of steps and the amount of required information for each step. Whatever makes each step focused and manageable is what should be aimed for.</p>
<p>In this tutorial, we will create a multistep form for a job application. Here are the details we are going to be requesting from the applicant at each step:</p>
<ul>
<li><strong>Personal Information</strong><br />
Collects applicant’s name, email, and phone number.</li>
<li><strong>Work Experience</strong><br />
Collects the applicant’s most recent company, job title, and years of experience.</li>
<li><strong>Skills &amp; Qualifications</strong><br />
The applicant lists their skills and selects their highest degree.</li>
<li><strong>Review &amp; Submit</strong><br />
This step is not going to collect any information. Instead, it provides an opportunity for the applicant to go back and review the information entered in the previous steps of the form before submitting it.</li>
</ul>
<p>You can think of structuring these questions as a digital way of getting to know somebody. You can’t meet someone for the first time and ask them about their work experience without first asking for their name.</p>
<p>Based on the steps we have above, this is what the body of our HTML with our form should look like. First, the main <code>&lt;form&gt;</code> element:</p>
<pre><code class="language-html">&lt;form id="jobApplicationForm"&gt;
  &lt;!-- Step 1: Personal Information --&gt;
  &lt;!-- Step 2: Work Experience --&gt;
  &lt;!-- Step 3: Skills &amp; Qualifications --&gt;
  &lt;!-- Step 4: Review &amp; Submit --&gt;
&lt;/form&gt;
</code></pre>
<p><strong>Step 1</strong> is for filling in personal information, like the applicant’s name, email address, and phone number:</p>
<div class="break-out">
<pre><code class="language-javascript">&lt;form id="jobApplicationForm"&gt;
  &lt;!-- Step 1: Personal Information --&gt;
  &lt;fieldset class="step" id="step-1"&gt;
    &lt;legend id="step1Label"&gt;Step 1: Personal Information&lt;/legend&gt;
    &lt;label for="name"&gt;Full Name&lt;/label&gt;
    &lt;input type="text" id="name" name="name" required /&gt;
    &lt;label for="email"&gt;Email Address&lt;/label&gt;
    &lt;input type="email" id="email" name="email" required /&gt;
    &lt;label for="phone"&gt;Phone Number&lt;/label&gt;
    &lt;input type="tel" id="phone" name="phone" required /&gt;
  &lt;/fieldset&gt;

  &lt;!-- Step 2: Work Experience --&gt;
  &lt;!-- Step 3: Skills &amp; Qualifications --&gt;
  &lt;!-- Step 4: Review &amp; Submit --&gt;
&lt;/form&gt;
</code></pre>
</div>
<p>Once the applicant completes the first step, we’ll navigate them to <strong>Step 2</strong>, focusing on their work experience so that we can collect information like their most recent company, job title, and years of experience. We’ll tack on a new <code>&lt;fieldset&gt;</code> with those inputs:</p>
<div class="break-out">
<pre><code class="language-javascript">&lt;form id="jobApplicationForm"&gt;
  &lt;!-- Step 1: Personal Information --&gt;

  &lt;!-- Step 2: Work Experience --&gt;
  &lt;fieldset class="step" id="step-2" hidden&gt;
    &lt;legend id="step2Label"&gt;Step 2: Work Experience&lt;/legend&gt;
    &lt;label for="company"&gt;Most Recent Company&lt;/label&gt;
    &lt;input type="text" id="company" name="company" required /&gt;
    &lt;label for="jobTitle"&gt;Job Title&lt;/label&gt;
    &lt;input type="text" id="jobTitle" name="jobTitle" required /&gt;
    &lt;label for="yearsExperience"&gt;Years of Experience&lt;/label&gt;
    &lt;input
      type="number"
      id="yearsExperience"
      name="yearsExperience"
      min="0"
      required
    /&gt;
  &lt;/fieldset&gt;

  &lt;!-- Step 3: Skills &amp; Qualifications --&gt;
  &lt;!-- Step 4: Review &amp; Submit --&gt;
&lt;/form&gt;
</code></pre>
</div>
<p><strong>Step 3</strong> is all about the applicant listing their skills and qualifications for the job they’re applying for:</p>
<div class="break-out">
<pre><code class="language-javascript">&lt;form id="jobApplicationForm"&gt;
  &lt;!-- Step 1: Personal Information --&gt;
  &lt;!-- Step 2: Work Experience --&gt;

  &lt;!-- Step 3: Skills &amp; Qualifications --&gt;
  &lt;fieldset class="step" id="step-3" hidden&gt;
    &lt;legend id="step3Label"&gt;Step 3: Skills &amp; Qualifications&lt;/legend&gt;
    &lt;label for="skills"&gt;Skill(s)&lt;/label&gt;
    &lt;textarea id="skills" name="skills" rows="4" required&gt;&lt;/textarea&gt;
    &lt;label for="highestDegree"&gt;Degree Obtained (Highest)&lt;/label&gt;
    &lt;select id="highestDegree" name="highestDegree" required&gt;
      &lt;option value=""&gt;Select Degree&lt;/option&gt;
      &lt;option value="highschool"&gt;High School Diploma&lt;/option&gt;
      &lt;option value="bachelor"&gt;Bachelor's Degree&lt;/option&gt;
      &lt;option value="master"&gt;Master's Degree&lt;/option&gt;
      &lt;option value="phd"&gt;Ph.D.&lt;/option&gt;
    &lt;/select&gt;
  &lt;/fieldset&gt;
  &lt;!-- Step 4: Review &amp; Submit --&gt;
  &lt;fieldset class="step" id="step-4" hidden&gt;
    &lt;legend id="step4Label"&gt;Step 4: Review &amp; Submit&lt;/legend&gt;
    &lt;p&gt;Review your information before submitting the application.&lt;/p&gt;
    &lt;button type="submit"&gt;Submit Application&lt;/button&gt;
  &lt;/fieldset&gt;
&lt;/form&gt;
</code></pre>
</div>
<p>And, finally, we’ll allow the applicant to review their information before submitting it:</p>
<div class="break-out">
<pre><code class="language-javascript">&lt;form id="jobApplicationForm"&gt;
  &lt;!-- Step 1: Personal Information --&gt;
  &lt;!-- Step 2: Work Experience --&gt;
  &lt;!-- Step 3: Skills &amp; Qualifications --&gt;

  &lt;!-- Step 4: Review &amp; Submit --&gt;
  &lt;fieldset class="step" id="step-4" hidden&gt;
    &lt;legend id="step4Label"&gt;Step 4: Review &amp; Submit&lt;/legend&gt;
    &lt;p&gt;Review your information before submitting the application.&lt;/p&gt;
    &lt;button type="submit"&gt;Submit Application&lt;/button&gt;
  &lt;/fieldset&gt;
&lt;/form&gt;
</code></pre>
</div>
<p><strong>Notice</strong>: We’ve added a <code>hidden</code> attribute to every <code>fieldset</code> element but the first one. This ensures that the user sees only the first step. Once they are done with the first step, they can proceed to fill out their work experience on the second step by clicking a navigational button. We’ll add this button later on.</p>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="adding-styles">Adding Styles</h2>
<p>To keep things focused, we’re not going to be emphasizing the styles in this tutorial. What we’ll do to keep things simple is leverage the <a href="https://simplecss.org">Simple.css style framework</a> to get the form in good shape for the rest of the tutorial.</p>
<p>If you’re following along, we can include Simple’s styles in the document <code>&lt;head&gt;</code>:</p>
<div class="break-out">
<pre><code class="language-javascript">&lt;link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" /&gt;
</code></pre>
</div>
<p>And from there, go ahead and create a <code>style.css</code> file with the following styles that I’ve folded up.</p>
<pre><code class="language-css">&lt;details&gt;
  &lt;summary&gt;View CSS&lt;/summary&gt;
  body {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  main {
    padding: 0 30px;
  }
  h1 {
    font-size: 1.8rem;
    text-align: center;
  }
  .stepper {
    display: flex;
    justify-content: flex-end;
    padding-right: 10px;
  }
  form {
    box-shadow: 0px 0px 6px 2px rgba(0, 0, 0, 0.2);
    padding: 12px;
  }
  input,
  textarea,
  select {
    outline: none;
  }
  input:valid,
  textarea:valid,
  select:valid,
  input:focus:valid,
  textarea:focus:valid,
  select:focus:valid {
    border-color: green;
  }
  input:focus:invalid,
  textarea:focus:invalid,
  select:focus:invalid {
    border: 1px solid red;
  }
&lt;/details&gt;
</code></pre>
<h2 id="form-navigation-and-validation">Form Navigation And Validation</h2>
<p>An easy way to ruin the user experience for a multi-step form is to wait until the user gets to the last step in the form before letting them know of any error they made along the way. Each step of the form should be validated for errors before moving on to the next step, and descriptive error messages should be displayed to enable users to understand what is wrong and how to fix it.</p>
<p>Now, the only part of our form that is visible is the first step. To complete the form, users need to be able to navigate to the other steps. We are going to use several buttons to pull this off. The first step is going to have a <kbd>Next</kbd> button. The second and third steps are going to have both a <kbd>Previous</kbd> and a <kbd>Next</kbd> button, and the fourth step is going to have a <kbd>Previous</kbd> and a <kbd>Submit</kbd> button.</p>
<div class="break-out">
<pre><code class="language-html">&lt;form id="jobApplicationForm"&gt;
  &lt;!-- Step 1: Personal Information --&gt;
  &lt;fieldset&gt;
    &lt;!-- ... --&gt;
    &lt;button type="button" class="next" onclick="nextStep()"&gt;Next&lt;/button&gt;
  &lt;/fieldset&gt;

  &lt;!-- Step 2: Work Experience --&gt;
  &lt;fieldset&gt;
    &lt;!-- ... --&gt;
    &lt;button type="button" class="previous" onclick="previousStep()"&gt;Previous&lt;/button&gt;
    &lt;button type="button" class="next" onclick="nextStep()"&gt;Next&lt;/button&gt;
  &lt;/fieldset&gt;

  &lt;!-- Step 3: Skills &amp; Qualifications --&gt;
  &lt;fieldset&gt;
    &lt;!-- ... --&gt;
    &lt;button type="button" class="previous" onclick="previousStep()"&gt;Previous&lt;/button&gt;
    &lt;button type="button" class="next" onclick="nextStep()"&gt;Next&lt;/button&gt;
  &lt;/fieldset&gt;

  &lt;!-- Step 4: Review &amp; Submit --&gt;
  &lt;fieldset&gt;
    &lt;!-- ... --&gt;
    &lt;button type="button" class="previous" onclick="previousStep()"&gt;Previous&lt;/button&gt;
    &lt;button type="submit"&gt;Submit Application&lt;/button&gt;
  &lt;/fieldset&gt;
&lt;/form&gt;
</code></pre>
</div>
<p><strong>Notice</strong>: We’ve added <code>onclick</code> attributes to the <kbd>Previous</kbd> and <kbd>Next</kbd> buttons to link them to their respective JavaScript functions: <code>previousStep()</code> and <code>nextStep()</code>.</p>
<div class="partners__lead-place"></div>
<h3 id="the-next-button">The “Next” Button</h3>
<p>The <code>nextStep()</code> function is linked to the Next button. Whenever the user clicks the Next button, the <code>nextStep()</code> function will first check to ensure that all the fields for whatever step the user is on have been filled out correctly before moving on to the next step. If the fields haven’t been filled correctly, it displays some error messages, letting the user know that they’ve done something wrong and informing them what to do to make the errors go away.</p>
<p>Before we go into the implementation of the <code>nextStep</code> function, there are certain variables we need to define because they will be needed in the function. First, we need the input fields from the DOM so we can run checks on them to make sure they are valid.</p>
<div class="break-out">
<pre><code class="language-javascript">// Step 1 fields
const name = document.getElementById("name");
const email = document.getElementById("email");
const phone = document.getElementById("phone");

// Step 2 fields
const company = document.getElementById("company");
const jobTitle = document.getElementById("jobTitle");
const yearsExperience = document.getElementById("yearsExperience");

// Step 3 fields
const skills = document.getElementById("skills");
const highestDegree = document.getElementById("highestDegree");
</code></pre>
</div>
<p>Then, we’re going to need an array to store our error messages.</p>
<pre><code class="language-javascript">let errorMsgs = [];
</code></pre>
<p>Also, we would need an element in the DOM where we can insert those error messages after they’ve been generated. This element should be placed in the HTML just below the last <code>fieldset</code> closing tag:</p>
<pre><code class="language-html">&lt;div id="errorMessages" style="color: rgb(253, 67, 67)"&gt;&lt;/div&gt;
</code></pre>
<p>Add the above <code>div</code> to the JavaScript code using the following line:</p>
<pre><code class="language-javascript">const errorMessagesDiv = document.getElementById("errorMessages");
</code></pre>
<p>And finally, we need a variable to keep track of the current step.</p>
<p><code class="language-javascript">let currentStep = 1;<br />
</code></p>
<p>Now that we have all our variables in place, here’s the implementation of the <code>nextstep()</code> function:</p>
<div class="break-out">
<pre><code class="language-javascript">function nextStep() {
  errorMsgs = [];
  errorMessagesDiv.innerText = "";

  switch (currentStep) {
    case 1:
      addValidationErrors(name, email, phone);
      validateStep(errorMsgs);
      break;

    case 2:
      addValidationErrors(company, jobTitle, yearsExperience);
      validateStep(errorMsgs);
      break;

    case 3:
      addValidationErrors(skills, highestDegree);
      validateStep(errorMsgs);
      break;
  }
}
</code></pre>
</div>
<p>The moment the <kbd>Next</kbd> button is pressed, our code first checks which step the user is currently on, and based on this information, it validates the data for that specific step by calling the <code>addValidationErrors()</code> function. If there are errors, we display them. Then, the form calls the <code>validateStep()</code> function to verify that there are no errors before moving on to the next step. If there are errors, it prevents the user from going on to the next step.</p>
<p>Whenever the <code>nextStep()</code> function runs, the error messages are cleared first to avoid appending errors from a different step to existing errors or re-adding existing error messages when the <code>addValidationErrors</code> function runs. The <code>addValidationErrors</code> function is called for each step using the fields for that step as arguments.</p>
<p>Here’s how the <code>addValidationErrors</code> function is implemented:</p>
<div class="break-out">
<pre><code class="language-javascript">function addValidationErrors(fieldOne, fieldTwo, fieldThree = undefined) {
  if (!fieldOne.checkValidity()) {
    const label = document.querySelector(`label[for="${fieldOne.id}"]`);
    errorMsgs.push(`Please Enter A Valid ${label.textContent}`);
  }

  if (!fieldTwo.checkValidity()) {
    const label = document.querySelector(`label[for="${fieldTwo.id}"]`);
    errorMsgs.push(`Please Enter A Valid ${label.textContent}`);
  }

  if (fieldThree &amp;&amp; !fieldThree.checkValidity()) {
    const label = document.querySelector(`label[for="${fieldThree.id}"]`);
    errorMsgs.push(`Please Enter A Valid ${label.textContent}`);
  }

  if (errorMsgs.length &gt; 0) {
    errorMessagesDiv.innerText = errorMsgs.join("n");
  }
}
</code></pre>
</div>
<p>This is how the <code>validateStep()</code> function is defined:</p>
<pre><code class="language-javascript">function validateStep(errorMsgs) {
  if (errorMsgs.length === 0) {
    showStep(currentStep + 1);
  }
}
</code></pre>
<p>The <code>validateStep()</code> function checks for errors. If there are none, it proceeds to the next step with the help of the <code>showStep()</code> function.</p>
<pre><code class="language-javascript">function showStep(step) {
  steps.forEach((el, index) =&gt; {
    el.hidden = index + 1 !== step;
  });
  currentStep = step;
}
</code></pre>
<p>The <code>showStep()</code> function requires the four fieldsets in the DOM. Add the following line to the top of the JavaScript code to make the fieldsets available:</p>
<pre><code class="language-javascript">const steps = document.querySelectorAll(".step");
</code></pre>
<p>What the <code>showStep()</code> function does is to go through all the <code>fieldsets</code> in our form and hide whatever <code>fieldset</code> is not equal to the one we’re navigating to. Then, it updates the <code>currentStep</code> variable to be equal to the step we’re navigating to.</p>
<h3 id="the-previous-button">The “Previous” Button</h3>
<p>The <code>previousStep()</code> function is linked to the <kbd>Previous</kbd> button. Whenever the previous button is clicked, similarly to the <code>nextStep</code> function, the error messages are also cleared from the page, and navigation is also handled by the <code>showStep</code> function.</p>
<pre><code class="language-javascript">function previousStep() {
  errorMessagesDiv.innerText = "";
  showStep(currentStep - 1);
}
</code></pre>
<p>Whenever the <code>showStep()</code> function is called with “<code>currentStep - 1</code>” as an argument (as in this case), we go back to the previous step, while moving to the next step happens by calling the <code>showStep()</code> function with “<code>currentStep + 1</code>&rdquo; as an argument (as in the case of the <code>validateStep()</code> function).</p>
<div class="partners__lead-place"></div>
<h2 id="improving-user-experience-with-visual-cues">Improving User Experience With Visual Cues</h2>
<p>One other way of improving the user experience for a multi-step form, is by integrating visual cues, things that will give users feedback on the process they are on. These things can include a progress indicator or a stepper to help the user know the exact step they are on.</p>
<h3 id="integrating-a-stepper">Integrating A Stepper</h3>
<p>To integrate a stepper into our form (sort of like <a href="https://m1.material.io/components/steppers.html#">this one</a> from Material Design), the first thing we need to do is add it to the HTML just below the opening <code>&lt;form&gt;</code> tag.</p>
<pre><code class="language-html">&lt;form id="jobApplicationForm"&gt;
  &lt;div class="stepper"&gt;
    &lt;span&gt;&lt;span class="currentStep"&gt;1&lt;/span&gt;/4&lt;/span&gt;
  &lt;/div&gt;
  &lt;!-- ... --&gt;
&lt;/form&gt;
</code></pre>
<p>Next, we need to query the part of the stepper that will represent the current step. This is the span tag with the class name of <code>currentStep</code>.</p>
<pre><code class="language-javascript">const currentStepDiv = document.querySelector(".currentStep");
</code></pre>
<p>Now, we need to update the stepper value whenever the previous or next buttons are clicked. To do this, we need to update the <code>showStep()</code> function by appending the following line to it:</p>
<pre><code class="language-javascript">currentStepDiv.innerText = currentStep;
</code></pre>
<p>This line is added to the <code>showStep()</code> function because the <code>showStep()</code> function is responsible for navigating between steps and updating the <code>currentStep</code> variable. So, whenever the <code>currentStep</code> variable is updated, the currentStepDiv should also be updated to reflect that change.</p>
<h3 id="storing-and-retrieving-user-data">Storing And Retrieving User Data</h3>
<p>One major way we can improve the form’s user experience is by storing user data in the browser. Multistep forms are usually long and require users to enter a lot of information about themselves. Imagine a user filling out 95% of a form, then accidentally hitting the <kbd>F5</kbd> button on their keyboard and losing all their progress. That would be a really bad experience for the user.</p>
<p>Using <code>localStorage</code>, we can store user information as soon as it is entered and retrieve it as soon as the DOM content is loaded, so users can always continue filling out their forms from wherever they left off. To add this feature to our forms, we can begin by saving the user’s information as soon as it is typed. This can be achieved using the <code>input</code> event.</p>
<p>Before adding the <code>input</code> event listener, get the form element from the DOM:</p>
<pre><code class="language-javascript">const form = document.getElementById("jobApplicationForm");
</code></pre>
<p>Now we can add the <code>input</code> event listener:</p>
<div class="break-out">
<pre><code class="language-javascript">// Save data on each input event
form.addEventListener("input", () =&gt; {
  const formData = {
    name: document.getElementById("name").value,
    email: document.getElementById("email").value,
    phone: document.getElementById("phone").value,
    company: document.getElementById("company").value,
    jobTitle: document.getElementById("jobTitle").value,
    yearsExperience: document.getElementById("yearsExperience").value,
    skills: document.getElementById("skills").value,
    highestDegree: document.getElementById("highestDegree").value,
  };
  localStorage.setItem("formData", JSON.stringify(formData));
});
</code></pre>
</div>
<p>Next, we need to add some code to help us retrieve the user data once the DOM content is loaded.</p>
<div class="break-out">
<pre><code class="language-javascript">window.addEventListener("DOMContentLoaded", () =&gt; {
  const savedData = JSON.parse(localStorage.getItem("formData"));
  if (savedData) {
    document.getElementById("name").value = savedData.name || "";
    document.getElementById("email").value = savedData.email || "";
    document.getElementById("phone").value = savedData.phone || "";
    document.getElementById("company").value = savedData.company || "";
    document.getElementById("jobTitle").value = savedData.jobTitle || "";
    document.getElementById("yearsExperience").value = savedData.yearsExperience || "";
    document.getElementById("skills").value = savedData.skills || "";
    document.getElementById("highestDegree").value = savedData.highestDegree || "";
  }
});
</code></pre>
</div>
<p>Lastly, it is good practice to remove data from <code>localStorage</code> as soon as it is no longer needed:</p>
<pre><code class="language-javascript">// Clear data on form submit
form.addEventListener('submit', () =&gt; {
  // Clear localStorage once the form is submitted
  localStorage.removeItem('formData');
}); 
</code></pre>
<h3 id="adding-the-current-step-value-to-localstorage">Adding The Current Step Value To <code>localStorage</code></h3>
<p>If the user accidentally closes their browser, they should be able to return to wherever they left off. This means that the current step value also has to be saved in <code>localStorage</code>.</p>
<p>To save this value, append the following line to the <code>showStep()</code> function:</p>
<pre><code class="language-javascript">localStorage.setItem("storedStep", currentStep);
</code></pre>
<p>Now we can retrieve the current step value and return users to wherever they left off whenever the DOM content loads. Add the following code to the <code>DOMContentLoaded</code> handler to do so:</p>
<pre><code class="language-javascript">const storedStep = localStorage.getItem("storedStep");

if (storedStep) {
    const storedStepInt = parseInt(storedStep);
    steps.forEach((el, index) =&gt; {
      el.hidden = index + 1 !== storedStepInt;
    });
    currentStep = storedStepInt;
    currentStepDiv.innerText = currentStep;
  }
</code></pre>
<p>Also, do not forget to clear the current step value from <code>localStorage</code> when the form is submitted.</p>
<pre><code class="language-javascript">localStorage.removeItem("storedStep");
</code></pre>
<p>The above line should be added to the submit handler.</p>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>Creating multi-step forms can help improve user experience for complex data entry. By carefully planning out steps, implementing form validation at each step, and temporarily storing user data in the browser, you make it easier for users to complete long forms.</p>
<p>For the full implementation of this multi-step form, you can access the complete code on <a href="https://github.com/jimavictor/multistep-form">GitHub</a>.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2024/12/03/creating-an-effective-multistep-form-for-better-user-experience/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Build A Static RSS Reader To Fight Your Inner FOMO</title>
		<link>http://computercoursesonline.com/index.php/2024/10/07/build-a-static-rss-reader-to-fight-your-inner-fomo/</link>
					<comments>http://computercoursesonline.com/index.php/2024/10/07/build-a-static-rss-reader-to-fight-your-inner-fomo/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Mon, 07 Oct 2024 13:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=404</guid>

					<description><![CDATA[Build A Static RSS Reader To Fight Your Inner FOMO Build A Static RSS Reader To Fight Your Inner FOMO Karin Hendrikse 2024-10-07T13:00:00+00:00 2025-05-01T20:32:48+00:00 In a fast-paced industry like tech, it can be hard to deal with the fear of missing out on important news. But, as many of us know, there’s an absolutely huge...]]></description>
										<content:encoded><![CDATA[<p>              <title>Build A Static RSS Reader To Fight Your Inner FOMO</title></p>
<article>
<header>
<h1>Build A Static RSS Reader To Fight Your Inner FOMO</h1>
<address>Karin Hendrikse</address>
<p>                  2024-10-07T13:00:00+00:00<br />
                  2025-05-01T20:32:48+00:00<br />
                </header>
<p>In a fast-paced industry like tech, it can be hard to deal with the fear of missing out on important news. But, as many of us know, there’s an absolutely huge amount of information coming in daily, and finding the right time and balance to keep up can be difficult, if not stressful. A classic piece of technology like <strong>an RSS feed is a delightful way of taking back ownership of our own time</strong>. In this article, we will create a static Really Simple Syndication (RSS) reader that will bring you the latest curated news only once (yes: <em>once</em>) a day.</p>
<p>We’ll obviously work with RSS technology in the process, but we’re also going to combine it with some things that maybe you haven’t tried before, including <strong>Astro</strong> (the static site framework), <strong>TypeScript</strong> (for JavaScript goodies), a package called <strong>rss-parser</strong> (for connecting things together), as well as <strong>scheduled functions</strong> and <strong>build hooks</strong> provided by Netlify (although there are other services that do this).</p>
<p>I chose these technologies purely because I really, really enjoy them! There may be other solutions out there that are more performant, come with more features, or are simply more comfortable to you &mdash; and in those cases, I encourage you to swap in whatever you’d like. The most important thing is getting the end result!</p>
<h2 id="the-plan">The Plan</h2>
<p>Here’s how this will go. Astro generates the website. I made the intentional decision to use a static site because I want the different RSS feeds to be fetched only once during build time, and that’s something we can control each time the site is “rebuilt” and redeployed with updates. That’s where Netlify’s scheduled functions come into play, as they let us trigger rebuilds automatically at specific times. There is no need to manually check for updates and deploy them! Cron jobs can just as readily do this if you prefer a server-side solution.</p>
<p>During the triggered rebuild, we’ll let the rss-parser package do exactly what it says it does: parse a list of RSS feeds that are contained in an array. The package also allows us to set a filter for the fetched results so that we only get ones from the past day, week, and so on. Personally, I only render the news from the last seven days to prevent content overload. We’ll get there!</p>
<p>But first&hellip;</p>
<h2 id="what-is-rss">What Is RSS?</h2>
<p>RSS is a web feed technology that you can feed into a reader or news aggregator. Because RSS is standardized, you know what to expect when it comes to the feed’s format. That means we have a ton of fun possibilities when it comes to handling the data that the feed provides. Most news websites have their own RSS feed that you can subscribe to (this is <strong>Smashing Magazine’s RSS feed</strong>: <a href="https://www.smashingmagazine.com/feed/">https://www.smashingmagazine.com/feed/</a>). An RSS feed is capable of updating every time a site publishes new content, which means it can be a quick source of the latest news, but we can tailor that frequency as well.</p>
<p>RSS feeds are written in an Extensible Markup Language (XML) format and have specific elements that can be used within it. Instead of focusing too much on the technicalities here, I’ll give you a link to the <a href="https://www.rssboard.org/rss-specification">RSS specification</a>. Don’t worry; that page should be scannable enough for you to find the most pertinent information you need, like the kinds of elements that are supported and what they represent. For this tutorial, we’re only using the following elements: <strong><code>&lt;title&gt;</code></strong>, <strong><code>&lt;link&gt;</code></strong>, <strong><code>&lt;description&gt;</code></strong>, <strong><code>&lt;item&gt;</code></strong>, and <strong><code>&lt;pubDate&gt;</code></strong>. We’ll also let our RSS parser package do some of the work for us.</p>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Hey UXer, it’s time to sharpen your design leadership skills. <strong>Learn to strategize, influence, and drive cultural change</strong> even with limited resources.</p>
<p><a data-instant href="https://smashingconf.com/online-workshops/workshops/ux-design-leadership-paul-boag/" class="btn btn--green btn--large">Get your tickets now&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://smashingconf.com/online-workshops/workshops/ux-design-leadership-paul-boag/" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="400" height="400" data-src="https://smashingconf.com/.netlify/images?url=%2Fimages%2fspeakers%2fpaul-boag.jpg&amp;w=400&amp;h=400&amp;fit=cover&amp;fm=png"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="creating-the-state-site">Creating The State Site</h2>
<p>We’ll start by creating our Astro site! In your terminal run <code>pnpm create astro@latest</code>. You can use any package manager you want &mdash; I’m simply trying out <a href="https://pnpm.io">pnpm</a> for myself.</p>
<p>After running the command, Astro’s chat-based helper, Houston, walks through some setup questions to get things started.</p>
<pre><code class="language-bash"> astro   Launch sequence initiated.

   dir   Where should we create your new project?
         ./rss-buddy

  tmpl   How would you like to start your new project?
         Include sample files

    ts   Do you plan to write TypeScript?
         Yes

   use   How strict should TypeScript be?
         Strict

  deps   Install dependencies?
         Yes

   git   Initialize a new git repository?
         Yes
</code></pre>
<p>I like to use Astro’s sample files so I can get started quickly, but we’re going to clean them up a bit in the process. Let’s clean up the <code>src/pages/index.astro</code> file by removing everything inside of the <code>&lt;main&gt;&lt;/main&gt;</code> tags. Then we’re good to go!</p>
<p>From there, we can spin things by running <code>pnpm start</code>. Your terminal will tell you which localhost address you can find your site at.</p>
<div class="partners__lead-place"></div>
<h2 id="pulling-information-from-rss-feeds">Pulling Information From RSS feeds</h2>
<p>The <code>src/pages/index.astro</code> file is where we will make an array of RSS feeds we want to follow. We will be using <a href="https://docs.astro.build/en/basics/astro-syntax/">Astro’s template syntax</a>, so between the two code fences (&mdash;), create an array of <code>feedSources</code> and add some feeds. If you need inspiration, you can copy this:</p>
<pre><code class="language-javascript">const feedSources = [
  'https://www.smashingmagazine.com/feed/',
  'https://developer.mozilla.org/en-US/blog/rss.xml',
  // etc.
]
</code></pre>
<p>Now we’ll install the <a href="https://github.com/rbren/rss-parser">rss-parser package</a> in our project by running <code>pnpm install rss-parser</code>. This package is a small library that turns the XML that we get from fetching an RSS feed into JavaScript objects. This makes it easy for us to read our RSS feeds and manipulate the data any way we want.</p>
<p>Once the package is installed, open the <code>src/pages/index.astro</code> file, and at the top, we’ll import the rss-parser and instantiate the <code>Partner</code> class.</p>
<pre><code class="language-javascript">import Parser from 'rss-parser';
const parser = new Parser();
</code></pre>
<p>We use this parser to read our RSS feeds and (surprise!) <em>parse</em> them to JavaScript. We’re going to be dealing with a list of promises here. Normally, I would probably use <code>Promise.all()</code>, but the thing is, this is supposed to be a complicated experience. If one of the feeds doesn’t work for some reason, I’d prefer to simply ignore it.</p>
<p>Why? Well, because <code>Promise.all()</code> rejects everything even if only one of its promises is rejected. That might mean that if one feed doesn’t behave the way I’d expect it to, my entire page would be blank when I grab my hot beverage to read the news in the morning. I do not want to start my day confronted by an error.</p>
<p>Instead, I’ll opt to use <code>Promise.allSettled()</code>. This method will actually let all promises complete even if one of them fails. In our case, this means any feed that errors will just be ignored, which is perfect.</p>
<p>Let’s add this to the <code>src/pages/index.astro</code> file:</p>
<div class="break-out">
<pre><code class="language-typescript">interface FeedItem {
  feed?: string;
  title?: string;
  link?: string;
  date?: Date;
}

const feedItems: FeedItem[] = [];

await Promise.allSettled(
  feedSources.map(async (source) =&gt; {
    try {
      const feed = await parser.parseURL(source);
      feed.items.forEach((item) =&gt; {
        const date = item.pubDate ? new Date(item.pubDate) : undefined;
        
          feedItems.push({
            feed: feed.title,
            title: item.title,
            link: item.link,
            date,
          });
      });
    } catch (error) {
      console.error(`Error fetching feed from ${source}:`, error);
    }
  })
);
</code></pre>
</div>
<p>This creates an array (or more) named <code>feedItems</code>. For each URL in the <code>feedSources</code> array we created earlier, the rss-parser retrieves the items and, yes, parses them into JavaScript. Then, we return whatever data we want! We’ll keep it simple for now and only return the following:</p>
<ul>
<li>The feed title,</li>
<li>The title of the feed item,</li>
<li>The link to the item,</li>
<li>And the item’s published date.</li>
</ul>
<p>The next step is to ensure that all items are sorted by date so we’ll truly get the “latest” news. Add this small piece of code to our work:</p>
<div class="break-out">
<pre><code class="language-typescript">const sortedFeedItems = feedItems.sort((a, b) =&gt; (b.date ?? new Date()).getTime() - (a.date ?? new Date()).getTime());
</code></pre>
</div>
<p>Oh, and&hellip; remember when I said I didn’t want this RSS reader to render anything older than seven days? Let’s tackle that right now since we’re already in this code.</p>
<p>We’ll make a new variable called <code>sevenDaysAgo</code> and assign it a date. We’ll then set that date to seven days ago and use that logic before we add a new item to our <code>feedItems</code> array.</p>
<p>This is what the <code>src/pages/index.astro</code> file should now look like at this point:</p>
<div class="break-out">
<pre><code class="language-typescript">---
import Layout from '../layouts/Layout.astro';
import Parser from 'rss-parser';
const parser = new Parser();

const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

const feedSources = [
  'https://www.smashingmagazine.com/feed/',
  'https://developer.mozilla.org/en-US/blog/rss.xml',
]

interface FeedItem {
  feed?: string;
  title?: string;
  link?: string;
  date?: Date;
}

const feedItems: FeedItem[] = [];

await Promise.allSettled(
  feedSources.map(async (source) =&gt; {
    try {
      const feed = await parser.parseURL(source);
      feed.items.forEach((item) =&gt; {
        const date = item.pubDate ? new Date(item.pubDate) : undefined;
        if (date &amp;&amp; date &gt;= sevenDaysAgo) {
          feedItems.push({
            feed: feed.title,
            title: item.title,
            link: item.link,
            date,
          });
        }
      });
    } catch (error) {
      console.error(`Error fetching feed from ${source}:`, error);
    }
  })
);

const sortedFeedItems = feedItems.sort((a, b) =&gt; (b.date ?? new Date()).getTime() - (a.date ?? new Date()).getTime());

---

&lt;Layout title="Welcome to Astro."&gt;
  &lt;main&gt;
  &lt;/main&gt;
&lt;/Layout&gt;
</code></pre>
</div>
<div class="partners__lead-place"></div>
<h2 id="rendering-xml-data">Rendering XML Data</h2>
<p>It’s time to show our news articles on the Astro site! To keep this simple, we’ll format the items in an unordered list rather than some other fancy layout.</p>
<p>All we need to do is update the <code>&lt;Layout&gt;</code> element in the file with the XML objects sprinkled in for a feed item’s title, URL, and publish date.</p>
<pre><code class="language-html">&lt;Layout title="Welcome to Astro."&gt;
  &lt;main&gt;
  {sortedFeedItems.map(item =&gt; (
    &lt;ul&gt;
      &lt;li&gt;
        &lt;a href={item.link}&gt;{item.title}&lt;/a&gt;
        &lt;p&gt;{item.feed}&lt;/p&gt;
        &lt;p&gt;{item.date}&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  ))}
  &lt;/main&gt;
&lt;/Layout&gt;
</code></pre>
<p>Go ahead and run <code>pnpm start</code> from the terminal. The page should display an unordered list of feed items. Of course, everything is styled at the moment, but luckily for you, you can make it look exactly like you want with CSS!</p>
<p>And remember that there are even <strong>more fields available in the XML for each item</strong> if you want to display more information. If you run the following snippet in your DevTools console, you’ll see all of the fields you have at your disposal:</p>
<pre><code class="language-javascript">feed.items.forEach(item =&gt; {}
</code></pre>
<h2 id="scheduling-daily-static-site-builds">Scheduling Daily Static Site Builds</h2>
<p>We’re nearly done! The feeds are being fetched, and they are returning data back to us in JavaScript for use in our Astro page template. Since feeds are updated whenever new content is published, we need a way to fetch the latest items from it.</p>
<p>We want to avoid doing any of this manually. So, let’s set this site on Netlify to gain access to their scheduled functions that trigger a rebuild and their build hooks that do the building. Again, other services do this, and you’re welcome to roll this work with another provider &mdash; I’m just partial to Netlify since I work there. In any case, you can follow Netlify’s documentation for <a href="https://docs.netlify.com/welcome/add-new-site/#import-from-an-existing-repository">setting up a new site</a>.</p>
<p>Once your site is hosted and live, you are ready to schedule your rebuilds. A <a href="https://docs.netlify.com/configure-builds/build-hooks/">build hook</a> gives you a URL to use to trigger the new build, looking something like this:</p>
<pre><code class="language-html">https://api.netlify.com/build_hooks/your-build-hook-id
</code></pre>
<p>Let’s trigger builds every day at midnight. We’ll use Netlify’s <a href="https://docs.netlify.com/functions/scheduled-functions/">scheduled functions</a>. That’s really why I’m using Netlify to host this in the first place. Having them at the ready via the host greatly simplifies things since there’s no server work or complicated configurations to get this going. Set it and forget it!</p>
<p>We’ll install <code>@netlify/functions</code> (<a href="https://docs.netlify.com/functions/get-started/">instructions</a>) to the project and then create the following file in the project’s root directory: <code>netlify/functions/deploy.ts</code>.</p>
<p>This is what we want to add to that file:</p>
<div class="break-out">
<pre><code class="language-typescript">// netlify/functions/deploy.ts

import type { Config } from '@netlify/functions';

const BUILD_HOOK =
  'https://api.netlify.com/build_hooks/your-build-hook-id'; // replace me!

export default async (req: Request) =&gt; {
  await fetch(BUILD&#095;HOOK, {
    method: 'POST',
  })
};

export const config: Config = {
  schedule: '0 0 &#042; &#042; &#042;',
};
</code></pre>
</div>
<p>If you commit your code and push it, your site should re-deploy automatically. From that point on, it follows a schedule that rebuilds the site every day at midnight, ready for you to take your morning brew and catch up on everything that <em>you</em> think is important.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2024/10/07/build-a-static-rss-reader-to-fight-your-inner-fomo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Generating Unique Random Numbers In JavaScript Using Sets</title>
		<link>http://computercoursesonline.com/index.php/2024/08/26/generating-unique-random-numbers-in-javascript-using-sets/</link>
					<comments>http://computercoursesonline.com/index.php/2024/08/26/generating-unique-random-numbers-in-javascript-using-sets/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Mon, 26 Aug 2024 15:00:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=406</guid>

					<description><![CDATA[Generating Unique Random Numbers In JavaScript Using Sets Generating Unique Random Numbers In JavaScript Using Sets Amejimaobari Ollornwi 2024-08-26T15:00:00+00:00 2025-05-01T20:32:48+00:00 JavaScript comes with a lot of built-in functions that allow you to carry out so many different operations. One of these built-in functions is the Math.random() method, which generates a random floating-point number that can...]]></description>
										<content:encoded><![CDATA[<p>              <title>Generating Unique Random Numbers In JavaScript Using Sets</title></p>
<article>
<header>
<h1>Generating Unique Random Numbers In JavaScript Using Sets</h1>
<address>Amejimaobari Ollornwi</address>
<p>                  2024-08-26T15:00:00+00:00<br />
                  2025-05-01T20:32:48+00:00<br />
                </header>
<p>JavaScript comes with a lot of built-in functions that allow you to carry out so many different operations. One of these built-in functions is the <code>Math.random()</code> method, which generates a random floating-point number that can then be manipulated into integers.</p>
<p>However, if you wish to generate a series of unique random numbers and create more random effects in your code, you will need to come up with a custom solution for yourself because the <code>Math.random()</code> method on its own cannot do that for you.</p>
<p>In this article, we’re going to be learning how to circumvent this issue and generate a series of unique random numbers using the <code>Set</code> object in JavaScript, which we can then use to create more randomized effects in our code.</p>
<p><strong>Note</strong>: <em>This article assumes that you know how to generate random numbers in JavaScript, as well as how to work with sets and arrays.</em></p>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="/printed-books/touch-design-for-mobile-interfaces/">Touch Design for Mobile Interfaces</a></strong>, Steven Hoober’s brand-new guide on <strong>designing for mobile</strong> with proven, universal, human-centric guidelines. <strong>400 pages</strong>, jam-packed with in-depth user research and <strong>best practices</strong>.</p>
<p><a data-instant href="https://www.smashingmagazine.com/printed-books/touch-design-for-mobile-interfaces/" class="btn btn--green btn--large">Jump to table of contents&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="https://www.smashingmagazine.com/printed-books/touch-design-for-mobile-interfaces/" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="480" height="697" data-src="https://archive.smashing.media/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/b14658fc-bb2d-41a6-8d1a-70eaaf1b8ec8/touch-design-book-shop-opt.png"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="generating-a-unique-series-of-random-numbers">Generating a Unique Series of Random Numbers</h2>
<p>One of the ways to generate a unique series of random numbers in JavaScript is by using <code>Set</code> objects. The reason why we’re making use of sets is because the elements of a set are unique. We can iteratively generate and insert random integers into sets until we get the number of integers we want.</p>
<p>And since sets do not allow duplicate elements, they are going to serve as a filter to remove all of the duplicate numbers that are generated and inserted into them so that we get a set of unique integers.</p>
<p>Here’s how we are going to approach the work:</p>
<ol>
<li>Create a <code>Set</code> object.</li>
<li>Define how many random numbers to produce and what range of numbers to use.</li>
<li>Generate each random number and immediately insert the numbers into the <code>Set</code> until the <code>Set</code> is filled with a certain number of them.</li>
</ol>
<p>The following is a quick example of how the code comes together:</p>
<div class="break-out">
<pre><code class="language-javascript">function generateRandomNumbers(count, min, max) {
  // 1: Create a `Set` object
  let uniqueNumbers = new Set();
  while (uniqueNumbers.size &lt; count) {
    // 2: Generate each random number
    uniqueNumbers.add(Math.floor(Math.random() &#042; (max - min + 1)) + min);
  }
  // 3: Immediately insert them numbers into the Set...
  return Array.from(uniqueNumbers);
}
// ...set how many numbers to generate from a given range
console.log(generateRandomNumbers(5, 5, 10));
</code></pre>
</div>
<p>What the code does is create a new <code>Set</code> object and then generate and add the random numbers to the set until our desired number of integers has been included in the set. The reason why we’re returning an array is because they are easier to work with.</p>
<p>One thing to note, however, is that the number of integers you want to generate (represented by <code>count</code> in the code) should be less than the upper limit of your range plus one (represented by <code>max + 1</code> in the code). Otherwise, the code will run forever. You can add an <code>if statement</code> to the code to ensure that this is always the case:</p>
<div class="break-out">
<pre><code class="language-javascript">function generateRandomNumbers(count, min, max) {
  // if statement checks that `count` is less than `max + 1`
  if (count &gt; max + 1) {
    return "count cannot be greater than the upper limit of range";
  } else {
    let uniqueNumbers = new Set();
    while (uniqueNumbers.size &lt; count) {
      uniqueNumbers.add(Math.floor(Math.random() &#042; (max - min + 1)) + min);
    }
    return Array.from(uniqueNumbers);
  }
}
console.log(generateRandomNumbers(5, 5, 10));
</code></pre>
</div>
<div class="partners__lead-place"></div>
<h2 id="using-the-series-of-unique-random-numbers-as-array-indexes">Using the Series of Unique Random Numbers as Array Indexes</h2>
<p>It is one thing to generate a series of random numbers. It’s another thing to use them.</p>
<p>Being able to use a series of random numbers with arrays unlocks so many possibilities: you can use them in shuffling playlists in a music app, randomly sampling data for analysis, or, as I did, <a href="https://github.com/jimavictor/remoji">shuffling the tiles in a memory game</a>.</p>
<p>Let’s take the code from the last example and work off of it to return random letters of the alphabet. First, we’ll construct an array of letters:</p>
<div class="break-out">
<pre><code class="language-javascript">const englishAlphabets = [
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
];

// rest of code
</code></pre>
</div>
<p>Then we <code>map</code> the letters in the range of numbers:</p>
<div class="break-out">
<pre><code class="language-javascript">const englishAlphabets = [
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
];

// generateRandomNumbers()

const randomAlphabets = randomIndexes.map((index) =&gt; englishAlphabets[index]);
</code></pre>
</div>
<p>In the original code, the <code>generateRandomNumbers()</code> function is logged to the console. This time, we’ll construct a new variable that calls the function so it can be consumed by <code>randomAlphabets</code>:</p>
<div class="break-out">
<pre><code class="language-javascript">const englishAlphabets = [
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
];

// generateRandomNumbers()

const randomIndexes = generateRandomNumbers(5, 0, 25);
const randomAlphabets = randomIndexes.map((index) =&gt; englishAlphabets[index]);
</code></pre>
</div>
<p>Now we can log the output to the console like we did before to see the results:</p>
<div class="break-out">
<pre><code class="language-javascript">const englishAlphabets = [
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
];

// generateRandomNumbers()

const randomIndexes = generateRandomNumbers(5, 0, 25);
const randomAlphabets = randomIndexes.map((index) =&gt; englishAlphabets[index]);
console.log(randomAlphabets);
</code></pre>
</div>
<p>And, when we put the <code>generateRandomNumbers</code><code>()</code> function definition back in, we get the final code:</p>
<div class="break-out">
<pre><code class="language-javascript">const englishAlphabets = [
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
];
function generateRandomNumbers(count, min, max) {
  if (count &gt; max + 1) {
    return "count cannot be greater than the upper limit of range";
  } else {
    let uniqueNumbers = new Set();
    while (uniqueNumbers.size &lt; count) {
      uniqueNumbers.add(Math.floor(Math.random() &#042; (max - min + 1)) + min);
    }
    return Array.from(uniqueNumbers);
  }
}
const randomIndexes = generateRandomNumbers(5, 0, 25);
const randomAlphabets = randomIndexes.map((index) =&gt; englishAlphabets[index]);
console.log(randomAlphabets);
</code></pre>
</div>
<p>So, in this example, we created a new array of alphabets by randomly selecting some letters in our <code>englishAlphabets</code> array.</p>
<p>You can pass in a count argument of <code>englishAlphabets.length</code> to the <code>generateRandomNumbers</code> function if you desire to shuffle the elements in the <code>englishAlphabets</code> array instead. This is what I mean:</p>
<pre><code class="language-javascript">generateRandomNumbers(englishAlphabets.length, 0, 25);
</code></pre>
<div class="partners__lead-place"></div>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>In this article, we’ve discussed how to create randomization in JavaScript by covering how to generate a series of unique random numbers, how to use these random numbers as indexes for arrays, and also some practical applications of randomization.</p>
<p>The best way to learn anything in software development is by consuming content and reinforcing whatever knowledge you’ve gotten from that content by practicing. So, don’t stop here. Run the examples in this tutorial (if you haven’t done so), play around with them, come up with your own unique solutions, and also don’t forget to share your good work. Ciao!</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2024/08/26/generating-unique-random-numbers-in-javascript-using-sets/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
