Skip to content

[Bug]: web-components TextInput spellcheck attr converter throws on null/undefined (null.toString) #36462

Description

@mcritzjam

Summary

<fluent-text-input>'s spellcheck attribute uses a converter whose toView
is not null-safe, so any reflection of spellcheck (setting spellcheck="false",
or SSR-hydrating an element that carries it) throws
TypeError: Cannot read properties of null (reading 'toString'). In practice
spellcheck is unusable on the component.

Root cause

packages/web-components/src/text-input/text-input.base.ts:

@attr({
  converter: {
    fromView: value => (typeof value === 'string' ? ['true', ''].includes(value.trim().toLowerCase()) : null),
    toView: value => value.toString(),
  },
})
public spellcheck!: boolean;
  • spellcheck has no default → the backing field is undefined.
  • toView calls .toString() directly → throws on null/undefined.
  • fromView returns null for any non-string input, which can feed null
    straight back into the field and into toView.

Steps to reproduce

Minimal standalone repro (loads the component from esm.sh, no build/install):

<fluent-text-input id="a" spellcheck="false">Case A</fluent-text-input>
<script type="module">
  import 'https://esm.sh/@fluentui/web-components@3.0.0-rc.14/dist/esm/text-input/define.js';
  await customElements.whenDefined('fluent-text-input');
  // Case B: enqueues attribute reflection -> converter.toView(null) -> null.toString()
  const b = document.createElement('fluent-text-input');
  document.body.appendChild(b);
  queueMicrotask(() => { b.spellcheck = null; });
</script>

Serve over http (custom elements need a real origin) and open DevTools console.

Versions

  • Reproduces on @fluentui/web-components@3.0.0-rc.14.
  • The same code is present on main (HEAD) — text-input.base.ts still has the
    identical converter and no default value.

Expected

Setting or reflecting spellcheck (including the default/unset state) does not
throw; <fluent-text-input spellcheck="false"> hydrates and disables spelling
suggestions on the internal <input>.

Actual

TypeError: Cannot read properties of null (reading 'toString')
    at Object.toView (.../@fluentui/web-components/dist/esm/text-input/text-input.base.js:417)
    at .../@microsoft/fast-element/dist/esm/components/attributes.js:151
    at tryRunTask (.../@microsoft/fast-element/dist/esm/observation/update-queue.js:19)

Suggested fix

Make the converter null-safe and give the attribute a boolean default, e.g.:

converter: {
  fromView: value => (typeof value === 'string' ? ['true', ''].includes(value.trim().toLowerCase()) : false),
  toView: value => String(!!value),
},

Any equivalent that avoids calling .toString() on null/undefined works.

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