autohand "Refactor this jQuery-based UI to vanilla JavaScript with modern DOM APIs. Keep all existing behavior identical. Show me a before/after comparison."

What you'll learn

  • How to audit a codebase for jQuery usage and categorize each occurrence
  • How to replace jQuery DOM manipulation, events, and AJAX with modern APIs
  • How to handle edge cases like animations, plugins, and implicit iteration
  • How to migrate file by file with test verification after each step

Before you start

  • Autohand Code installed. Run autohand --version to confirm. See Your First Autohand Session if you need to install it.
  • A project with jQuery. This tutorial works on any codebase using jQuery 1.x, 2.x, or 3.x.
  • Existing tests, if any. Even basic smoke tests will help you verify the refactoring did not change behavior.
  • Git with a clean working tree. Run git stash or commit any changes before starting so you can diff the results clearly.

Assess the jQuery codebase

Before touching any code, get a clear picture of what jQuery is doing in the project. Some usages are straightforward to replace; others need more thought.

Ask Autohand to map out the jQuery usage:

bash

autohand "Scan all JavaScript files in src/ and categorize every jQuery usage by type: DOM selection, event binding, AJAX, animations, and utilities. List the file and line for each one."

A typical output looks like this:

text

jQuery usage in src/ (47 total)

DOM selection (22 occurrences)
  src/ui/form.js:12     $('input[name="email"]')
  src/ui/form.js:34     $('#submit-btn')
  src/ui/modal.js:8     $('.modal-overlay')
  ...

Event binding (15 occurrences)
  src/ui/form.js:45     $(document).on('click', '.btn', handler)
  src/ui/tabs.js:19     $tabs.on('click', switchTab)
  ...

AJAX (5 occurrences)
  src/api/client.js:23  $.ajax({ url, type: 'POST' })
  src/api/client.js:67  $.get(url, callback)
  ...

Animations (5 occurrences)
  src/ui/modal.js:33    $('.modal').fadeIn(200)
  src/ui/dropdown.js:14 $(el).slideDown('fast')
  ...

This inventory tells you where to focus. DOM selection and event binding are trivial to replace. AJAX calls using $.ajax map directly to fetch. Animations require the most judgment since jQuery animations have no direct equivalent without a library.

Tip: Start with files that only use DOM selection and event binding. Leave files with complex animations or plugins for last.

Run the refactoring prompt

Pick a single file to start with. Isolated files with no jQuery plugins are the easiest entry point.

bash

autohand "Refactor src/ui/form.js from jQuery to vanilla JavaScript with modern DOM APIs. Keep all existing behavior identical. Show me a before/after comparison."

Autohand will read the file, identify every jQuery call, and produce a refactored version. The response includes an explanation of each substitution so you understand what changed and why.

For files that have AJAX calls, use a more targeted prompt:

bash

autohand "Refactor src/api/client.js. Replace all $.ajax and $.get calls with the native fetch API. Preserve existing error handling and response parsing logic. Use async/await syntax."

Review the changes

Autohand produces a side-by-side comparison in its output. Here is what a typical DOM manipulation replacement looks like.

Before (jQuery):

javascript

// Showing/hiding an element
$('#error-message').hide();
$('#error-message').text('Email is required').show();

// Adding/removing classes
$('#submit-btn').addClass('loading').prop('disabled', true);

// Reading form values
const email = $('input[name="email"]').val().trim();

// Event delegation
$(document).on('click', '.delete-btn', function() {
    const id = $(this).data('id');
    deleteItem(id);
});

After (vanilla JS):

javascript

// Showing/hiding an element
const errorMsg = document.getElementById('error-message');
errorMsg.hidden = true;
errorMsg.textContent = 'Email is required';
errorMsg.hidden = false;

// Adding/removing classes
const submitBtn = document.getElementById('submit-btn');
submitBtn.classList.add('loading');
submitBtn.disabled = true;

// Reading form values
const email = document.querySelector('input[name="email"]').value.trim();

// Event delegation
document.addEventListener('click', (e) => {
    const btn = e.target.closest('.delete-btn');
    if (!btn) return;
    const id = btn.dataset.id;
    deleteItem(id);
});

For AJAX, the replacement is equally direct:

Before:

javascript

$.ajax({
    url: '/api/users',
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify(payload),
    success: (res) => handleSuccess(res),
    error: (xhr) => handleError(xhr.responseJSON)
});

After:

javascript

fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload)
})
    .then(res => res.ok ? res.json() : res.json().then(err => Promise.reject(err)))
    .then(handleSuccess)
    .catch(handleError);

Verify behavior is preserved

After applying each file's changes, run your test suite immediately. Do not batch multiple files and run tests once at the end.

bash

# After applying the refactored form.js
npm test -- --testPathPattern=form

# Or if you are using Vitest
npx vitest run src/ui/form

If tests pass, commit that file before moving to the next one. This keeps your git history clean and makes any regression easy to bisect.

bash

git add src/ui/form.js
git commit -m "refactor: migrate form.js from jQuery to vanilla JS"

Tip: If you have no automated tests, open the page in a browser after each file and manually exercise the interactions the file handles. Click buttons, submit forms, check that animations still occur. Write down what you checked so you do not forget between files.

Handle edge cases

Some jQuery patterns need extra attention. The three most common ones you will encounter are animation replacements, plugin dependencies, and implicit iteration.

jQuery animations

jQuery's fadeIn, fadeOut, and slideDown have no direct vanilla equivalent. The cleanest replacement is a CSS transition paired with a class toggle:

css

/* Add to your stylesheet */
.fade-target {
    opacity: 1;
    transition: opacity 200ms ease;
}
.fade-target.is-hidden {
    opacity: 0;
    pointer-events: none;
}

javascript

// Instead of $('.modal').fadeIn(200)
document.querySelector('.modal').classList.remove('is-hidden');

// Instead of $('.modal').fadeOut(200)
document.querySelector('.modal').classList.add('is-hidden');

Ask Autohand to handle this for you:

bash

autohand "Replace all jQuery animation calls in src/ui/modal.js with CSS class toggles. Add the required CSS transition rules to src/styles/modal.css."

jQuery plugins

If your code uses third-party jQuery plugins like Select2, Flatpickr (old versions), or DataTables, you have two options: replace them with framework-independent equivalents, or keep the plugin and only remove the jQuery wrapping code around it.

bash

autohand "The codebase uses Select2 for dropdown enhancement. Find all Select2 initializations and suggest vanilla-compatible replacements that preserve the same UX."

Implicit iteration

jQuery silently applies operations to all matched elements. Vanilla JS requires explicit iteration:

javascript

// jQuery - applies to all .tab elements at once
$('.tab').addClass('inactive');

// Vanilla - must iterate explicitly
document.querySelectorAll('.tab').forEach(tab => {
    tab.classList.add('inactive');
});

Autohand handles this automatically when refactoring, but it is useful to know when reviewing the output.

Iterate on remaining files

Once you have one or two files done, you have a pattern. Apply it to the rest of the codebase file by file, running tests after each one.

To speed up files that are similar in structure, describe the pattern you established:

bash

autohand "Refactor src/ui/tabs.js using the same approach we used for form.js: querySelector for selection, classList for class manipulation, addEventListener for events, fetch for AJAX. Apply it consistently."

When all files are done, check that jQuery is no longer imported anywhere:

bash

autohand "Search the entire codebase for any remaining jQuery imports or usages. List them."

If the list is empty, remove jQuery from your dependencies:

bash

npm uninstall jquery
# or
bun remove jquery

Run your full test suite one final time. If everything passes, you are done.

What you learned

  • Scanned a codebase and categorized every jQuery usage by type
  • Replaced jQuery DOM selection, event binding, and AJAX with modern vanilla JavaScript
  • Handled animations with CSS transitions and class toggles instead of jQuery effects
  • Migrated file by file with test runs after each change to preserve behavior

Try next

autohand "Now that jQuery is removed, migrate the codebase to TypeScript starting with the files in src/ui/"