Skip to main content

Object.assign is not a deep clone

· 3 min read
Simon Porritt
JsPlumb Development

The JsPlumb API, as with most Javascript APIs, makes extensive use of Javascript objects for configuration. A good practice to follow with inputs from the user is to make an internal copy, so as to be sure that your library is not introducing any unexpected side effects. Keep in mind that Javascript objects are passed by reference, ie:

function doSomething(obj) {
obj.title = "A new value."
}

const myObject = {
title:"Initial title"
}

doSomething(myObject)

console.log(myObject.title)

=> "A new value."

...this is why we want to make a copy of any object we are given, because we don't know where else the user is using this JS object it passed in.

function doSomething(obj) {
obj = Object.assign({}, obj)
obj.title = "A new value."
}

const myObject = {
title:"Initial title"
}

doSomething(myObject)

console.log(myObject.title)

=> "Initial title"

Obviously this example is a little contrived, but hopefully you get the point. By using Object.assign({}, obj) to create a copy of the object we were given, we've now insulated ourselves from userland...or have we? What about if I pass in a more complex object:

function doSomething(obj) {
obj = Object.assign({}, obj)
obj.child.title = "A new value."
}

const myObject = {
title:"Initial title",
child:{
title:"Initial child title"
}
}

doSomething(myObject)

console.log(myObject.child.title)

=> "A new value."

Aur naur! What's happened here? The problem is that Object.assign is not a deep clone.

Cloning an object

What you should do instead of using Object.assign is to clone the object. Most browsers have offered a structuredClone method on the window since about 2022, so an approach like this is widely supported:

function doSomething(obj) {
obj = structuredClone(obj)
obj.child.title = "A new value."
}

const myObject = {
title:"Initial title",
child:{
title:"Initial child title"
}
}

doSomething(myObject)

console.log(myObject.child.title)

=> "Initial child title"

core-js Polyfill

If the fact that it's not guaranteed that your users have a browser supporting structuredClone is a concern, you could use a polyfill from core-js.

Clone example code

If you don't want to use a polyfill from core-js, feel free to grab this code instead:


function clone(a) {
if (a == null) {
return null
} else if (typeof a === "string") {
// string
return "" + a
} else if (typeof a === "boolean") {
// boolean
return !!a
} else if (Object.prototype.toString.call(a) === "[object Date]") {
// Date
return new Date(a.getTime())
} else if (Object.prototype.toString.call(a) === "[object Function]") {
// Function (not cloned; returned as-is)
return a
} else if (Array.isArray(a)) {
// Array - create new array and clone entries from existing array
return a.map(e => clone(e))
} else if (Object.prototype.toString.call(a).match(/\[object .*Element]/) != null) {
// DOM Element (not cloned; returned as-is)
return a
} else if (Object.prototype.toString.call(a) === "[object Text]") {
// DOM Text Node (not cloned; returned as-is)
return a
} else if (Object.prototype.toString.call(a) === "[object Object]") {
// JS Object
const c = {}
for (let j in a) {
c[j] = clone(a[j])
}
return c
}
else {
return a
}
}

Read more

  • Read about structuredClone on MDN

Problems with the change event on a color input in React

· 3 min read
Simon Porritt
JsPlumb Development

Way back in the day, HTML offered a very limited set of components for capturing input from a user. Over the years this has changed, with new controls being introduced and finding widespread support across the various browsers. One of these new-ish input types (new-ish if you've been writing webapps for 20 years or so, anyway) is the color input:

#442288

You can click that color bar and you'll get a popup from which you can select a new color. When you change the color, the span next to it will update to tell you what the current color is.

Available events

According to MDN there are a couple of events you can hook into:

  • input input is fired on the input element every time the color changes
  • change The change event is fired when the user dismisses the color picker

In React, you'd use onInput or onChange to bind to these events.

Using Amazon's Java S3 client with Hetzner

· 4 min read
Simon Porritt
JsPlumb Development

We recently completed a migration of infrastructure from AWS to Hetzner, saving quite a bit of cash in the process. It was quite straightforward for the most part - we created Cloud instances in Hetzner to replace our EC2 boxes, and span up a Postgres instance on a box of its own to replace RDS. We store packages for our private NPM repository on S3; Hetzner has Object Storage for that, so we migrated those, and we were pretty much done. But then a quick look at Hetzner's docs for Object Storage did not show any official Java libraries for interfacing with Object Storage, and in any case we had a whole persistence layer sitting on S3 that we were not particularly keen to rewrite!

From reading Hetzner's website we reached the conclusion that "S3 Compatible" probably meant we could access our buckets via the Amazon S3 client in Java, but how? Our original code looked like this:

import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client

S3Client s3Client = S3Client.builder().region(Region.MY_REGION).build()

To access an AWS API, you need to tell it an access key and a secret access key (which you've created in your AWS console). This code doesn't tell AWS anything about credentials, so how does it know what to use? The AWS client code tests various places looking for an access key and a secret access key. In our case we were using a credentials file in the ~/aws for development, and environment variables in production.

How we improved our apidocs with Typedoc groups and categories

· 11 min read
Simon Porritt
JsPlumb Development

JsPlumb uses the excellent Typedoc documentation generator to create our API documentation, but since we adopted it last year we haven't really made the most of it. We would run Typedoc over our codebase with the default settings, and the result is a page in which everything is listed alphabetically, grouped by Enumerations Classes, Interfaces, etc.

It can be a useful page - if you know what you're looking for. But it doesn't lend itself very well to browsing, and browseable docs are great, because they lead to discovery.

TS4111 - noPropertyAccessFromIndexSignature

· 2 min read
Simon Porritt
JsPlumb Development

While working on an Angular version of our popular Gantt chart demonstration this morning I stumbled across a Typescript linting issue whose purpose is not at all clear to me.

Say I have this interface for a person:

export interface Person {    
firstName:string
lastName:string
}

and this for a serialized person:

export interface Person {    
firstName:string
lastName:string
fullName:string
}

Now I want to write a serializer to export a bunch of these people:

export function serializePeople(persons:Array<Person>):string {
return persons.map(p => {
return {
fullName:`${p.firstName} ${p.lastName}`,
firstName:p.firstName,
lastName:p.lastName
}
})
}

This looks ok, right? Well in fact the default settings for Angular's tsconfig.json cause an error:

Property 'firstName' comes from an index signature, so it must be accessed with ['firstName']

Testing class compatibility with isAssignableFrom

· 4 min read
Simon Porritt
JsPlumb Development

Classes are a controversial topic in the Javascript/Typescript world. Are they a terrible idea? Are they really useful, when used sensibly? This post will make no attempt at answering either of these questions. This post is about a niche method that I first encountered many years ago in the world of Java - isAssignableFrom - and how you can go about writing it for use in Typescript/Javascript.

What does it do?

This is what the Javadocs have to say about it:

public boolean isAssignableFrom(Class<?> cls)

Determines if the class or interface represented by this Class object is either the same as, or is a superclass or superinterface of, the class or interface represented by the specified Class parameter. It returns true if so; otherwise it returns false.

So, given some class, you can use isAssignableFrom to figure out whether that class is a subclass of some other class.

Why would I need this?

If you're thinking it's a bit niche, yes, I agree. isAssignableFrom is one of those methods you don't use much. But when you need it, you need it.

Docusaurus JSON environment loader

· 2 min read
Simon Porritt
JsPlumb Development

JsPlumb uses Docusaurus, a super handy static site generator, for various parts of our ecosystem, including our Documentation and also our recently released stand alone components product.

While developing the site for JsPlumb Components, we wanted to include a couple of pieces of information that would vary depending on whether we were running in development mode locally (ie docusaurus start) or building for production. We looked into the various options available, all based on the dotenv plugin for Webpack, but couldn't find what we were looking for: a solution that worked, with minimal manual intervention, in both development mode and when building for production.

So we ended up building our own plugin. This plugin reads values from a JSON file and makes them available to your site via the customFields map in the Docusaurus siteConfig.

note

When I say "Docusaurus" in this post I am talking about v2/v3. I've not used v1.

Statcounter plugin for Docusaurus

· 5 min read
Simon Porritt
JsPlumb Development

JsPlumb uses Statcounter to keep track of what pages in the documentation people are looking at. Recently, at the tail end of the work to migrate to Typescript and release version 5.x of the Community and Toolkit editions, we started using Docusaurus, a super handy static site generator that not only runs on React but has React baked right into it, allowing us to easily embed working demonstrations throughout our docs, or to embed snippets from the api documentation directly into the main documentation via React components. It's a really handy tool, and if you're looking for a static site generator I recommend giving it a look.

note

When I say "Docusaurus" in this post I am talking about v2. I've not used v1.