WordPress AJAX Plugin Not Updating DIV Content With Server Response

I’m building a WordPress AJAX plugin that should fetch data and update a div element with the response. However, the div is not getting updated with the server results.

Here’s my JavaScript code:

function fetchStudentRecords(user_id) {
  
  // Check if jQuery is loaded
  if (typeof jQuery === 'undefined') {
    console.error('jQuery not found. Make sure jQuery is loaded.');
    return;
  }

  // Get values from input fields
  var subject_code = document.getElementById("subject_code").value;
  var $displayArea = $('#studentRecordsDisplay');
  var user_id = user_id || document.getElementById("user_id").value;
  console.log("Subject Code: " + subject_code);
  console.log("User ID: " + user_id);

  // Check if required fields are filled
  if (!user_id || !subject_code) {
    alert("Please enter both User ID and Subject Code.");
    return;
  }

  alert("Fetching Records for User ID: " + user_id + " and Subject Code: " + subject_code);

  $displayArea.html('<p>Loading Records...</p>');

  // AJAX call to get student records
  $.ajax({
    url: ajaxurl,
    type: 'POST',
    data: {
      action: 'get_student_records',
      user_id: user_id,
      subject_code: subject_code,
      days_limit: 30
    },
    success: function(response) {
      $displayArea.html(response);
    },
    error: function(xhr, status, error) {
      $displayArea.html('<p>Failed to load records. Please try again.</p>');
      console.error('AJAX Error:', status, error);
    }
  });
}

And here’s my PHP handler:

// Hook for logged in users
add_action('wp_ajax_get_student_records', 'get_student_records');

// Hook for non-logged in users
add_action('wp_ajax_nopriv_get_student_records', 'get_student_records');

function get_student_records($user_id, $subject_code, $days_limit) {

    global $wpdb;

    $query = "CALL sp_get_student_records('$user_id', '$subject_code', $days_limit);";
    $wpdb->query($query);
    $records = $wpdb->get_results($query, ARRAY_A);

    if (empty($records)) {
        return "<p>No records found for this user.</p>";
    }

    // Build the HTML output
    $html = "<table><tr><th>DATE</th><th>Score</th></tr>";

    foreach ($records as $record) {
        $html .= "<tr>";
        $html .= "<td>{$record['DATE']}</td>";
        $html .= "<td>{$record['score']}</td>";
        $html .= "</tr>";
    }
    $html .= "</table>";

return $html;
}

The console shows this error:

error   @   myplugin_script.js:63
c   @   jquery.min.js?ver=3.7.1:2
fireWith    @   jquery.min.js?ver=3.7.1:2
l   @   jquery.min.js?ver=3.7.1:2
(anonymous) @   jquery.min.js?ver=3.7.1:2
XMLHttpRequest.send     
send    @   jquery.min.js?ver=3.7.1:2
ajax    @   jquery.min.js?ver=3.7.1:2
(anonymous) @   jquery-migrate.min.js?ver=3.4.1:2
e.<computed>    @   jquery-migrate.min.js?ver=3.4.1:2
fetchStudentRecords  @   myplugin_script.js:48
onchange    @   ?page_id=1238&user_id=954894:516
handleMouseUp_  @   unknown

I expect the AJAX response to populate the displayArea div, but it’s not working.

Found your problem - you’re returning the HTML instead of echoing it. WordPress AJAX handlers need to echo their output, not return it. Change return $html; to echo $html; and add wp_die(); right after.

Also, your function parameters are wrong. Don’t pass them as parameters - grab them from POST data instead. Use $_POST['user_id'], $_POST['subject_code'], and $_POST['days_limit'] inside the function. Your function signature should just be function get_student_records() with no parameters.

I ran into the exact same issues when I started with WordPress AJAX. These fixes sorted it out.

You’ve got a major security hole here. You’re dumping variables straight into your SQL query without sanitizing them - that’s a SQL injection waiting to happen. Use $wpdb->prepare() to fix this:

$query = $wpdb->prepare("CALL sp_get_student_records(%s, %s, %d)", $user_id, $subject_code, $days_limit);

Also, you’re calling $wpdb->query($query) twice - once to execute and once to get results. This runs your stored procedure twice. Just ditch the first $wpdb->query($query) line and stick with $wpdb->get_results(). I made this same mistake and ended up with duplicate entries everywhere because my stored procedures kept running multiple times.

Your ajaxurl variable is probably undefined. WordPress doesn’t automatically make ajaxurl available everywhere - you’ve got to localize it first with wp_localize_script() in your PHP file. Try something like wp_localize_script('your-script-handle', 'ajax_object', array('ajax_url' => admin_url('admin-ajax.php'))); then use ajax_object.ajax_url in your JS instead of just ajaxurl.