Telerik blogs

Singletons may be a common go-to for sharing data across components, but are they secure? Short answer, no. Let’s see how various JS frameworks handle data then.

If you want to share data across your components, the first thing you might think of is a singleton. A singleton is a single instance of an object. In JavaScript, an object can be a function or a class. However, singletons are not always a perfect use case for state sharing.

A Singleton Example

shared.ts

export const shared = new Map();

I can share a single shared object across multiple components. This basically just works like a global. Anywhere a variable can be imported, it can be used, and the state stays the same. If the object is updated in Component 1, the data will persist to Component 2 until the app is destroyed.

component1.tsx

import { shared } from 'shared.ts';

// ... somewhere in template

shared.set('key', 'value');

component2.tsx

import { shared } from 'shared.ts';

// ... somewhere in template

const value = shared.get('key');

This could work in Svelte, Angular, React or in any component in any JavaScript framework.

But …

The Global Problem

Singletons are globals. This is NOT a good thing for server-side rendering, and it does NOT allow you to create reusable data patterns like services or stores on the server. If you ran this code on the server, every client could have access to your shared value. It is not secure. So how do you prevent this?

To help solve this problem, it is important to understand that every JS object is passed by reference, not by value.

// delcare object x
const x = { test: 'value' };

// declare function y
function y(p: { test: string }) {
	p.test = 'new value';

	console.log(x);
}

// run function y
y(x);

If we run this code, we will see that even though we didn’t update x directly, its value still was changed. The x object was passed by reference.

It also worth noting that all parent values are available to children in JavaScript:

function v() {

    const x = 'me';

    function y() {

      function z() {
        console.log(x);
      }
      z();
    };
    y();
  }
  v();

This works as expected, and the x value is not at the top global namespace. This is how we need to write it to prevent the global variable declarations. But how does this work in actual frameworks?

Providers

A Provider in a framework provides an instance of an object to a component. This means it passes the instance of that object to its children’s functions. A component is essentially just a function, class or object, within a function class or object. The provider provides a singleton that can be shared across your app.

It may be called something different in all frameworks, but the premise is the same: provide a singleton that your app’s components can share.

Angular

Angular’s providers use services and injectors. This is called Angular’s Dependency Injection System. It just passes the instance of the function, object or class to the component. You will usually see just a shared class (called a service) as a provider. It is very customizable.

React

This framework works a little differently. React shares a global state that is made available to components using the context. The createContext object needs to be shared with the provider and useContext (consumer). This means the contexts in a global variable are only available to those components that consume them. That global variable saves all the contexts. Next.js and Remix keep the global variable from leaking.

SolidJS

Solid uses the same pattern as React, with createContext and useContext. SolidJS is React’s genetically modified child, after all—the engineers just forgot to make dealing with context easier.

Svelte

Svelte is genius in that it automatically creates a new context for each component, inheriting all its parent context. This means getContext and setContext are doing just that: automatic prop drilling.

However, the recommended way of sharing a writable on the client is not recommended for SvelteKit, as it is just a global. See my article: The Correct Way to Use Stores in SvelteKit.

Vue

Vue uses provide and inject, which is a combination of Angular and React techniques, but with fewer headaches.

Qwik

Qwik unfortunately didn’t learn from React’s mistakes, and makes you declare a provider context manually for each component. However, unlike React, you don’t have to create an HTML component tree, as this is done automatically.

I still didn’t like this in Qwik, so I created a custom useShared hook in my Menu Example App. I’m hoping these design patterns become the norm and automatically built into the future framework versions.

Summary

  • Data is always shared in a global object or parent object. This object is referenced by a child component (object/function/class) by passing it directly by reference, or by just referring to the shared global object.
  • Don’t directly export an object or call a function at the global level if you’re using server-side rendering. This will cause the data to be available to all users on that server.

There are many paradigms for sharing data across our components, but I like Svelte’s simplicity. Perhaps this will become easier in future framework versions. Either way, be careful at how you handle your shared data.


About the Author

Jonathan Gamble

Jonathan Gamble has been an avid web programmer for more than 20 years. He has been building web applications as a hobby since he was 16 years old, and he received a post-bachelor’s in Computer Science from Oregon State. His real passions are language learning and playing rock piano, but he never gets away from coding. Read more from him at https://code.build/.

 

 

Related Posts

Comments

Comments are disabled in preview mode.