Custom Column in CPT admin table not updated after Quick Edit save

When the Update button is clicked in Quick Edit the post_meta are properly updated, values saved in database, but column’s content is not updated by Ajax.

Indeed, the Quick Edit feature makes no attempt to update or reload individual admin columns. It instead replaces the row with whatever the AJAX endpoint returned.

Here is the implementation:

https://github.com/WordPress/WordPress/blob/master/wp-admin/js/inline-edit-post.js#L386-L451

    /**
     * Saves the changes made in the quick edit window to the post.
     * Ajax saving is only for Quick Edit and not for bulk edit.
     *
     * @since 2.7.0
     *
     * @param {number} id The ID for the post that has been changed.
     * @return {boolean} False, so the form does not submit when pressing
     *                   Enter on a focused field.
     */
    save : function(id) {
        var params, fields, page = $('.post_status_page').val() || '';

        if ( typeof(id) === 'object' ) {
            id = this.getId(id);
        }

        $( 'table.widefat .spinner' ).addClass( 'is-active' );

        params = {
            action: 'inline-save',
            post_type: typenow,
            post_ID: id,
            edit_date: 'true',
            post_status: page
        };

        fields = $('#edit-'+id).find(':input').serialize();
        params = fields + '&' + $.param(params);

        // Make Ajax request.
        $.post( ajaxurl, params,
            function(r) {
                var $errorNotice = $( '#edit-' + id + ' .inline-edit-save .notice-error' ),
                    $error = $errorNotice.find( '.error' );

                $( 'table.widefat .spinner' ).removeClass( 'is-active' );

                if (r) {
                    if ( -1 !== r.indexOf( '<tr' ) ) {
                        $(inlineEditPost.what+id).siblings('tr.hidden').addBack().remove();
                        $('#edit-'+id).before(r).remove();
                        $( inlineEditPost.what + id ).hide().fadeIn( 400, function() {
                            // Move focus back to the Quick Edit button. $( this ) is the row being animated.
                            $( this ).find( '.editinline' )
                                .attr( 'aria-expanded', 'false' )
                                .focus();
                            wp.a11y.speak( wp.i18n.__( 'Changes saved.' ) );
                        });
                    } else {
                        r = r.replace( /<.[^<>]*?>/g, '' );
                        $errorNotice.removeClass( 'hidden' );
                        $error.html( r );
                        wp.a11y.speak( $error.text() );
                    }
                } else {
                    $errorNotice.removeClass( 'hidden' );
                    $error.text( wp.i18n.__( 'Error while saving the changes.' ) );
                    wp.a11y.speak( wp.i18n.__( 'Error while saving the changes.' ) );
                }
            },
        'html');

        // Prevent submitting the form when pressing Enter on a focused field.
        return false;
    },

The function that handles this is function wp_ajax_inline_save() {

https://github.com/WordPress/WordPress/blob/0e3147c40e91f6eb1f57585724be173e3c04a719/wp-admin/includes/ajax-actions.php#L1981

Which is the same that powers post updates in the classic editor.

So I tested a custom column with this code:

<?php
/**
 * Plugin Name: Custom columns
 */

add_filter(
    'manage_post_posts_columns',
    function( $columns ) {
        return array_merge( $columns, [ 'tomcol' => 'Toms Awesome Column' ] );
    }
);

add_action(
    'manage_post_posts_custom_column',
    function ( $column_key, $post_id ) {
        if ( $column_key === 'tomcol' ) {
            $meta = get_post_meta( $post_id, 'tomcol', true );
            echo esc_html( $meta );
        }
    },
    10,
    2
);

add_action(
    'quick_edit_custom_box',
    function ( $column_name, $post_type ) {
        if ( 'tomcol' !== $column_name ) {
            return;
        }
        $meta = get_post_meta( $post_id, 'tomcol', true );
        ?>
        <fieldset class="inline-edit-col-right">
            <div class="inline-edit-col">
                <div class="inline-edit-group wp-clearfix">
                    <label class="alignleft">
                        <span class="title">Toms Column</span>
                        <input type="text" name="tomcol" value="<?php echo esc_html( $meta ); ?>">
                    </label>
                </div>
            </div>
        </fieldset>
        <?php
    },
    10,
    2
);

add_action(
    'admin_footer',
    function () {
        ?>
        <script>
        jQuery( function( $ ) {
            var wp_inline_edit_function = inlineEditPost.edit;
            inlineEditPost.edit = function( post_id ) {
                wp_inline_edit_function.apply( this, arguments );
                var id = 0;
                if ( typeof( post_id ) == 'object' ) {
                    id = parseInt( this.getId( post_id ) );
                }

                if ( id > 0 ) {
                    var specific_post_edit_row = $( '#edit-' + id );
                    var specific_post_row = $( '#post-' + id );
                    var url = $( '.column-tomcol', specific_post_row ).text();
                    $( ':input[name="tomcol"]', specific_post_edit_row ).val( url );
                }
            }
        });
        </script>
        <?php
    }
);

add_action(
    'save_post',
    function ( $post_id ){
        global $post;
        if ( empty( $_POST ) ) {
            return $post_id;
        }
        if ( ! current_user_can( 'edit_post', $post_id ) ) {
            return $post_id;
        }
        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
            return $post_id;
        }
        if ( isset( $post->post_type ) && $post->post_type == 'revision' ) {
            return $post_id;
        }

        if ( isset( $_POST['tomcol'] ) ) {
            update_post_meta( $post_id, 'tomcol', sanitize_text_field( wp_unslash( $_POST['tomcol'] ) ) );
        }
    }
);

And it worked. The column updated on AJAX refresh. So I can only conclude that your column is indeed being updated, but with stale values, or a silly mistake/typo has been made somewhere

The important distinction though, is that the column was not updated, but rather the row was replaced completely