Создал компонент для просмотра/изменения записи, который вместо инпута использует элементы с contentEditable.
Компонент
type NoteEditorProps = {
activeNote: Note | null;
dispatch: Dispatch<Action>;
};
const NoteEditor = (props: NoteEditorProps) => {
const [noteState, setNoteState] = useState<Partial<Note>>();
const formatedLocalDateTime = useDatetimeInputDateFormat();
//хелпер, переводит дату в формат приемлемый для инпута
const dateInputValue = noteState?.expiredAt
? formatedLocalDateTime(noteState.expiredAt)
: formatedLocalDateTime(new Date());
useEffect(() => {
//устанавливаю начальное состояние компонента
if (props.activeNote !== null) {
setNoteState(props.activeNote);
}
}, [props.activeNote]);
//собственно комплексный обработчик ввода для трех инпутов
function inputsHandler(
e: ChangeEvent<HTMLHeadingElement | HTMLParagraphElement | HTMLInputElement>
) {
if (e.target.id === "expiredAt" && "value" in e.target) {
setNoteState({ ...noteState, [e.target.id]: new Date(e.target.value) });
} else if ("innerText" in e.target) {
setNoteState({ ...noteState, [e.target.id]: e.target.innerText });
}
}
//гуард для проверки что все поля на месте при сабмите
function isNoteComplete(obj: Partial<Note> | undefined): obj is Note {
return (
obj?.noteName !== undefined &&
obj?.noteText !== undefined &&
obj?.expiredAt !== undefined &&
obj?.createdAt !== undefined
);
}
//сабмит
function handleNoteUpdate() {
if (isNoteComplete(noteState)) {
props.dispatch({
type: "updateNote",
payload: { updatedNote: noteState },
});
}
}
if (props.activeNote === null) {
return (
<div className="h-1/2 flex items-center justify-center">
<p>
Выберите запись для просмотра/редактирования.
</p>
</div>
);
}
return (
<div className="h-1/2 flex flex-col border-b-2 border-black">
<h2
className="text-2xl text-center font-bold my-2"
contentEditable
suppressContentEditableWarning={true}
id="noteName"
onInput={inputsHandler}
>
{noteState?.noteName}
</h2>
<p
className="pl-4 m-6"
contentEditable
suppressContentEditableWarning={true}
id="noteText"
onInput={inputsHandler}
>
{noteState?.noteText}
</p>
<p className="m-2">
Запись создана - {noteState?.createdAt?.toLocaleString()}.
</p>
<div className="m-2">
<CustomInput
id="expiredAt"
label="Дата напоминания"
type="datetime-local"
value={dateInputValue}
onChange={inputsHandler}
/>
</div>
<CustomButton
className="bg-green-300 rounded disabled:bg-gray-700 disabled:text-white p-4 m-4 mr-6 self-end"
disabledMessage="Все поля должны быть заполнены"
onClick={handleNoteUpdate}
>
Подтвердить изменения записи.
</CustomButton>
</div>
);
};
export default NoteEditor;
Прошу прощения за "простыню", специально ничего не убирал.
Собственно проблема - при вводе данных в элемент с contentEditable курсор после каждого ввода данных перескакивает в начало строки. Проблема известная,
вот к примеру та же история, хоть и 6-летней давности вопрос.
Насколько я понял, проблема в изменении стейта и ререндерах. Окей. Я решил вопрос использованием рефов вместо стейта(костыли, откровенно говоря). Еще нормально работает, если повесить обработчик на onBlur вместо onInput.
Вопрос вот в чем - почему тогда
в этом примере не происходит перескакивания курсора?
Есть ли аксакалы которые сталкивались с чем-то подобным?