suid icon indicating copy to clipboard operation
suid copied to clipboard

When using `For` to iterate and create `TextField`, the `TextField` rerenders and loses focus

Open thep0y opened this issue 4 months ago • 1 comments

Native input elements do not have this issue.

import { For } from "solid-js";
import { createStore } from "solid-js/store";

import { TextField } from "@suid/material";

function App() {
	const [textFieldInputs, setTextFieldInputs] = createStore<string[]>([""]);
	const [nativeInputs, setNativeInputs] = createStore<string[]>([""]);

	const updateTextFieldInput = (index: number, value: string) => {
		setTextFieldInputs(index, value);
	};

	const updateNativeInput = (index: number, value: string) => {
		setNativeInputs(index, value);
	};

	return (
		<div>
			<h4>TextField</h4>
			<For each={textFieldInputs}>
				{(input, index) => (
					<TextField
						value={input}
						onChange={(e) => updateTextFieldInput(index(), e.target.value)}
						size="small"
					/>
				)}
			</For>

			<h4>Native Input</h4>
			<For each={nativeInputs}>
				{(input, index) => (
					<input
						value={input}
						onChange={(e) => updateNativeInput(index(), e.target.value)}
						size="small"
					/>
				)}
			</For>
		</div>
	);
}

export default App;

thep0y avatar Aug 08 '25 09:08 thep0y

I briefly looked into this issue. First, TextField is not based on the input component but on a div. The key difference causing this issue is the different behavior mechanism of onChange. You can easily see the difference visually with the following code.

<div>
        <h4>{textFieldInputs[0]}</h4>
        <h4>TextField</h4>
        <For each={textFieldInputs}>
          {(input, index) => {
            console.log('TextField input: ',input);
            return (
              <TextField
                value={input}
                onChange={(e) => updateTextFieldInput(index(), e.target.value)}
                size="small"
              />
            );
          }}
        </For>

        <h4>{nativeInputs[0]}</h4>
        <h4>Native Input</h4>
        <For each={nativeInputs}>
          {(input, index) => {
            console.log('Native input: ',input);
            return (
              <input
                value={input}
                onChange={(e) => updateNativeInput(index(), e.target.value)}
                size="small"
              />
            );
          }}
        </For>
</div>

TextField triggers onChange with every input. input does not. It triggers when you press Enter or lose focus. Additionally, both re-render when the store changes.

Therefore, to achieve the desired behavior with TextField, you should do the following:

        <h4>{textFieldInputs[0]}</h4>
        <h4>TextField</h4>
        <For each={textFieldInputs}>
          {(input, index) => {
            console.log(input);
            const [tempField, setTempField] = createSignal<string>(input);

            return (
              <TextField
                value={tempField()}
                onChange={(e) => setTempField(e.target.value)}
                onBlur={({ currentTarget }) =>
                  updateTextFieldInput(index(), currentTarget.value)
                }
                onKeyDown={handleKeyDown} // To handle the `Enter` key, insert this line and write the function.
                size="small"
              />
            );
          }}
        </For>

The behavior of input and TextField is now the same.

dennev avatar Oct 14 '25 04:10 dennev