\n```\n\n### 3. Практический пример: Сохранение данных при blur\n\n```javascript\n// Классический паттерн: сохранять при blur, но НЕ сохранять при клике на кнопку отмены\n\nfunction UserProfileForm() {\n const [formData, setFormData] = useState({ name: '', email: '' });\n const [isSaving, setIsSaving] = useState(false);\n const inputRef = useRef(null);\n\n const handleInputBlur = async (e) => {\n // blur срабатывает когда user уходит с input\n // Но если он кликает на \"Отмена\" - мы НЕ хотим сохранять\n\n // Решение 1: Задержка (race condition!)\n setTimeout(() => {\n // К этому моменту уже сработал click \"Отмены\"\n setIsSaving(true);\n api.saveUser(formData).finally(() => setIsSaving(false));\n }, 100);\n };\n\n const handleCancel = () => {\n // Если это сработает ДО blur - отмена сработает\n // Но blur все равно может сработать из-за race condition\n setFormData({ name: '', email: '' });\n inputRef.current?.focus(); // Возвращаем фокус\n };\n\n return (\n
\n setFormData({ ...formData, name: e.target.value })}\n />\n \n
\n );\n}\n```\n\n### 4. Правильное решение с mouseDown\n\n```javascript\n// Решение: используем mousedown вместо click для кнопки отмены\n\nfunction UserProfileForm() {\n const [formData, setFormData] = useState({ name: '', email: '' });\n const [isSaving, setIsSaving] = useState(false);\n const inputRef = useRef(null);\n const isCancellingRef = useRef(false);\n\n const handleInputBlur = async () => {\n // Проверяем был ли клик на кнопку отмены\n if (isCancellingRef.current) {\n isCancellingRef.current = false;\n return; // Не сохраняем\n }\n\n // Сохраняем при blur\n setIsSaving(true);\n try {\n await api.saveUser(formData);\n } finally {\n setIsSaving(false);\n }\n };\n\n // mousedown срабатывает ДО blur!\n const handleCancelMouseDown = () => {\n isCancellingRef.current = true;\n setFormData({ name: '', email: '' });\n };\n\n return (\n
\n setFormData({ ...formData, name: e.target.value })}\n onBlur={handleInputBlur}\n placeholder=\"Username\"\n />\n inputRef.current?.focus()}\n >\n Cancel\n \n \n
\n );\n}\n```\n\n### 5. Использование onMouseDown для dropdown меню\n\n```javascript\n// Классический паттерн: dropdown закрывается при blur input\n// Но нужно чтобы можно было кликать на items в dropdown\n\nfunction SearchWithDropdown() {\n const [query, setQuery] = useState('');\n const [isDropdownOpen, setIsDropdownOpen] = useState(false);\n const [selectedItem, setSelectedItem] = useState(null);\n\n const handleInputBlur = () => {\n // Закрываем dropdown когда input теряет фокус\n // Но если user клик на item в dropdown - это не сработает!\n // Потому что mousedown на item сработает ДО blur\n setTimeout(() => setIsDropdownOpen(false), 100);\n };\n\n const handleItemMouseDown = (item) => {\n // mousedown срабатывает ДО blur input\n // Поэтому мы можем выбрать item до того как blur закроет dropdown\n setSelectedItem(item);\n setQuery(item.label);\n setIsDropdownOpen(false);\n };\n\n return (\n
\n setQuery(e.target.value)}\n onFocus={() => setIsDropdownOpen(true)}\n onBlur={handleInputBlur}\n placeholder=\"Search...\"\n />\n {isDropdownOpen && (\n
\n {items.map((item) => (\n handleItemMouseDown(item)}\n className=\"p-2 hover:bg-gray-100 cursor-pointer\"\n >\n {item.label}\n
\n ))}\n
\n )}\n {selectedItem &&

Selected: {selectedItem.label}

}\n
\n );\n}\n```\n\n### 6. Event bubbling и capturing\n\n```javascript\n// blur НЕ bubbles (не всплывает), поэтому parent div не получит blur\n\nconst parent = document.getElementById('parent');\nconst input = document.getElementById('input');\nconst button = document.getElementById('button');\n\ninput.addEventListener('blur', () => {\n console.log('blur на input');\n});\n\nparent.addEventListener('blur', () => {\n console.log('blur на parent - НЕ СРАБОТАЕТ');\n}, true); // даже с capturing=true это не сработает\n\n// click БЫ bubbles:\nbutton.addEventListener('click', () => {\n console.log('click на button');\n});\n\nparent.addEventListener('click', () => {\n console.log('click на parent - СРАБОТАЕТ (bubbling)');\n});\n```\n\n### 7. focusout vs blur\n\n```javascript\n// focusout это более полезный event\n\nconst input = document.getElementById('input');\n\ninput.addEventListener('blur', () => {\n console.log('blur - не bubbles');\n});\n\ninput.addEventListener('focusout', () => {\n console.log('focusout - bubbles!');\n});\n\nconst parent = document.getElementById('parent');\nparent.addEventListener('focusout', () => {\n console.log('focusout поймана на parent - сработает!');\n});\n\n// focusout полезнее для form validation потому что можно поймать на форме\n```\n\n### 8. Полная демонстрация порядка\n\n```javascript\n// HTML\n// \n// \n\nconst input = document.getElementById('input');\nconst button = document.getElementById('button');\n\nconst log = (msg) => {\n const log = document.getElementById('log');\n log.innerHTML += msg + '
';\n};\n\n// Input события\ninput.addEventListener('focus', () => log('INPUT: focus'));\ninput.addEventListener('mousedown', () => log('INPUT: mousedown'));\ninput.addEventListener('mouseup', () => log('INPUT: mouseup'));\ninput.addEventListener('click', () => log('INPUT: click'));\ninput.addEventListener('blur', () => log('INPUT: blur'));\n\n// Button события\nbutton.addEventListener('mousedown', () => log('BUTTON: mousedown'));\nbutton.addEventListener('mouseup', () => log('BUTTON: mouseup'));\nbutton.addEventListener('click', () => log('BUTTON: click'));\n\n// Сценарий: input в фокусе, user кликает на button\n// Вывод:\n// BUTTON: mousedown\n// INPUT: blur\n// BUTTON: mouseup\n// BUTTON: click\n```\n\n### 9. Практическая применение в React\n\n```typescript\ninterface EditableFieldProps {\n initialValue: string;\n onSave: (value: string) => Promise;\n}\n\nfunction EditableField({ initialValue, onSave }: EditableFieldProps) {\n const [value, setValue] = useState(initialValue);\n const [isEditing, setIsEditing] = useState(false);\n const isSavingRef = useRef(false);\n\n const handleBlur = async () => {\n if (value === initialValue || isSavingRef.current) return;\n\n try {\n isSavingRef.current = true;\n await onSave(value);\n } catch (error) {\n setValue(initialValue);\n alert('Failed to save');\n } finally {\n isSavingRef.current = false;\n setIsEditing(false);\n }\n };\n\n const handleCancelMouseDown = () => {\n // mousedown срабатывает ДО blur\n setValue(initialValue);\n };\n\n if (!isEditing) {\n return (\n setIsEditing(true)}\n className=\"cursor-pointer p-2 hover:bg-gray-100\"\n >\n {value}\n \n );\n }\n\n return (\n
\n setValue(e.target.value)}\n onBlur={handleBlur}\n />\n \n
\n );\n}\n```\n\n### 10. Key Takeaway\n\n```javascript\n// Критически важный факт о порядке событий:\n\n// Когда user кликает на элемент который не в фокусе:\n// 1. Element теряющий фокус: BLUR срабатывает ПЕРВЫМ\n// 2. Element получающий фокус: FOCUS срабатывает\n// 3. Element в котором был клик: MOUSEDOWN -> MOUSEUP -> CLICK\n\n// Используйте это для:\n// - Сохранения при blur с проверкой mousedown флага\n// - Dropdown меню (mousedown вместо click)\n// - Отмены операций с mousedown\n// - Form validation на focusout (не blur)\n```\n\n## Итого\n\n1. **blur срабатывает раньше click** - это ключевой момент\n2. **blur не bubbles** - используйте focusout если нужен bubbling\n3. **Используйте mousedown** для кнопок в dropdowns и отмены\n4. **focusout более полезнее** для form-level обработки\n5. **Порядок**: mousedown -> blur -> mouseup -> click\n\nЭто знание критично для правильной работы с формами, dropdown меню и сохранением данных при потере фокуса.","dateCreated":"2026-04-03T17:56:54.189901","upvoteCount":0,"author":{"@type":"Person","name":"claude-haiku-4.5"}}}}
← Назад к вопросам

Как связана по логике работа click с onblur?

1.7 Middle🔥 201 комментариев
#Soft Skills и рабочие процессы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Взаимосвязь Click и onBlur в браузере

Это отличный вопрос, который показывает понимание порядка срабатывания событий (event order) в браузере. Объясню связь и приведу практические примеры.

1. Порядок срабатывания событий

Сценарий: User кликает на кнопку, пока input в фокусе

// Timeline событий:

const input = document.querySelector('input');
const button = document.querySelector('button');

input.addEventListener('focus', () => console.log('1. INPUT FOCUSED'));
input.addEventListener('blur', () => console.log('3. INPUT BLURRED'));
button.addEventListener('click', () => console.log('2. BUTTON CLICKED'));

// Если user кликает на button когда input в фокусе:
// Вывод:
// 1. INPUT FOCUSED (когда user кликает на input)
// 3. INPUT BLURRED (когда user кликает на button)
// 2. BUTTON CLICKED (во время клика на button)

// ВАЖНО: blur срабатывает ДО click на другом элементе!

2. Точный порядок событий

<input type="text" id="username" placeholder="Enter username" />
<button id="submit">Submit</button>

<script>
  const input = document.getElementById('username');
  const button = document.getElementById('submit');

  input.addEventListener('focus', (e) => {
    console.log('INPUT: focus event');
  });

  input.addEventListener('blur', (e) => {
    console.log('INPUT: blur event - element is losing focus');
  });

  button.addEventListener('mousedown', (e) => {
    console.log('BUTTON: mousedown event');
  });

  button.addEventListener('click', (e) => {
    console.log('BUTTON: click event');
  });

  button.addEventListener('mouseup', (e) => {
    console.log('BUTTON: mouseup event');
  });

  // Если input в фокусе и user кликает на button:
  // Порядок:
  // 1. BUTTON: mousedown event
  // 2. INPUT: blur event - element is losing focus
  // 3. BUTTON: mouseup event
  // 4. BUTTON: click event
</script>

3. Практический пример: Сохранение данных при blur

// Классический паттерн: сохранять при blur, но НЕ сохранять при клике на кнопку отмены

function UserProfileForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [isSaving, setIsSaving] = useState(false);
  const inputRef = useRef(null);

  const handleInputBlur = async (e) => {
    // blur срабатывает когда user уходит с input
    // Но если он кликает на "Отмена" - мы НЕ хотим сохранять

    // Решение 1: Задержка (race condition!)
    setTimeout(() => {
      // К этому моменту уже сработал click "Отмены"
      setIsSaving(true);
      api.saveUser(formData).finally(() => setIsSaving(false));
    }, 100);
  };

  const handleCancel = () => {
    // Если это сработает ДО blur - отмена сработает
    // Но blur все равно может сработать из-за race condition
    setFormData({ name: '', email: '' });
    inputRef.current?.focus(); // Возвращаем фокус
  };

  return (
    <div>
      <input
        ref={inputRef}
        onBlur={handleInputBlur}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
      />
      <button onClick={handleCancel}>Cancel</button>
    </div>
  );
}

4. Правильное решение с mouseDown

// Решение: используем mousedown вместо click для кнопки отмены

function UserProfileForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [isSaving, setIsSaving] = useState(false);
  const inputRef = useRef(null);
  const isCancellingRef = useRef(false);

  const handleInputBlur = async () => {
    // Проверяем был ли клик на кнопку отмены
    if (isCancellingRef.current) {
      isCancellingRef.current = false;
      return; // Не сохраняем
    }

    // Сохраняем при blur
    setIsSaving(true);
    try {
      await api.saveUser(formData);
    } finally {
      setIsSaving(false);
    }
  };

  // mousedown срабатывает ДО blur!
  const handleCancelMouseDown = () => {
    isCancellingRef.current = true;
    setFormData({ name: '', email: '' });
  };

  return (
    <div className="form">
      <input
        ref={inputRef}
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        onBlur={handleInputBlur}
        placeholder="Username"
      />
      <button
        onMouseDown={handleCancelMouseDown}
        onClick={() => inputRef.current?.focus()}
      >
        Cancel
      </button>
      <button type="submit">Save</button>
    </div>
  );
}

5. Использование onMouseDown для dropdown меню

// Классический паттерн: dropdown закрывается при blur input
// Но нужно чтобы можно было кликать на items в dropdown

function SearchWithDropdown() {
  const [query, setQuery] = useState('');
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
  const [selectedItem, setSelectedItem] = useState(null);

  const handleInputBlur = () => {
    // Закрываем dropdown когда input теряет фокус
    // Но если user клик на item в dropdown - это не сработает!
    // Потому что mousedown на item сработает ДО blur
    setTimeout(() => setIsDropdownOpen(false), 100);
  };

  const handleItemMouseDown = (item) => {
    // mousedown срабатывает ДО blur input
    // Поэтому мы можем выбрать item до того как blur закроет dropdown
    setSelectedItem(item);
    setQuery(item.label);
    setIsDropdownOpen(false);
  };

  return (
    <div className="relative">
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        onFocus={() => setIsDropdownOpen(true)}
        onBlur={handleInputBlur}
        placeholder="Search..."
      />
      {isDropdownOpen && (
        <div className="absolute bg-white border rounded mt-1">
          {items.map((item) => (
            <div
              key={item.id}
              onMouseDown={() => handleItemMouseDown(item)}
              className="p-2 hover:bg-gray-100 cursor-pointer"
            >
              {item.label}
            </div>
          ))}
        </div>
      )}
      {selectedItem && <p>Selected: {selectedItem.label}</p>}
    </div>
  );
}

6. Event bubbling и capturing

// blur НЕ bubbles (не всплывает), поэтому parent div не получит blur

const parent = document.getElementById('parent');
const input = document.getElementById('input');
const button = document.getElementById('button');

input.addEventListener('blur', () => {
  console.log('blur на input');
});

parent.addEventListener('blur', () => {
  console.log('blur на parent - НЕ СРАБОТАЕТ');
}, true); // даже с capturing=true это не сработает

// click БЫ bubbles:
button.addEventListener('click', () => {
  console.log('click на button');
});

parent.addEventListener('click', () => {
  console.log('click на parent - СРАБОТАЕТ (bubbling)');
});

7. focusout vs blur

// focusout это более полезный event

const input = document.getElementById('input');

input.addEventListener('blur', () => {
  console.log('blur - не bubbles');
});

input.addEventListener('focusout', () => {
  console.log('focusout - bubbles!');
});

const parent = document.getElementById('parent');
parent.addEventListener('focusout', () => {
  console.log('focusout поймана на parent - сработает!');
});

// focusout полезнее для form validation потому что можно поймать на форме

8. Полная демонстрация порядка

// HTML
// <input id="input" />
// <button id="button">Click me</button>

const input = document.getElementById('input');
const button = document.getElementById('button');

const log = (msg) => {
  const log = document.getElementById('log');
  log.innerHTML += msg + '<br>';
};

// Input события
input.addEventListener('focus', () => log('INPUT: focus'));
input.addEventListener('mousedown', () => log('INPUT: mousedown'));
input.addEventListener('mouseup', () => log('INPUT: mouseup'));
input.addEventListener('click', () => log('INPUT: click'));
input.addEventListener('blur', () => log('INPUT: blur'));

// Button события
button.addEventListener('mousedown', () => log('BUTTON: mousedown'));
button.addEventListener('mouseup', () => log('BUTTON: mouseup'));
button.addEventListener('click', () => log('BUTTON: click'));

// Сценарий: input в фокусе, user кликает на button
// Вывод:
// BUTTON: mousedown
// INPUT: blur
// BUTTON: mouseup
// BUTTON: click

9. Практическая применение в React

interface EditableFieldProps {
  initialValue: string;
  onSave: (value: string) => Promise<void>;
}

function EditableField({ initialValue, onSave }: EditableFieldProps) {
  const [value, setValue] = useState(initialValue);
  const [isEditing, setIsEditing] = useState(false);
  const isSavingRef = useRef(false);

  const handleBlur = async () => {
    if (value === initialValue || isSavingRef.current) return;

    try {
      isSavingRef.current = true;
      await onSave(value);
    } catch (error) {
      setValue(initialValue);
      alert('Failed to save');
    } finally {
      isSavingRef.current = false;
      setIsEditing(false);
    }
  };

  const handleCancelMouseDown = () => {
    // mousedown срабатывает ДО blur
    setValue(initialValue);
  };

  if (!isEditing) {
    return (
      <div
        onClick={() => setIsEditing(true)}
        className="cursor-pointer p-2 hover:bg-gray-100"
      >
        {value}
      </div>
    );
  }

  return (
    <div className="flex gap-2">
      <input
        autoFocus
        value={value}
        onChange={(e) => setValue(e.target.value)}
        onBlur={handleBlur}
      />
      <button onMouseDown={handleCancelMouseDown} onClick={() => setIsEditing(false)}>
        Cancel
      </button>
    </div>
  );
}

10. Key Takeaway

// Критически важный факт о порядке событий:

// Когда user кликает на элемент который не в фокусе:
// 1. Element теряющий фокус: BLUR срабатывает ПЕРВЫМ
// 2. Element получающий фокус: FOCUS срабатывает
// 3. Element в котором был клик: MOUSEDOWN -> MOUSEUP -> CLICK

// Используйте это для:
// - Сохранения при blur с проверкой mousedown флага
// - Dropdown меню (mousedown вместо click)
// - Отмены операций с mousedown
// - Form validation на focusout (не blur)

Итого

  1. blur срабатывает раньше click - это ключевой момент
  2. blur не bubbles - используйте focusout если нужен bubbling
  3. Используйте mousedown для кнопок в dropdowns и отмены
  4. focusout более полезнее для form-level обработки
  5. Порядок: mousedown -> blur -> mouseup -> click

Это знание критично для правильной работы с формами, dropdown меню и сохранением данных при потере фокуса.

Как связана по логике работа click с onblur? | PrepBro