Skip to content

Reactivity in Svelte

Reactivity in Svelte

Keeping your application in sync with its state is one of the most important features that a framework can provide. In this post, we'll learn about how reactivity works in Svelte, and avoid common issues when using it.

Let's start a new application to explain how it works.

npm init @vitejs/app

✔ Project name: · svelte-reactivity
✔ Select a framework: · svelte
✔ Select a variant: · svelte-ts

cd svelte-reactivity
pnpm install //use the package manager you prefer
pnpm run dev

We will remove everything we have in our App.svelte component an replace it with the following:

<!-- App.svelte -->
<script lang="ts">
	let language: 'es'|'en' = 'en';

	function toggleLanguage() {
		language = language === 'en' ? 'es' : 'en';
	}
</script>

<main>
	<p>{language}</p>
	<button on:click={toggleLanguage}>Toggle Language</button>
</main>
Reactivity_Svelte_01

We added a button with an event handler responsible for toggling our variable with two values en and es. We can see that the value is updated every time we click the button.

In Svelte, the DOM is updated when an assignment is made. In this example, language is assigned with the result of language === 'en' ? 'es' : 'en'. Behinds the scenes, Svelte will take care of rerendering the value of language when the assignment happens.

If we take a look at the compiled code we will find this.

/* App.svelte generated by Svelte v3.38.3 */
// ...

function instance($$self, $$props, $$invalidate) {
	let language = "en";

	function toggleLanguage() {
		$$invalidate(0, language = language === "en" ? "es" : "en");
	}

	return [language, toggleLanguage];
}

// ...

We can see that our toggleLanguage function looks a bit different, wrapping the assignment with the $$invalidate method.

Let's make a few more changes to our file to see how assignment affects reactivity and rerendering.

<!-- App.svelte -->
<script lang="ts">
	let testArray = [0]

	function pushToArray(){
		testArray.push(testArray.length)
	}

	function assignToArray(){
		testArray = [...testArray, testArray.length]
	}
</script>
<main>
	<p>{testArray}</p>
	<button on:click={pushToArray}>Push To Array</button>
	<button on:click={assignToArray}>Assign To Array</button>
</main>
Reactivity_Svelte_02
Reactivity_Svelte_03

Whenever we click on the Assign To Array Button, the DOM is updated with the new value. When we try to get the same result by mutating the array, the DOM is not updated, but the app state is. We can verify that when we later click the Assignment button and the DOM is updated, showing the actual state of testArray.

Let's inspect the generated code once again.

function instance($$self, $$props, $$invalidate) {
	let testArray = [0];

	function pushToArray() {
		testArray.push(testArray.length);
	}

	function assignToArray() {
		$$invalidate(0, testArray = [...testArray, testArray.length]);
	}

	return [testArray, pushToArray, assignToArray];
}

If you compare both functions, we can now see that only the assignment will call the $$invalidate method, while the other one calls the expression as is.

This doesn't mean we cannot mutate arrays and force a rerender. We need to use an assignment after mutation to do it.

<!-- App.svelte -->
<script lang="ts">
	//...

	function pushToArray(){
		testArray.push(testArray.length)
		testArray = testArray
	}

	//...
</script>

Our complied function will be updated to:

function pushToArray() {
	testArray.push(testArray.length);
	$$invalidate(0, testArray);
}

which will update the DOM when called($$invalidate method wraps the expression, that is simplified to testArray instead of testArray = testArray)

Reactivity_Svelte_04

Reactive Variables

Imagine our team decided that we need to add a second array where each value is squared. If we were doing it imperatively, this would mean we need to update the second array each time the first one changes. The previous example would look like this.

<!-- App.svelte -->
<script lang="ts">
	let testArray = [0]
	let squared = [0]

	function pushToArray(){
		testArray.push(testArray.length)
		testArray = testArray
		squared = testArray.map(value => value*value)
	}

	function assignToArray(){
		testArray = [...testArray, testArray.length]
		squared = testArray.map(value => value*value)
	}
</script>
<main>
	<p>{testArray}</p>
	<p>{squared}</p>
	<!-- ... -->
</main>
Reactivity_Svelte_05

If we check the generated code again, we'll see that we are invalidating both arrays every time.

function pushToArray() {
	testArray.push(testArray.length);
	$$invalidate(0, testArray);
	$$invalidate(1, squared = testArray.map(value => value * value));
}

function assignToArray() {
	$$invalidate(0, testArray = [...testArray, testArray.length]);
	$$invalidate(1, squared = testArray.map(value => value * value));
}

Unfortunately, this approach has a problem. We need to keep track of every place where testArray is modified, and also update the squared array.

If we think about this problem reactively, we only need to listen to changes in testArray.

In Svelte, there's a special way to do this. Instead of declaring a variable with let, we will use $:. This is a labeled statement (it's valid JS), and it's used by the compiler to let it know that a reactive variable is being declared, and it depends on all the variables that are added to the expression. In our example:

<script lang="ts">
  let testArray = [0];
  $: squared = testArray.map(value => value * value)

  function pushToArray() {
    testArray.push(testArray.length);
    testArray = testArray;
  }

  function assignToArray() {
    testArray = [...testArray, testArray.length];
  }
</script>

Using this reactive approach, we need to handle changes to testArray exclusively. The compiler will detect that there's a dependency in testArray to calculate the actual value of squared.

If you run the app again, the same behavior is achieved.

How did this happen? Let's look at our compiled code.

	$$self.$$.update = () => {
		if ($$self.$$.dirty & /*testArray*/ 1) {
			$: $$invalidate(1, squared = testArray.map(value => value * value));
		}
	};

The internal property update is now assigned to a function that will check if the instance has changed, and that invalidate squared if the condition is met.

Every other reactive variable we add to our component will add a new block that will check if a dependency changed, and invalidate the declared variable. For example:

<script lang="ts">
  let testArray = [0];
  let multiplier = 5
  $: squared = testArray.map(value => value * value)
	// if ($$self.$$.dirty & /*testArray*/ 1) {
	//	 $: $$invalidate(1, squared = testArray.map(value => value * value));
	// }
  $: squaredTwice = squared.map(value => value * value)
	// if ($$self.$$.dirty & /*squared*/ 2) {
	//   $: squaredTwice = squared.map(value => value * value);
	// }
  $: multiplied: squaredTwice.map(value => value * multiplier)
	// if ($$self.$$.dirty & /*squaredTwice, multiplier*/ 34) {
	//   $: multiplied = squaredTwice.map(value => value * multiplier);
	// }

</script>
<!-- ... -->

The last declaration, however, depends on two variables, squareTwice and multiplier. You can tell by the comment in the if condition.

Our updated component now looks like this:

<script lang="ts">
  let testArray = [0];
  let multiplier = 5;

  $: squared = testArray.map((value) => value * value);
  $: squaredTwice = squared.map((value) => value * value);
  $: multiplied = squaredTwice.map((value) => value * multiplier);

  function pushToArray() {
    testArray.push(testArray.length);
    testArray = testArray;
  }

  function assignToArray() {
    testArray = [...testArray, testArray.length];
  }
</script>

<main>
  <p>{testArray}</p>
  <p>{squared}</p>
  <p>{squaredTwice}</p>
  <p>{multiplied}</p>
  <button on:click={pushToArray}>Push To Array</button>
  <button on:click={assignToArray}>Assign To Array</button>
  <button on:click={() => multiplier = multiplier + 1}>Multiplier</button>
</main>

I added a button to add 1 to multiplier to verify that the multiplied array is also depending on it.

Reactivity_Svelte_06

Reactive Statements

Reactivity is not limited to variable declarations. Using the same $: pattern we can create reactive statements. For example, we could add an if statement or add a try-catch block.

Let's try the following:

<script lang="ts">
  //...
  let error = null;
  //...
  $: try {
    if (multiplier > 8) {
      throw 'boo';
    }
  } catch (e) {
    error = e;
  }
  //...
</script>

<main>
  <!-- ... -->
  {#if error}
    <p>{error}</p>
  {/if}
  <!-- ... -->
</main>

Looking at the generated code we can see the same pattern as before:

if ($$self.$$.dirty & /*multiplier*/ 2) {
	$: try {
		if (multiplier > 8) {
			throw "boo";
		}
	} catch(e) {
		$$invalidate(4, error = e);
	}
}

The compiler recognizes how the statement depends on changes to multiplier and that invalidating error is a possibility.

Store auto-subscription

A store is defined as an object that implements the following contract (at a minimum): store = { subscribe: (subscription: (value: any) => void) => (() => void), set?: (value: any) => void } Stores are beyond the scope of this post but they will make possible to listen for changes to a piece of your app state. Then, we can translate this event (when the store emits a new value) into an assignment that, as we mentioned before, will update our DOM. For example:

// stores.ts
import { writable } from 'svelte/store';
export const storeArray = writable([0]);
<!-- App.svelte -->
<script lang="ts">
  import { onDestroy } from 'svelte';
  import { storeArray } from './stores';

  let testArray;
  const unsubscribe = storeArray.subscribe((value) => {
    testArray = value;
  });
  function addValueToArray() {
    storeArray.update((value) => [...value, value.length]);
  }
  onDestroy(unsubscribe);
</script>

<main>
  <p>{testArray}</p>
  <button on:click={addValueToArray}>Add Value</button>
</main>

Whenever we update, or set our store, a new value will be emitted and assigned to testArray.

We can confirm that we are calling $$invalidate in the compiled code.

const unsubscribe = storeArray.subscribe(value => {
		$$invalidate(0, testArray = value);
	});
Reactivity_Svelte_08

But there's another way to achieve this with auto-subscriptions.

Our component now becomes this:

<script lang="ts">
  import { storeArray } from './stores';
  function addValueToArray() {
    storeArray.update((value) => [...value, value.length]);
  }
</script>

<main>
  <p>{$storeArray}</p>
  <button on:click={addValueToArray}>Add Value</button>
</main>

Looking at auto subscriptions. There's no assignment in it, but our DOM is updated when we update the array. How is this achieved?

Let's analyze the output code:

function instance($$self, $$props, $$invalidate) {
	let $storeArray;
	component_subscribe($$self, storeArray, $$value => $$invalidate(0, $storeArray = $$value));

	function addValueToArray() {
		storeArray.update(value => [...value, value.length]);
	}

	return [$storeArray, addValueToArray];
}

We can see that we are calling component_subscribe with three parameters: the component, the store, and a callback function, which is invalidating our $storeArray variable.

If we go deeper and check what component_subscribe is doing underneath, we'll find the following:

export function subscribe(store, ...callbacks) {
	if (store == null) {
		return noop;
	}
	const unsub = store.subscribe(...callbacks);
	return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}

export function component_subscribe(component, store, callback) {
	component.$$.on_destroy.push(subscribe(store, callback));
}

... Which is doing the same as the original code.

It subscribes to the store, and returns a unsubscribe method (or an object with an unsubscribe method), and calls it when the component is destroyed. When a new value is emitted, the callback is executed ($$invalidate), assigning the emitted value to the auto-subscribe variable.

Common issues

  • Remember that we need an assignment to call $$invalidate mark the component instance as dirty and run all the checks. =, ++, --, +=, -= are all considered assignments.

  • When working with objects, the assignment must include the name of the variable referenced in the template. For example:

<script>
  let foo = { bar: { baz: 1 } };
  let foo2 = foo;
  function addOne() {
    foo2.bar.baz++;
  }
  function refreshFoo() {
    foo = foo;
  }
</script>
<p>foo: {JSON.stringify(foo, null, 2)}</p>
<p>foo2: {JSON.stringify(foo2, null, 2)}</p>

<button on:click={addOne}> add 1 </button>

<button on:click={refreshFoo}> Refresh foo </button>

When adding 1 to foo2.bar.baz the compiler only knows that it must update references to foo2 in the templates, but it will not update references to foo event if it changes too (they're the same object). When calling refreshFoo we are manually invalidating foo Reactivity_Svelte_07

  • when mutating arrays, be mindful of adding an assignment at the end to let the compiler know that it must update the template references.

Wrapping up

In general, whenever an assignment is made it will compile into an $$invalidate method that will mark the component as dirty and apply the required changes to the DOM. If there's any reactive variable (or statement) it will check if the component is marked as dirty and if any of its dependencies changed (because it has been invalidated), if that's the case then it will also invalidate it. Store auto subscription creates an assignment that invalidates the $ prepended variable when the store emits a new value.

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

Understanding Vue's Reactive Data cover image

Understanding Vue's Reactive Data

Introduction Web development has always been about creating dynamic experiences. One of the biggest challenges developers face is managing how data changes over time and reflecting these changes in the UI promptly and accurately. This is where Vue.js, one of the most popular JavaScript frameworks, excels with its powerful reactive data system. In this article, we dig into the heart of Vue's reactivity system. We unravel how it perfectly syncs your application UI with the underlying data state, allowing for a seamless user experience. Whether new to Vue or looking to deepen your understanding, this guide will provide a clear and concise overview of Vue's reactivity, empowering you to build more efficient and responsive Vue 3 applications. So, let’s kick off and embark on this journey to decode Vue's reactive data system. What is Vue's Reactive Data? What does it mean for data to be ”'reactive”? In essence, when data is reactive, it means that every time the data changes, all parts of the UI that rely on this data automatically update to reflect these changes. This ensures that the user is always looking at the most current state of the application. At its core, Vue's Reactive Data is like a superpower for your application data. Think of it like a mirror - whatever changes you make in your data, the user interface (UI) reflects these changes instantly, like a mirror reflecting your image. This automatic update feature is what we refer to as “reactivity”. To visualize this concept, let's use an example of a simple Vue application displaying a message on the screen: ` In this application, 'message' is a piece of data that says 'Hello Vue!'. Let's say you change this message to 'Goodbye Vue!' later in your code, like when a button is clicked. ` With Vue's reactivity, when you change your data, the UI automatically updates to 'Goodbye Vue!' instead of 'Hello Vue!'. You don't have to write extra code to make this update happen - Vue's Reactive Data system takes care of it. How does it work? Let's keep the mirror example going. Vue's Reactive Data is the mirror that reflects your data changes in the UI. But how does this mirror know when and what to reflect? That's where Vue's underlying mechanism comes into play. Vue has a behind-the-scenes mechanism that helps it stay alerted to any changes in your data. When you create a reactive data object, Vue doesn't just leave it as it is. Instead, it sends this data object through a transformation process and wraps it up in a Proxy. Proxy objects are powerful and can detect when a property is changed, updated, or deleted. Let's use our previous example: ` Consider our “message” data as a book in a library. Vue places this book (our data) within a special book cover (the Proxy). This book cover is unique - it's embedded with a tracking device that notifies Vue every time someone reads the book (accesses the data) or annotates a page (changes the data). In our example, the reactive function creates a Proxy object that wraps around our state object. When you change the 'message': ` The Proxy notices this (like a built-in alarm going off) and alerts Vue that something has changed. Vue then updates the UI to reflect this change. Let’s look deeper into what Vue is doing for us and how it transforms our object into a Proxy object. You don't have to worry about creating or managing the Proxy; Vue handles everything. ` In the example above, we encapsulate our object, in this case, “state”, converting it into a Proxy object. Note that within the second argument of the Proxy, we have two methods: a getter and a setter. The getter method is straightforward: it merely returns the value, which in this instance is “state.message” equating to 'Hello Vue!' Meanwhile, the setter method comes into play when a new value is assigned, as in the case of “state.message = ‘Hey young padawan!’”. Here, “value” becomes our new 'Hey young padawan!', prompting the property to update. This action, in turn, triggers the reactivity system, which subsequently updates the DOM. Venturing Further into the Depths If you have been paying attention to our examples above, you might have noticed that inside the Proxy method, we call the functions track and trigger to run our reactivity. Let’s try to understand a bit more about them. You see, Vue 3 reactivity data is more about Proxy objects. Let’s create a new example: ` In this example, when you click on the button, the message's value changes. This change triggers the effect function to run, as it's actively listening for any changes in its dependencies. How does the effect property know when to be called? Vue 3 has three main functions to run our reactivity: effect, track, and trigger. The effect function is like our supervisor. It steps in and takes action when our data changes – similar to our effect method, we will dive in more later. Next, we have the track function. It notes down all the important data we need to keep an eye on. In our case, this data would be state.message. Lastly, we've got the trigger function. This one is like our alarm bell. It alerts the effect function whenever our important data (the stuff track is keeping an eye on) changes. In this way, trigger, track, and effect work together to keep our Vue application reacting smoothly to changes in data. Let’s go back to them: ` Tracking (Dependency Collection) Tracking is the process of registering dependencies between reactive objects and the effects that depend on them. When a reactive property is read, it's "tracked" as a dependency of the current running effect. When we execute track(), we essentially store our effects in a Set object. But what exactly is an "effect"? If we revisit our previous example, we see that the effect method must be run whenever any property changes. This action — running the effect method in response to property changes — is what we refer to as an "Effect"! (computed property, watcher, etc.) > Note: We'll outline a basic, high-level overview of what might happen under the hood. Please note that the actual implementation is more complex and optimized, but this should give you an idea of how it works. Let’s see how it works! In our example, we have the following reactive object: ` We need a way to reference the reactive object with its effects. For that, we use a WeakMap. Which type is going to look something like this: ` We are using a WeakMap to set our object state as the target (or key). In the Vue code, they call this object targetMap. Within this targetMap object, our value is an object named depMap of Map type. Here, the keys represent our properties (in our case, that would be message and showSword), and the values correspond to their effects – remember, they are stored in a Set that in Vue 3 we refer to as dep. Huh… It might seem a bit complex, right? Let's make it more straightforward with a visual example: With the above explained, let’s see what this Track method kind of looks like and how it uses this targetMap. This method essentially is doing something like this: ` At this point, you have to be wondering, how does Vue 3 know what activeEffect should run? Vue 3 keeps track of the currently running effect by using a global variable. When an effect is executed, Vue temporarily stores a reference to it in this global variable, allowing the track function to access the currently running effect and associate it with the accessed reactive property. This global variable is called inside Vue as activeEffect. Vue 3 knows which effect is assigned to this global variable by wrapping the effects functions in a method that invokes the effect whenever a dependency changes. And yes, you guessed, that method is our effect method. ` This method behind the scenes is doing something similar to this: ` The handling of activeEffect within Vue's reactivity system is a dance of careful timing, scoping, and context preservation. Let’s go step by step on how this is working all together. When we run our Effect method for the first time, we call the get trap of the Proxy. ` When running the get trap, we have our activeEffect so we can store it as a dependency. ` This coordination ensures that when a reactive property is accessed within an effect, the track function knows which effect is responsible for that access. Trigger Method Our last method makes this Reactive system to be complete. The trigger method looks up the dependencies for the given target and key and re-runs all dependent effects. ` Conclusion Diving into Vue 3's reactivity system has been like unlocking a hidden superpower in my web development toolkit, and honestly, I've had a blast learning about it. From the rudimentary elements of reactive data and instantaneous UI updates to the intricate details involving Proxies, track and trigger functions, and effects, Vue 3's reactivity is an impressively robust framework for building dynamic and responsive applications. In our journey through Vue 3's reactivity, we've uncovered how this framework ensures real-time and precise updates to the UI. We've delved into the use of Proxies to intercept and monitor variable changes and dissected the roles of track and trigger functions, along with the 'effect' method, in facilitating seamless UI updates. Along the way, we've also discovered how Vue ingeniously manages data dependencies through sophisticated data structures like WeakMaps and Sets, offering us a glimpse into its efficient approach to change detection and UI rendering. Whether you're just starting with Vue 3 or an experienced developer looking to level up, understanding this reactivity system is a game-changer. It doesn't just streamline the development process; it enables you to create more interactive, scalable, and maintainable applications. I love Vue 3, and mastering its reactivity system has been enlightening and fun. Thanks for reading, and as always, happy coding!...

How to add Dark Mode in your Web Application  cover image

How to add Dark Mode in your Web Application

How to Create Dark/Light Mode in your JavaScript project? Providing our visitors with the ability to toggle between white and dark themes has become one of the most important features of all time. In this article, we will cover the requirements to develop a persistent theme for your website successfully. The code provided as part of this article will be technology agnostic and can be used with all major front-end frameworks such as Vue, React, or Svelte. TL;DR If you are in a rush and just want access to the code, you can grab it from this Stackblitz: Light / Dark theme functionality in Vanilla JS. What is White/Dark Mode Before we jump into the code, we should first explain what the meaning behind the feature name "White/Dark Mode is". This feature was first introduced in our Operating system by allowing users to change their default theme into Dark Mode, After this, Dark Mode has slowly made its way into our most used applications and websites as users expected to have a consistent look from their native application to Web App equivalent. Providing "White/Dark mode" in your site means giving the user the ability to toggle their theme to either align to the Operating system theme settings or override it from the Web App UI. The above example shows the feature on thisdot.co triggered by the little toggle button available within the main navbar. Implementing Dark Mode with Javascript This feature has two main implementation levels. The first is the ability to assign the theme directly from the Operating System preferences, while the second combines the feature of the first plus the ability to override that feature from the UI. Some websites just offer the first option and allow the site to emulate the OS preferences, even if this is mostly fine for most users, in this post, we will develop the second option as it is the most complicated approach and, therefore the most complete. The development of this feature is going to be divided into the following steps: - Ensure CSS variables drive our application - Create a second set of variables for a dark theme - Create logic to read user preferences - Create logic to override the theme preference - Make our theme selection persistent Let's dive into the code with our first step and initialize our application. Move all styles into CSS variables We are going to start our application by initializing a plain VanillaJs Stackblitz. This creates a simple project with an index.js file that includes our HTML and a style.css file that includes our global styles. To be able to have a good starting point for our post, we are going to add some more elements on the main page of the app and then define some CSS variables to make our example more appealing. First, we are going to clean our index.js and just leave the style import: ` Then, we are going to change our index.html with the following content: ` And finally, define some CSS variables and styles in style.css: ` If you are new to CSS variables, you can read more about them in the MDN docs: Using CSS custom properties. There is one point to clarify regarding CSS variables and their naming convention. Because the color held within our variables will change (for example from red to blue), the variable name should be based on the color "role" (eg primary, shadow, border") and not on the actual color "blue, green". In our example, we can see this naming convention in action as the variables are called "primary", "secondary" and "background". The above should output a very simple but colorful design: Create a second set of variables for the dark theme Now that our basic application is set, it is time to create a second set of variables to be able to define the difference between the light and dark theme. As you may notice, the CSS variables are not a must for this to work, but having all the variables set makes the maintenance of the site much easier than having to update color and styles scattered around. We are returning to our style.css file and updating a few of the CSS properties. These variables will just be updated if the body has a specific class dark associated with it: ` As the above code shows, because we are just overriding the variables, we do not actually need to redeclare them all and we can just declare the variables that we wish to update. If we would go back and add a class of "dark" to our body in index.html we would see the following output: As you can see, both white themes are fully set up and working. All that is left is creating the logic that can handle the theme toggle, but before we do so, there is one more change to add to our CSS. In fact, some components, such as form controls and scrollbar, use a property called "color-scheme" to change their theme, to give this a try, we will add an in our HTML: ` Then, let's see how this would look in our design: The input is showing a white theme even if the dark theme is selected, and this is happening because we are not changing the "color-scheme" that is what is used for the form input. Let's fix this by adding another CSS variable and a CSS declaration within our body: ` With the above code, the "color-scheme" variable will be changed to align with our overall theme, as shown below: Note that settings can be overridden on a component-by-component level. In fact, if you would like to always display the white theme for a button or field, you can just redeclare the "color-scheme" and apply it to the CSS selection of your choice. It is now time to move forward and include some JS to make the toggling automatic. Create logic to read user preferences It is now time to write some JavaScript and make our application dynamic. In this first logical step, we are going to read our Operating System color preference and use it to toggle the class assigned to our body. To do so, we are going to use the "matchMedia" method to read the "prefer-color-scheme" just like shown in the code below: ` The next step is to assign or remove the class from our body depending on the preferred scheme: ` Let's see if this works. If you are on Mac you can change the preferences by following the following steps: 1) System settings 2) Appearance 3) Change the appearance If you change the preferences and refresh your application, you should be able to see the application changing on the fly. Create logic to override the theme preference Having to change the full OS just for a single Website theme preference may be a little bit too much. So, in this step, we are going to enhance our logic to be able to override the preferences. First, we are going to add a simple button in the HTML called Toggle Theme: ` Now, it is time to update the logic. First, we need to allow a way to override the preferences. We can do this by adding an argument to our updateScheme function: ` The above code just adds a global variable called selectedTheme and then, as we previously mentioned, adds a parameter of overrideScheme to our method and finally makes sure we use this variable if available selectedTheme = overriddenScheme || preferredScheme. The next step requires us to be able to update our selectedTheme on a button click: ` With the above code added to our application, we could override the system preferences, but unfortunately, our work is not completed. The logic we wrote above is not persistent, and the theme selection will be lost if we refresh the page. Make our theme selection persistent In this last step, we are going to save our selectedTheme in localStorage using the documentation provided in the MDN documentation: Local Storage. First, let's save the selectedTheme into local storage by using localStorage.setItem: ` Then make sure we read this value on load using localStorage.getItem: ` After the above changes, you should be able to toggle the theme, refresh the page and be able to see the correct theme loaded up for you. NOTE: Due to the way StackBlitz loads the JS, there will be a small flash, but if you would place this code in the head that flash should be extremely minimal. What about the images You may have noticed that we have left the image unchanged between the different themes. This was, unfortunately not by choice, as there is no native way to swap images when using class-based theming. If you were just to implement the theme based on System Preference, you could declare different images using the media query (prefers-color-scheme: dark). This would allow you to declare images like this: ` The above code will show day.jpg if your theme settings are light or night.jpg if dark. If you would like to implement something similar with a class-based theme, you will have to create a custom image component in your framework of choice that loads the correct theme, but this is outside this tutorial's scope. Conclusion It is now time to conclude this post and leave you to go and apply what we learned in your Web applications. Implementing this feature is quite simple, and it helps to improve your user's experience. The code we have written above is available on StackBlitz: Light / Dark theme functionality in Vanilla JS. But let's see it in action in: So, in this article, we have introduced CSS variables and ensured our design colors were driven solely by them. We created an override for this variable using a class-based approach; we then learned how some elements, such as input fields, can change the theme using the "color-theme" property. We then moved into the JS, where we used the media query "prefers-color-scheme" to read the user Operating System theme preferences and, last but not least, created a live toggle that would create and save a personal preference in LocalStorage....

Testing with Vitest cover image

Testing with Vitest

Vitest is a new testing framework powered by Vite. It's still in development, and some features may not be ready yet, but it's a nice alternative to try and explore. Setup Let's create a new Vite Project! Note: Vitest requires Vite >=v2.7.10 and Node >=v14 to work. ` With our project created, we now need to install all the dependencies required for Vitest to work. ` I added jsdom to be able to mock the DOM API. By default, Vitest will use the configuration from vite.config.ts. I will add one modification to it, and it's svelte specific. Disabling Svelte's hot module replacement when running tests. It should look like the following: ` I'm using the VITEST env variable to differentiate when running tests, but if your configuration is too different, you can use another configuration file for tests. There are a couple of options to do this. * Create a configuration file named vitest.config.ts: it will take precedence when running tests * Using the --config flag: use it like npx vitest --config Writing tests Let's write some tests for the Counter component created by default with our project. ` To write our first set of tests, let's create a file named Counter.spec.ts next to our component. ` Adding the comment line @vitest-environment jsdom at the top of the file will allow us to mock the DOM APIs for all of the tests in the file. However, doing this in every file can be avoided via the config file. We can also make sure that we import describe, it, expect as globals. We do this via the config file. We need to make the types available by adding vitest/globals types in your tsconfig.json file (you can skip this if not using TypeScript). ` ` Our test files don't need to import globals now, and we can remove the jsdom environment setup. ` Commands There are four commands to run from the cli: * dev: run vitest in development mode * related: runs tests for a list of source files * run: run tests once * watch: default mode, same as running vitest. Watches for changes and then reruns the tests. test/suite modifiers There are modifiers for tests and suites that will change how your tests run. * .only will focus on one or more tests, skipping the rest. For suites, it will focus on all the tests in it. * .skip will skip the specified test/suite. * .todo will mark a test or suite to be implemented later. * .concurrently will run contiguous tests marked as concurrent in parallel. For suites, it will run all tests in it in parallel. This modifier can be combined with the previous ones. For example: it.concurrently.todo("do something async") Assertions Vitest ships with chai and jest compatible assertions ` For a list of available assertions, check the API docs. Coverage For coverage reports, we will need to install c8 and run the tests with the --coverage flag ` This will give us a nice coverage report. An output folder coverage will be created at the root of the project. You can specify the desired output type in the configuration file. ` UI You can also run vitest using a UI, which can help you visualize what tests you are running, and their results. Let's install the required package, and run it with the --ui flag. ` I like this interface. It even allows you to read the test code and open it in your editor. More features Vitest comes with many more features, like snapshot testing, mocking, fake timers, and more that you may know from other testing libraries. Migrating to Vitest (from a Vite project using jest) If you are working on a small project or just starting one, you may need to adapt your config file and that would be it. If you are using mock functions, Vitest uses TinySpy and for fake timers, it uses @sinonjs/fake-timers. Check for compatibility. Also, remember to import {vi} from vitest if you will be using it. Another thing that you may need to configure is a setup file. For example, to use jest-dom matchers, we can create a setup file. ` and declare it on our config file. ` Here is an example of the migration of VitePress to Vitest. (There are some ts-config changes but you can see where vitest is added, and the vitest.config.ts file) Final Thoughts Even though Vitest is still in development, it looks very promising, and the fact that they kept the API so similar to Jest, makes the migration very smooth. It also ships with TypeScript support (no external types package). Using the same config file (by default) lets you focus on writing tests very quickly. I look forward to v1.0.0...

OAuth2 for JavaScript Developers cover image

OAuth2 for JavaScript Developers

OAuth2 for JavaScript Developers OAuth is a staple of modern web development. It's the magic behind the "Log in with Facebook" or "Connect to Google" buttons you see everywhere. But what exactly is it, and how does it work, especially for a JavaScript developer? Let's dive in, using GitHub as our primary example. What is OAuth? At its core, OAuth (Open Authorization) is a protocol for authorization. It allows third-party applications to access user data without needing the user's password. Instead of users sharing their password with an app, they are granted a token that the app can use to act on their behalf. This offers several benefits; the most significant is that the app doesn't need to store user passwords, which greatly reduces potential attack vectors. Although OAuth is often used interchangeably with OAuth2, they are distinct. OAuth 1.0, introduced in 2007, is the first version of the protocol. While it provided a solid method for consumers to access protected resources without user credentials, it had complexities, such as the need for cryptographic libraries for request signing. Recognizing these challenges, the community introduced OAuth2 in 2012. OAuth2 simplified many aspects, eliminating the need for cryptographic signatures and emphasizing bearer tokens, which are easier to manage. OAuth2 provides flexibility with various methods of obtaining access tokens suitable for different application types. OAuth2 Steps Explained Using GitHub To explain how OAuth2 works, it's best to describe the entire process step-by-step. It's challenging to do this without code, so we'll reference our starter.dev backend showcase, which illustrates integrating a serverless backend with GitHub as an OAuth2 provider. Although the steps below are tailored to GitHub's OAuth2 implementation, it's important to note that the core flow is consistent across most OAuth2 services. For most of these services, you'll only need to adjust elements like URLs, query parameter names, and so on. Also, please note that our starter.dev backend showcase is designed to be deployed on Netlify. As such, all the API endpoints run as Netlify functions. When running the app locally, you must prepend the URL path with /.netlify/functions/server to access these endpoints. This step is unnecessary for the deployed version; API endpoints can be accessed directly (e.g., /api/auth/signin instead of /.netlify/functions/server/api/auth/signin) due to the redirects configured in the netlify.toml file. Step 0: Creating the App on GitHub The first step is to create your app with the provider. For GitHub, this is done on the Settings / Developer Settings / OAuth Apps page. This step is essential so that the provider can identify your app and possibly manage service-level parameters, like rate limits. In the screenshot above, when registering our app on GitHub, the authorization callback URL is a local URL. This is suitable for local development. However, in production, it should be a public API endpoint accessible to GitHub, like an API endpoint on Netlify. After registering the app, you'll receive a public client ID for your app and will need to generate a client secret (which should stay private). The client secret, used alongside the client ID, helps authenticate your application when trading an authorization code for an access token. Essentially, the client secret assures GitHub that the request for an access token genuinely comes from your app and not a malicious actor. Step 1: App Sends User to GitHub for Authorization The first step in the OAuth2 flow typically involves the user clicking a "Log in with GitHub" button on our web app or something similar. This action redirects the user from the web app to the OAuth2 provider's page (in this case, GitHub). For GitHub, the base URL for such a page is https://github.com/login/oauth/authorize, followed by query parameters that provide more details about the app. These parameters usually include the client ID — a mandatory identifier for your app — and several optional parameters, such as the redirect URI (where GitHub will send the user back) and scope (defining the permissions you're requesting). In our starter.dev example, clicking the "Log in with GitHub" button should lead the user to the /api/auth/signin route in our app. This route generates the GitHub authorization URL, populating it with all the necessary query parameters, and then redirects the user to that URL using a standard HTTP 303 redirect. ` Step 2: User Grants Permission In this step, the user arrives at the provider's authorization page and sees a prompt asking if they allow your app to access the specified data. The user can either approve or decline this request. For our scenario, this page would appear as follows: If either the cancel or authorize button is clicked, GitHub will invoke the callback URL specified when registering the app on GitHub. However, if the authorize button is clicked, GitHub will also send a one-time-use code query parameter that can be used to get the access token. Step 3: App Receives an Authorization Code After the user grants permission, GitHub redirects them to the specified callback URL. Attached to this URL is a one-time-use code query parameter. ` Step 3: App Exchanges Authorization Code for Access Token With this code, the app needs to make a POST request to the GitHub API to exchange the code for an access token. As shown below, the app sends the client ID, client secret, and the one-time code to GitHub and receives the access token in return. The access token should then be persisted in a store (such as Redis) or sent to the frontend as a cookie, as shown below: ` Step 4: App Accesses User Data with the Access Token Once you have the access token, you can use it to make authorized API requests on behalf of the user. All the app needs to do is include the access token in the Authorization header. ` And that is all. As long as the access token is valid, you can access any of the API services that were granted by the user in the authorization step. Conclusion OAuth2, though appearing complex initially, provides a strong method for authentication and authorization. For JavaScript developers, knowing its process helps in easy third-party integrations, giving users a safe way to access different services. Whether using GitHub or another platform, the main concepts are the same, allowing for better web applications. Check out our starter.dev backend showcase for the full code! There's also an associated StackBlitz project if you want to play with it....