question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unable to extend JSX properties of already defined elements

See original GitHub issue

Bug Report

We’ve just release Preact Signals which are sort of an addon to both Preact and React that allow you to pass an observable object directly into JSX. As part of that we need to extend the existing JSX definitions to allow passing our observable type too, instead of just the existing types. But I’m unable todo so. I keep getting a type error.

🔎 Search Terms

JSX, declaration merging, React, Preact, Signals, IntrinsicElements

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about JSX + declaration merging

⏯ Playground Link

Playground link with relevant code

💻 Code

import * as React from "react"

// This and the JSX additions live in a library
interface Signal<T> {
    value: T
}

declare global {
    namespace JSX {
        interface IntrinsicElements {
            // Doesn't work
            input:
                | React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>,HTMLInputElement>
                | Signal<string>
        }
    }
}

// This represents app code the user writes
const foo: Signal<string> = {
    value: "foo"
}

const a = <input value={foo} />

🙁 Actual behavior

Passing a Signal as value errors with a type error

const foo = { value: "foo" };
const a = <input value={foo} />

🙂 Expected behavior

Passing a Signal as value should work

const foo = { value: "foo" };
const a = <input value={foo} />

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
RyanCavanaughcommented, Sep 21, 2022

I’m worried that this approach breaks as soon as there is another package that also wants to extend the types.

It’s not really clear how to make this scenario work in a generalizable way, except via the one already proposed upthread. If Signal2 also existed and also wants to make things work, what happens?

// In Signal
            input: TheReactDefinition | Signal<string>

// In Signal2
            input: TheReactDefinition | Signal2<string>

Is TS supposed to merge these two types using Union? Intersection? How/why? It’s very unclear. Even if we had some keyword like merge so you could write input: merge | Signal<string> to refer to the property you’re overwriting, then you run into declaration ordering problems because repeated utterances of merge in the same property slot could manifest in ways that have order-dependent nesting (e.g. input: Foo<merge>)

1reaction
Andaristcommented, Sep 20, 2022

Telling users to point to another package for JSX would work, but I’m worried that this approach breaks as soon as there is another package that also wants to extend the types.

This is a valid concern but there is still probably a way to handle this through the composition of types. Note that this would at least reference the “composed” module somewhere and it would make it discoverable - instead of relying on implicit extensions. It also wouldn’t require any new language constructs to be introduced - right now those interfaces just behave in the very same way the usual interface merging works but that, of course, doesn’t fit your use case. This behavior can’t change - so something new would have to be introduced to account for this.

The problem with interface merging, in general, is that it requires the full knowledge of the project to work correctly - so by nature, it slows down the project startup times and stuff. Just my 2 cents but explicit deps >>> implicit deps, for this very reason.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Typescript/React extending default functional component ...
On the other hand, built-in components like <div> or <span> have already defined the props that correspond to their HTML elements.
Read more >
Documentation - JSX - TypeScript
It is determined by the type of a property on the element instance type that was previously determined. Which property to use is...
Read more >
React Without JSX
Using React without JSX is especially convenient when you don't want to set up compilation in your build environment. Each JSX element is...
Read more >
React with TypeScript Cheatsheet - Bits and Pieces
How to type (extend) HTML elements. Sometimes, you want to create a small, modular component that takes the attributes of a native HTML...
Read more >
Custom Elements v1 - Reusable Web Components
Custom elements allow web developers to define new HTML tags, extend existing ones ... Elements can react to attribute changes by defining a ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found