Designating the data type for Object3D.userData in Three.js using TypeScript

Within the realm of Three.js, there exists a field on the Object3D class known as userData. This particular property is defined in the type declaration file

node_modules/three/src/core/Object3D.d.ts
:

/**
 * Base class for scene graph objects
 */
export class Object3D extends EventDispatcher {

[...]

    /**
     * An object that can be used to store custom data about the Object3d. It should not hold references to functions as these will not be cloned.
     * @default {}
     */
    userData: { [key: string]: any };

[...]

}

In an effort to impose stronger typing on the userData attribute, I crafted a module type declaration named src/typings/three.d.ts:

declare module 'three' {
  export class Object3D {
    userData: MyType1 | MyType2;
  }
}

type MyType1 = {
  type: 'type1';
  radius: number;
};
type MyType2 = {
  type: 'type2';
  name: string;
};

Despite successfully overwriting the userData property, all other type declarations within the three module were replaced rather than merged. This outcome proved to be more destructive than beneficial, evident in the absence of additional properties.

Is there an effective method to merge type declarations whereby solely the userData is substituted without impacting the entire module?

https://i.sstatic.net/VQZtG.png

Answer №1

When working with typescript terminology, you are attempting "declaration merging" in combination with "module augmentation". The issue with your current example has a few fixable problems and one limitation imposed by the language.

Let's address these problems and provide some solutions before suggesting an alternative method for achieving the desired outcome.

  1. If you intend to augment a class definition through module augmentation, your augmentation should be represented as an un-exported interface rather than an exported class definition. This means replacing export class Object3D with interface Object3D. Although it may not be intuitive, the documentation explains that certain merges are not allowed in TypeScript.
  2. It is crucial to match the signature of the class you are augmenting precisely, including generic parameters and extends statements. Make sure to adhere to the exact structure of the class during augmentation.
  3. The structure of @types/three involves various export statements spread across different files, which can complicate module augmentation. When targeting a specific file for module augmentation, ensure your declare module statement aligns directly with that file without being affected by intermediary exports.

To summarize, here's an almost functional approach:

import { BaseEvent, EventDispatcher } from "three";

declare module "three/src/core/Object3D" {
  interface Object3D<E extends BaseEvent> extends EventDispatcher<E> {
    somethingElse: string;
    userData: MyType1 | MyType2; // Unexpected behavior due to TypeScript limitations
  }
}

It's important to note that declaration merging cannot alter the type of an existing property on the original class definition. Instead, it can only add new properties to the class.

An Effective Alternative Approach

To overcome this limitation, consider creating a new class declaration that extends the original class and overrides the necessary type:

declare module "three" {
  import { BaseEvent } from "three/src/core/EventDispatcher";
  import { Object3D as Object3DOriginal } from "three/src/core/Object3D";
  export * from "three/src/Three";
  export class Object3D<E extends BaseEvent = Event> extends Object3DOriginal<
    E
  > {
    userData: MyType1 | MyType2;
  }
}

For a working example, refer to this codesandbox link.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error: The function req.logIn is not recognized - Passport JS

After researching extensively, I am confident that the issue I'm facing is not a known bug. I am currently utilizing passport JS with the local strategy in my login route, employing a custom callback and invoking req.login once I confirm the user&apos ...

Unauthorized Token Authentication in .NET and Angular Application

Encountering an issue with user authentication. Login is successful and I receive a token from the API, which I save in JwtTokenService within my Angular App. When making a request (e.g. Delete), I add the "Authorization" header with the value "Bearer toke ...

Encountering Next.js Hydration Issue when Using Shadcn Dialog Component

While working on a Next.js project, I came across a hydration error when utilizing the Shadcn Dialog component. The specific error message reads: "Hydration failed because the initial UI does not match what was rendered on the server." Highligh ...

This function template is designed to work with a wide range of inputs, thanks to its generic nature. It allows a callback function to be executed with a set of parameters of any type

I've encountered an issue with the function I defined: function someFunc<T extends any[]>(callback: (...args: T) => void, params: T) {} Unexpected behavior occurs when calling it in TypeScript: // this works // hovering over a, b, and c reve ...

Having trouble displaying a group of threeJs renderers

I am working on a project that involves displaying multiple renderer, scene, and camera "groups" on a single HTML page. However, I encountered challenges when attempting to implement a render loop using requestAnimationFrame across all these groups. Despit ...

Unable to access Papa Parse results beyond the 'complete' function

Currently, I am utilizing Papa Parse along with Angular 2 to import a CSV list and then wish to pass that list to another component. While I can successfully read the CSV data and display them using console log, I am facing challenges in accessing the pars ...

Step-by-step guide on uploading a file for API testing

When working on API integration tests using playwright, I encounter a scenario where I need to call an API that uploads a file. In my project directory, there is a folder dedicated to static assets which contains the following file: /static-assets - im ...

Guide on making a three-dimensional trapezoid using three.js

I'm currently working on a unique animation project using three.js, which features various 3D models. One particular model that I am finding challenging to create is the "trapezoid". Up until now, I've successfully managed to generate a truncate ...

Position Geometry on Plane by picking 3 intersecting points

I've been struggling for a few days to align a 3D object by selecting three intersecting points on the object. My goal is to align the geometry to the plane. I began by calculating the cross vectors from the three points. The first step I took was ...

Angular 7: Show selected value upon clicking Radio button

I am having an issue with a radio button where I need to display a value when it is clicked. For example, when the "weekly" option is selected, I want it to display "Weekly" in the "Selection" element. I have tried to implement this but it is not working a ...

Guide to generating multiple instances of Isocohedrons (polygons) with react-three/fiber and react-three/drei for rendering an unlimited number of shapes

I am excited to showcase the programming languages and technologies I have expertise in through my portfolio. To display them, I decided to use icosahedrons as a unique visual representation. Each icosahedron is assigned an icon using decal from react-thre ...

Guide to retrieving specific attributes from an object within an array of objects using Angular Typescript

As an example, if we consider a sample JSON data retrieved from the JSONPlaceholder website at https://jsonplaceholder.typicode.com/users. [ { "id": 1, "name": "Leanne Graham", "username": "Bret&q ...

How does the plain constant continue to be effective in the Composition API of Vue 3?

In accordance with the official documentation, it is necessary to use ref or reactive when defining the "data" variable in the new composition api setup method. This allows Vue to track any changes made to that particular variable. While experimenting wit ...

Triggering an event from a higher-level component to a lower-level component

Within the parent component named personDetails, there is a Submit button. This parent component contains multiple child components called person`. Each time I click on the Submit button, I need to trigger a method within the child component. Is it possib ...

Mean value calculated for each hour within a given array

Every minute, my array updates. To show the daily average of each hour, I need to calculate the average for every 60 elements. The latest minute gets added at the end of the array. // Retrieving the last elements from the array var hours = (this. ...

The browser appears to be interpreting the JavaScript file as HTML, resulting in the error message "Uncaught SyntaxError: Unexpected token '<'"

Referencing a similar unresolved question here: Browser seems to read react JS file as HTML, not JSX I am currently working on a project that is developed with Next.js, React, and Typescript. However, I am facing an issue while trying to integrate a Java ...

Merge the values of checkboxes into a single variable

How can I gather all the checkbox values from my list and combine them into a single variable? This is the structure of my HTML: <div class="card" *ngFor="let event of testcases" > <input class="form-check-input" ...

Using type aliases in Typescript to improve string interpolation

After working with Typescript for some time, I have delved into type aliases that come in the form: type Animal = "cat" | "dog"; let a1_end = "at"; let a1: Animal = `c${a1_end}` I initially thought that only the values "cat" ...

The cube from Three.js refuses to render after being bundled with Webpack in a Wordpress environment

I am facing an issue while trying to incorporate three.js with WordPress using webpack. The problem lies in the fact that the 3D cube is not showing up inside the canvas. <div class="div" id="canvas"></div> appears to be em ...

The Observable pipeline is typically void until it undergoes a series of refreshing actions

The issue with the observable$ | async; else loading; let x condition usually leads to staying in the loading state, and it requires multiple refreshes in the browser for the data to become visible. Below is the code snippet that I utilized: // TypeScript ...