nanojsx/nano

`undefined` attributes are not elided during SSR

lucacasonato opened this issue · 6 comments

Describe the bug

In React and Preact, setting an attribute to undefined will cause that attribute to be elided from the rendered output (client and server side). NanoJSX does not do this, instead setting the attribute with a literal "undefined" string value. For example <button disabled={undefined}>Hey</button> would render an enabled button in React/Preact, while rendering a disabled button in nanojsx.

I don't know if it is a good idea.

In HTML, all buttons below render the same.

<button disabled>click me</button>
<button disabled="disabled">click me</button>
<button disabled="false">click me</button>
<button disabled="true">click me</button>
<button disabled="undefined">click me</button>

In the example below, the first 3 buttons are disabled, the others are enabled.

const Button: FC<{ disabled: boolean | string | undefined; children: string }> = ({ disabled, children }) => {
  let p = {}
  if (disabled === true || disabled === 'true') p = { ...p, disabled: '' }
  return <button {...p}>{children}</button>
}

const App = () => (
  <div>
    <Button disabled="false">click me</Button>
    <Button disabled={false}>click me</Button>
    <Button disabled={undefined}>click me</Button>

    <Button disabled>click me</Button>
    <Button disabled="true">click me</Button>
    <Button disabled={true}>click me</Button>
  </div>
)

@yandeu I am aware of the workaround with the spread operator, but I would argue that disabled={undefined} should mean that undefined is elided:

  1. Using the spread operator it is more verbose than using undefined as an attribute value:
    <button {...(disabled ? { disabled: "" } : {})} /> // spread operator
    <button disabled={disabled || undefined} /> // undefined as attribute value
  2. Users are less likely to be familiar with the spread operator. The undefined means elided is rather common knowledge in the React/Preact ecosystem from my experience.
  3. The behaviour matches React/Preact.
  4. Web APIs also have the behaviour that values set as undefined are treated as if they had been elided. Examples:
    // these are the same
    fetch(url, { method: undefined });
    fetch(url, {  });
    
    // these are also the same
    fetch(url, undefined);
    fetch(url);
  5. A property on an object being present, vs being undefined is often confusing to new developers because they both show up as the property being undefined: ({}).disabled and ({disabled: undefined}).disabled both return undefined.

So undefined and "undefined" should completely remove the attribute?

No, only the undefined value (the one where (typeof V === "undefined") === true) should have this behavior. Not the string "undefined".

👍

Thanks @yandeu !