Skip to content

Signals no longer auto-unwrap in JSX attributes (require .value) #801

@Darka7

Description

@Darka7

Signals no longer auto-unwrap in JSX attributes for boolean props

  • Check if updating to the latest version resolves the issue

Environment

  • I am using @preact/signals-core
  • I am using @preact/signals
  • I am using @preact/signals-react

Versions:

  • @preact/signals-react: 3.6.0
  • @preact/signals-react-transform: 0.6.0
  • react: 19.2.0
  • react-dom: 19.2.0
  • @types/react: 19.2.2
  • @types/react-dom: 19.2.2

Describe the bug

Signals are no longer auto-unwrapping when passed directly to JSX attributes, particularly for boolean props. Previously, signals could be passed directly (e.g., checked={signal}, disabled={signal}), but now they require explicit .value access (e.g., checked={signal.value}).

This appears to be inconsistent with the official demo code which shows signals being used without .value in JSX attributes.

To Reproduce

The official nesting demo shows this pattern (lines 62-71):

<tr>
    <td>boolKey:</td>
    <td>{obj.boolKey}</td>
    <td>
        <input
            type="checkbox"
            onChange={e => (obj.boolKey.value = e.currentTarget.checked)}
            checked={obj.boolKey}  // ← Signal without .value
        />
    </td>
</tr>

This pattern no longer works in version 3.6.0. We must now use:

<tr>
    <td>boolKey:</td>
    <td>{obj.boolKey.value.toString()}</td>  // ← Need .value
    <td>
        <input
            type="checkbox"
            onChange={e => (obj.boolKey.value = e.currentTarget.checked)}
            checked={obj.boolKey.value}  // ← Need .value
        />
    </td>
</tr>

Minimal reproduction:

import React from "react";
import { signal } from "@preact/signals-react";
import { useSignals } from "@preact/signals-react/runtime";

const obj = {
    boolKey: signal(false)
};

export default function TestComponent() {
    useSignals();

    return (
        <>
            {/* ❌ Doesn't work - checkbox doesn't reflect signal value */}
            <input
                type="checkbox"
                checked={obj.boolKey}
                onChange={e => (obj.boolKey.value = e.currentTarget.checked)}
            />

            {/* ✅ Works - but requires .value */}
            <input
                type="checkbox"
                checked={obj.boolKey.value}
                onChange={e => (obj.boolKey.value = e.currentTarget.checked)}
            />

            {/* Same issue with disabled attribute */}
            <button disabled={obj.boolKey}>Test (broken)</button>
            <button disabled={obj.boolKey.value}>Test (works)</button>
        </>
    );
}

Steps to reproduce:

  1. Clone or reference the official nesting demo
  2. Try using checked={signal} on a checkbox input
  3. Observe that the checkbox doesn't reflect the signal value
  4. Change to checked={signal.value} and it works

Expected behavior

Based on the official demo code, signals should auto-unwrap when passed to JSX attributes.

From the demo (line 69):

checked={obj.boolKey}  // Should work without .value

This pattern is shown in the official examples and should work. Signals should unwrap automatically when:

  • Used in JSX children: <div>{signal}</div> ✅ Works
  • Used in JSX attributes: <input checked={signal} />Broken

Actual behavior

  • Without .value: The signal object itself is passed to the DOM attribute. The attribute doesn't reflect the signal value or update reactively.
  • With .value: It works correctly, but contradicts the official demo code.

Interestingly, signals work fine in text content:

<td>{obj.boolKey}</td>  // ✅ Works - shows true/false

But fail in attributes:

<input checked={obj.boolKey} />  // ❌ Broken - needs .value

Additional context

  • We're calling useSignals() at the component level as per the integration guide
  • Using React 19.2.0 - This might be relevant as React 19 introduced changes to how props are handled
  • This pattern used to work in previous versions/configurations
  • String signals in text content work fine: <td>{signal}</td>
  • Boolean signals in text content work fine: <td>{boolSignal}</td>
  • But boolean signals in attributes don't work: <input checked={boolSignal} />

What works:

<td>{obj.boolKey}</td>                        // ✅ Auto-unwraps
<input checked={obj.boolKey.value} />         //  Explicit .value

What doesn't work (but is shown in official demos):

<input checked={obj.boolKey} />               // ❌ Should auto-unwrap
<button disabled={disabledSignal} />          // ❌ Should auto-unwrap

Question

The official nesting demo shows checked={obj.boolKey} without .value. Is this:

  1. A bug in the demo code that should be updated to use .value?
  2. A regression where this used to work but no longer does?
  3. A React 19 compatibility issue that needs to be addressed?

If .value is always required for attributes, the demo code and documentation should be updated to reflect this. Otherwise, this appears to be a bug where signal auto-unwrapping in attributes is broken.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions