React: Event Handling

Last updated: 2026-08-26

Event handling is the nerve endings of a React app—when users click buttons, enter text, or submit forms, these interactions are responded to through events. React’s evento system is smarter and more unified than native DOM events.


1. What You'll Learn



2. A Story About Form Interaction

(1) Pain Points: 3 Pitfalls in Form Submission

Bob is building a user registration form and writing evento handlers in native JavaScript:

JAVASCRIPT
// Native JS:Various compatibility issues and details need to be handled manually

// 1. Get DOM Element
const form = document.getElementById('register-form')
const nameInput = document.getElementById('name')
const submitBtn = document.getElementById('submit-btn')

// 2. Event Binding
form.addEventListener('submit', function(event) {
  event.preventDefault()  // Prevent the page from refreshing
  
  // 3. Manually Retrieving Form Data
  const formData = new FormData(form)
  const data = Object.fromEntries(formData)
  
  // 4. Submit Data
  fetch('/api/register', {
    method: 'POST',
    body: JSON.stringify(data)
  })
})

// 5. Real-Time Input Validation
nameInput.addEventListener('input', function(event) {
  if (event.target.value.length < 2) {
    showError('Name (at least) 2 characters')
  } else {
    hideError()
  }
})

// 6. Prevent duplicate submissions when the Submit button is clicked
let isSubmitting = false
submitBtn.addEventListener('click', function() {
  if (isSubmitting) return
  isSubmitting = true
  submitBtn.disabled = true
  submitBtn.textContent = 'Submitting......'
})

Bob's problem:

(2) Event Handling in React

JSX
function RegisterForm() {
  const [name, setName] = React.useState('')
  const [isSubmitting, setIsSubmitting] = React.useState(false)

  // Submit for Processing
  function handleSubmit(event) {
    event.preventDefault()
    setIsSubmitting(true)
    
    fetch('/api/register', {
      method: 'POST',
      body: JSON.stringify({ name })
    }).finally(() => setIsSubmitting(false))
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
        placeholder="Please enter your name"
      />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting......' : 'Register'}
      </button>
    </form>
  )
}
▶ Try it Yourself

Benefits: The code was reduced from 30 lines to 15 lines, with no DOM manipulation, no event unbinding issues, and no browser compatibility issues.



3. React Composite Events

React events are not native DOM events, but rather SyntheticEvents—React listens to them at the top level and simulates a cross-browser event system.

Feature Native DOM Events React Composite Events
Binding Method addEventListener / onclick Write directly in JSX
Browser Compatibility Manual Handling of Differences Required Automatic Standardization
Memory Management Listeners Must Be Removed Manually Automatic Cleanup
Event Object Native Event SyntheticEvent
Prevent Bubbling event.stopPropagation() Identical (Standardized)
Block Default event.preventDefault() Same (Standardized)
100%
graph TB
    A[The user clicks the button] --> B[React Root Node Event Handling]
    B --> C[Create a Synthetic Event Object]
    C --> D[Simulated Bubbles/Capture Phase]
    D --> E[Call JSX Bound to the middle handler]
    
    style B fill:#61dafb,color:#000
    style C fill:#1890ff,color:#fff


4. Quick Reference for Common Events

Event Name Trigger Conditions Common Scenarios Event Object Type
onClick Element clicked Button, link, card MouseEvent
onChange Changes to input field content Form inputs, drop-down menus ChangeEvent
onSubmit Submit Form Log In/Sign Up/Search FormEvent
onFocus Element gains focus Input field is highlighted FocusEvent
onBlur Element loses focus Input validation FocusEvent
onKeyDown Press a key Shortcut key, Enter to search KeyboardEvent
onKeyUp Press and Release Real-Time Search KeyboardEvent
onMouseEnter Mouseover Hover Effect MouseEvent
onMouseLeave Mouse-out Hover effect MouseEvent
onScroll Scroll Infinite Scroll UIEvent

▶ Example: Comprehensive Overview of Common Events

Output:

TEXT 📖 Display only
...
Clicked the "Log In" button
Button on Mouse Hover
Mouse leaves button
JSX
// ============================================
// Example:Complete Event Handling for a Login Form
// ============================================

function LoginForm() {
  const [email, setEmail] = React.useState('')
  const [password, setPassword] = React.useState('')
  const [errors, setErrors] = React.useState({})
  const [focusedField, setFocusedField] = React.useState('')

  function handleSubmit(event) {
    event.preventDefault()
    
    // Form Validation
    const newErrors = {}
    if (!email.includes('@')) newErrors.email = 'The email address format is incorrect.'
    if (password.length < 6) newErrors.password = 'Password must be at least 6 chars'
    
    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors)
      return
    }
    
    // Submit Login
    console.log('Log In:', { email, password })
  }

  function handleKeyDown(event) {
    // Press Escape key to clear focus
    if (event.key === 'Escape') {
      event.target.blur()
    }
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '400px', margin: '0 auto' }}>
      <h2>Log In</h2>

      {/* Enter your email address */}
      <div style={{ marginBottom: '16px' }}>
        <label>Email:</label>
        <input
          type="email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          onFocus={() => setFocusedField('email')}
          onBlur={() => { setFocusedField(''); setErrors({...errors, email: undefined}) }}
          onKeyDown={handleKeyDown}
          style={{
            width: '100%',
            padding: '8px',
            borderColor: errors.email ? '#ff4d4f' : focusedField === 'email' ? '#1890ff' : '#d9d9d9'
          }}
        />
        {errors.email && <p style={{ color: '#ff4d4f', fontSize: '12px' }}>{errors.email}</p>}
      </div>

      {/* Password Entry */}
      <div style={{ marginBottom: '16px' }}>
        <label>Password:</label>
        <input
          type="password"
          value={password}
          onChange={e => setPassword(e.target.value)}
          onFocus={() => setFocusedField('password')}
          onBlur={() => setFocusedField('')}
          onKeyDown={handleKeyDown}
          style={{
            width: '100%',
            padding: '8px',
            borderColor: errors.password ? '#ff4d4f' : focusedField === 'password' ? '#1890ff' : '#d9d9d9'
          }}
        />
        {errors.password && <p style={{ color: '#ff4d4f', fontSize: '12px' }}>{errors.password}</p>}
      </div>

      {/* Submit Button */}
      <button
        type="submit"
        onClick={() => console.log('Clicked the "Log In" button')}
        onMouseEnter={() => console.log('Button on Mouse Hover')}
        onMouseLeave={() => console.log('Mouse leaves button')}
        style={{
          width: '100%',
          padding: '10px',
          backgroundColor: '#1890ff',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer'
        }}
      >
        Log In
      </button>
    </form>
  )
}

Output:

TEXT 📖 Display only
Login form with email/password. Invalid email → red border + "The email address format is incorrect." Password <6 → "Password must be at least 6 chars". Submit → console.log({email, password})


5. Event Parameter Passing

(1) Passing Parameters Directly

Wrap it in an arrow function and pass the additional parameters directly:

JSX
function UserList({ users, onDelete }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.name}
          {/* Arrow Functions:Incoming user.id As an additional parameter */}
          <button onClick={() => onDelete(user.id)}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  )
}

// Usage
<UserList
  users={users}
  onDelete={(id) => console.log('Delete User:', id)}
/>
▶ Try it Yourself

(2) Retrieve both the event object and custom parameters

JSX
function ColorPicker({ colors, onSelect }) {
  return (
    <div>
      <p>Select a Color:</p>
      {colors.map(color => (
        <button
          key={color}
          onClick={(event) => {
            // event:Native Synthesis Event Object
            // color:Custom Parameters
            onSelect(color)
            console.log('Click here:', event.clientX, event.clientY)
          }}
          style={{
            backgroundColor: color,
            width: '40px',
            height: '40px',
            border: '2px solid #ddd',
            borderRadius: '50%',
            margin: '4px',
            cursor: 'pointer'
          }}
        />
      ))}
    </div>
  )
}
▶ Try it Yourself

(3) Three Ways to Pass Event Objects

Method Syntax Applicable Scenarios
Implicit Passing onClick={handleClick} No Additional Parameters Required
Arrow Functions onClick={() => handleClick(id)} Requires Parameters
Pass simultaneously onClick={(e) => handleClick(e, id)} Requires an event object + custom parameters


6. Preventing Default Behavior and Bubbling

(1) Prevent the default behavior: preventDefault()

The most common use case is preventing the page from refreshing when a form is submitted:

JSX
function SearchForm() {
  const [query, setQuery] = React.useState('')

  function handleSubmit(event) {
    event.preventDefault()  // Prevent the page from refreshing
    // Execute Custom Search Logic
    console.log('Search:', query)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <button type="submit">Search</button>
    </form>
  )
}

// Other scenarios where the default behavior needs to be overridden:
// - Link Redirection:<a href="#" onClick={e => e.preventDefault()}>
// - Context Menu:onContextMenu={e => e.preventDefault()}
// - Drag-and-Drop File Upload:onDragOver={e => e.preventDefault()}
▶ Try it Yourself

(2) Prevent bubbling: stopPropagation()

JSX
function Modal() {
  return (
    // Click the mask layer to close the pop-up window
    <div
      style={overlayStyle}
      onClick={() => console.log('Click the mask layer → Close Pop-up')}
    >
      {/* Clicking on the pop-up content does not display a speech bubble */}
      <div
        style={modalStyle}
        onClick={(event) => {
          event.stopPropagation()  // Prevent Bubbles from Rising to the Mask Layer
          console.log('Click on the pop-up window content')
        }}
      >
        <h2>Pop-up Title</h2>
        <p>Pop-up Content</p>
        <button onClick={() => console.log('Close')}>Close</button>
      </div>
    </div>
  )
}

// Click "Modal Content" area → only triggers modal onClick
// Click "Overlay" (outside modal) → trigger mask layer onClick
▶ Try it Yourself

7. Complete Example: To-Do List (Comprehensive Event Handling)

JSX
// ============================================
// Example:Todo List(Comprehensive Incident Handling)
// Features:Add a Task、Marked as complete、Delete Task、Double-click to edit
// ============================================

function TodoApp() {
  const [todos, setTodos] = React.useState([
    { id: 1, text: 'Study React Event', done: false },
    { id: 2, text: 'Complete the homework', done: false }
  ])
  const [input, setInput] = React.useState('')

  // 1. Add a Task(onSubmit + Enter key)
  function handleSubmit(event) {
    event.preventDefault()
    if (!input.trim()) return
    
    setTodos([...todos, {
      id: Date.now(),
      text: input.trim(),
      done: false
    }])
    setInput('')
  }

  // 2. Quick Add with the Enter Key(onKeyDown)
  function handleKeyDown(event) {
    if (event.key === 'Enter' && input.trim()) {
      handleSubmit(event)
    }
  }

  // 3. Switch to "Completed" status(onChange)
  function handleToggle(id) {
    setTodos(todos.map(t =>
      t.id === id ? { ...t, done: !t.done } : t
    ))
  }

  // 4. Delete Task(onClick + Parameter Passing)
  function handleDelete(id, event) {
    event.stopPropagation()  // Prevent triggering li the incident
    setTodos(todos.filter(t => t.id !== id))
  }

  // 5. Double-click to edit(onDoubleClick)
  function handleEdit(todo) {
    const newText = prompt('Edit Task:', todo.text)
    if (newText && newText.trim()) {
      setTodos(todos.map(t =>
        t.id === todo.id ? { ...t, text: newText.trim() } : t
      ))
    }
  }

  return (
    <div style={{ maxWidth: '500px', margin: '0 auto' }}>
      <h2>📋 Todo List</h2>

      {/* Input Form */}
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyDown={handleKeyDown}
          placeholder="Input Task,Press Enter to add..."
          style={{
            width: '70%', padding: '8px',
            border: '1px solid #d9d9d9', borderRadius: '4px'
          }}
        />
        <button type="submit" style={{
          padding: '8px 16px', marginLeft: '8px',
          backgroundColor: '#1890ff', color: 'white',
          border: 'none', borderRadius: '4px', cursor: 'pointer'
        }}>
          Add
        </button>
      </form>

      {/* Statistics */}
      <p style={{ color: '#666', fontSize: '14px' }}>
        Total {todos.length} items, Completed {todos.filter(t => t.done).length} items
      </p>

      {/* Task List */}
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {todos.map(todo => (
          <li
            key={todo.id}
            onDoubleClick={() => handleEdit(todo)}
            style={{
              display: 'flex', alignItems: 'center',
              padding: '10px', margin: '4px 0',
              backgroundColor: todo.done ? '#f6ffed' : '#fff',
              border: '1px solid #f0f0f0',
              borderRadius: '4px',
              textDecoration: todo.done ? 'line-through' : 'none',
              color: todo.done ? '#999' : '#333',
              cursor: 'pointer'
            }}
          >
            {/* Checkbox */}
            <input
              type="checkbox"
              checked={todo.done}
              onChange={() => handleToggle(todo.id)}
              style={{ marginRight: '10px' }}
            />

            {/* Mission Text */}
            <span style={{ flex: 1 }}>{todo.text}</span>

            {/* Delete Button */}
            <button
              onClick={(e) => handleDelete(todo.id, e)}
              style={{
                padding: '2px 8px',
                backgroundColor: 'transparent',
                color: '#ff4d4f',
                border: 'none',
                cursor: 'pointer',
                fontSize: '16px'
              }}
            >
              ✕
            </button>
          </li>
        ))}
      </ul>
    </div>
  )
}

Interaction Flow:

  1. Type the task name in the text box, press Enter, or click the "Add" button → The task is added to the list
  2. Check the checkbox → Mark the task as complete (text is struck through and grayed out)
  3. Click the ✕ button → Delete the task (stopPropagation to prevent double-clicking from triggering the edit function)
  4. Double-click any task → A dialog box for editing appears

▶ Example 2: Keyboard Shortcuts and Focus Management

Output:

TEXT 📖 Display only
... ('Log In:', { email, password })
Clicked the "Log In" button
Button on Mouse Hover
Mouse leaves button
JSX
// ============================================
// Example:Shortcut Keys Panel——Integrated Use of Keyboard Events and Focus Management
// Features:Usage onKeyDown Implement Keyboard Shortcuts,Usage onFocus/onBlur Management Focus
// ============================================

function ShortcutPanel() {
  const [output, setOutput] = React.useState('')
  const [activeKeys, setActiveKeys] = React.useState(new Set())
  const inputRef = React.useRef(null)

  // Keyboard Event Handling
  function handleKeyDown(event) {
    const { key, ctrlKey, shiftKey, altKey } = event

    // Ctrl+S:Save
    if (ctrlKey && key === 's') {
      event.preventDefault()
      setOutput('💾 Saved(Ctrl+S)')
    }
    // Ctrl+Z:Revoke
    else if (ctrlKey && key === 'z') {
      event.preventDefault()
      setOutput('↩️ Undo(Ctrl+Z)')
    }
    // Escape:Clear Output
    else if (key === 'Escape') {
      setOutput('')
      event.target.blur()
    }
    // Enter:Confirm
    else if (key === 'Enter') {
      setOutput(`✅ Confirm:${event.target.value || '(Empty input)'}`)
    }

    // Display the currently pressed key
    setActiveKeys(prev => new Set([...prev, key]))
  }

  function handleKeyUp(event) {
    setActiveKeys(prev => {
      const next = new Set(prev)
      next.delete(event.key)
      return next
    })
  }

  // Autofocus
  React.useEffect(() => {
    inputRef.current.focus()
  }, [])

  return (
    <div style={{ maxWidth: '500px', margin: '0 auto' }}>
      <h2>⌨️ Shortcut Keys Panel</h2>

      <input
        ref={inputRef}
        onKeyDown={handleKeyDown}
        onKeyUp={handleKeyUp}
        onFocus={() => setOutput('The input field is currently focused')}
        onBlur={() => setOutput('The input field has lost focus')}
        placeholder="Type here,Try the shortcut keys..."
        style={{
          width: '100%',
          padding: '10px',
          fontSize: '16px',
          border: '2px solid #1890ff',
          borderRadius: '6px',
          outline: 'none'
        }}
      />

      {/* Keyboard Shortcut Tips */}
      <div style={{ marginTop: '12px', display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
        {[
          { key: 'Ctrl+S', desc: 'Save' },
          { key: 'Ctrl+Z', desc: 'Revoke' },
          { key: 'Enter', desc: 'Confirm' },
          { key: 'Escape', desc: 'Clear/blur' }
        ].map(shortcut => (
          <span key={shortcut.key} style={{
            padding: '4px 10px',
            backgroundColor: '#f5f5f5',
            border: '1px solid #d9d9d9',
            borderRadius: '4px',
            fontSize: '12px',
            fontFamily: 'monospace'
          }}>
            {shortcut.key} <span style={{ color: '#999' }}>{shortcut.desc}</span>
          </span>
        ))}
      </div>

      {/* Currently Pressed Key */}
      {activeKeys.size > 0 && (
        <p style={{ marginTop: '8px', fontSize: '13px', color: '#666' }}>
          Currently Pressed Key:{[...activeKeys].join(' + ')}
        </p>
      )}

      {/* Operation Output */}
      {output && (
        <div style={{
          marginTop: '12px',
          padding: '10px',
          backgroundColor: '#f6ffed',
          border: '1px solid #b7eb8f',
          borderRadius: '4px',
          color: '#52c41a',
          fontWeight: 'bold'
        }}>
          {output}
        </div>
      )}
    </div>
  )
}

Output:

TEXT 📖 Display only
Autofocus input. Ctrl+S → "💾 Saved (Ctrl+S)", Ctrl+Z → "↩️ Undo (Ctrl+Z)", Escape → clear, Enter → "✅ Confirm: [text]". Shows pressed keys.

▶ Example 3: Handling Drag-and-Drop Sorting Events

Output:

TEXT 📖 Display only
Subheading: "⌨️ Shortcut Keys Panel". Displays: "⌨️ Shortcut Keys Panel". Input: Type here,Try the shortcut keys.... useEffect manages side effects. useRef references DOM/variable
JSX
function DragSortList() {
  const [items, setItems] = useState(['Apple', 'Banana', 'Cherry', 'Date'])
  const [dragIdx, setDragIdx] = useState(null)

  function handleDragStart(idx) { setDragIdx(idx) }

  function handleDragOver(e, idx) {
    e.preventDefault()
    if (dragIdx === null || dragIdx === idx) return
    setItems(prev => {
      const updated = [...prev]
      const [moved] = updated.splice(dragIdx, 1)
      updated.splice(idx, 0, moved)
      return updated
    })
    setDragIdx(idx)
  }

  function handleDragEnd() { setDragIdx(null) }

  return (
    <div style={{ maxWidth: 300, margin: '0 auto' }}>
      <h3>Drag to Reorder</h3>
      {items.map((item, idx) => (
        <div key={item} draggable
          onDragStart={() => handleDragStart(idx)}
          onDragOver={e => handleDragOver(e, idx)}
          onDragEnd={handleDragEnd}
          style={{
            padding: '8px 12px', marginBottom: 4, borderRadius: 4, cursor: 'grab',
            background: dragIdx === idx ? '#e6f7ff' : '#f5f5f5',
            border: dragIdx === idx ? '2px solid #1890ff' : '2px solid transparent',
          }}>
          {item}
        </div>
      ))}
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
4 draggable items (Apple, Banana, Cherry, Date). Drag to reorder; dragged item gets blue highlight border.

▶ Example 4: Custom Hook for Wrapping Event Listeners

Output:

TEXT 📖 Display only
Displays: "Drag to Reorder". State: items (setter: setItems), dragIdx (setter: setDragIdx)
JSX
function useEventListener(event, handler, element = window) {
  useEffect(() => {
    element.addEventListener(event, handler)
    return () => element.removeEventListener(event, handler)
  }, [event, handler, element])
}

function MouseTracker() {
  const [pos, setPos] = useState({ x: 0, y: 0 })
  const handler = useCallback(e => setPos({ x: e.clientX, y: e.clientY }), [])
  useEventListener('mousemove', handler)

  return (
    <div style={{ padding: 20 }}>
      <p>Mouse: ({pos.x}, {pos.y})</p>
      <div style={{
        width: 200, height: 200, border: '1px solid #ddd', position: 'relative', borderRadius: 4,
      }}>
        <div style={{
          width: 10, height: 10, borderRadius: '50%', background: '#1890ff',
          position: 'absolute', left: Math.min(pos.x - 100, 190), top: Math.min(pos.y - 100, 190),
          transition: 'left 0.1s, top 0.1s',
        }} />
      </div>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
useEventListener hook: automatically adds/removes DOM event listeners. Cleans up on unmount to prevent memory leaks

▶ Example 5: Form Submission and Event Combination

Output:

TEXT 📖 Display only
State: pos (setter: setPos). useEffect manages side effects. useCallback memoizes handler
JSX
function SearchForm({ onSearch }) {
  const [query, setQuery] = useState('')
  const [category, setCategory] = useState('all')

  function handleSubmit(e) {
    e.preventDefault()
    onSearch({ query, category })
  }

  function handleReset() {
    setQuery('')
    setCategory('all')
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: 400, margin: '0 auto' }}>
      <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
        <input value={query} onChange={e => setQuery(e.target.value)}
          placeholder="Search..." style={{ flex: 1, padding: 8, borderRadius: 4 }} />
        <select value={category} onChange={e => setCategory(e.target.value)}
          style={{ padding: 8, borderRadius: 4 }}>
          <option value="all">All</option>
          <option value="electronics">Electronics</option>
          <option value="books">Books</option>
        </select>
      </div>
      <div style={{ display: 'flex', gap: 8 }}>
        <button type="submit" style={{ padding: '6px 16px', cursor: 'pointer' }}>Search</button>
        <button type="button" onClick={handleReset} style={{ padding: '6px 16px', cursor: 'pointer' }}>Reset</button>
      </div>
    </form>
  )
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Search bar + category dropdown (All/Electronics/Books). Search submits {query, category}. Reset clears both fields.

❓ FAQ

Q Why use an arrow function () => handleClick(id) instead of just handleClick(id)?
A If you write onClick={handleClick(id)} directly, React will execute handleClick(id) immediately during rendering, rather than waiting until it’s clicked. This is because it’s a function call (with parentheses), not a function reference (without parentheses). The correct approach is onClick={() => handleClick(id)} (arrow function with delayed execution) or onClick={handleClick} (directly referencing the function name when no arguments are passed).
Q Can you bind multiple identical events to a single element?
A You cannot directly bind two onClick. Solutions: ① Call multiple functions onClick={() => { fn1(); fn2() }} within the same handler; ② Or wrap the element with multiple HOCs. Typically, an element only needs one event handler, within which you can call multiple pieces of logic.
Q What is the difference between React synthetic events and native events?
A React’s SyntheticEvent is a cross-browser wrapper around native events that provides a unified API (such as e.preventDefault() and e.stopPropagation()) and behaves consistently across all browsers. Synthetic events operate under an event delegation mechanism—all events are attached to the root node rather than individual DOM elements. In React 17 and later, events are delegated to the root rather than the document, which prevents conflicts when multiple versions of React coexist.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty ⭐): Create a ButtonCounter component that increments the displayed count by 1 each time the button is clicked. Use the onClick event.
  2. Advanced Exercise (Difficulty ⭐⭐): Create a SearchInput component that implements "debounced search": the search is automatically triggered 500 ms after the user stops typing. Use onChange and useEffect.
  3. Challenge (Difficulty: ⭐⭐⭐): Create a DragAndDropList component that implements drag-and-drop sorting. Use the onDragStart, onDragOver, and onDrop events. List items can be dragged to new positions.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏