How to add WooCommerce fields to the profile form

Hi,

I am trying to manage validation to work and avoid “Field Contains Invalid Value” error on user form.

here is my code:

        $form['fields']['billing_state2'] = array(
            'type' => 'select',
            'source' =>  admin_url( 'admin-ajax.php' ),
             'label' => __( 'State', 'your-text-domain' ),
            'required' => false,
            'options' =>   [], // To be populated dynamically based on country
            'class' => array('select2'), // Add class for Select2

            'attributes' => [
                'data-parent' => 'billing_country',
                'data-options' => wp_json_encode(
                    [
                        'action' => 'get_states'
                    ]

                )

            ]
        );

Could you please advice how to pass action parameter to query?

Hi,

Please clarify your user case in more detail, and we will try to help. Also, please send us the full PHP snippet so that we can review it in more detail.

Hi, thanks for the reply.

I am trying to embed WooCommerce basic fields like Country, State, City/Town, Zip and Phone into user Settings page as required fields. When I am saving the form as I am facing the field Validation error, and I’ve tried to browse around to see if it could be resolved.

Here is my snippet, but solution is incomplete. The issue coming from State field ‘billing_state’.

<?php
add_filter(
    'hivepress/v1/forms/user_update',
    function( $form ) {
        $form['fields']['first_name']['required'] = true;
        $form['fields']['last_name']['required'] = true;

        // Add billing fields
        $form['fields']['billing_country'] = array(
            'type' => 'select',
            'label' => __( 'Country', 'your-text-domain' ),
            'required' => true,


            'options' => WC()->countries->get_allowed_countries(), // Get allowed countries


            'class' => array('select2'), // Add class for Select2
        );

        $form['fields']['billing_state'] = array(
            'type' => 'select',
            'source' =>  admin_url( 'admin-ajax.php' ),
             'label' => __( 'State', 'your-text-domain' ),
            'required' => false,
            'options' =>   [], // To be populated dynamically based on country
            'class' => array('select2'), // Add class for Select2

            'attributes' => [
                'data-parent' => 'billing_country',
                'data-options' => wp_json_encode(
                    [
                        'action' => 'get_states'
                    ]

                )

            ]
        );

        $form['fields']['billing_city'] = array(
            'type' => 'text',
            'label' => __( 'City/Town', 'your-text-domain' ),
            'required' => true,
        );

        $form['fields']['billing_zip'] = array(
            'type' => 'text',
            'label' => __( 'Zip/Postal Code', 'your-text-domain' ),
            'required' => true,
        );

        $form['fields']['billing_phone'] = array(
            'type' => 'text',
            'label' => __( 'Phone', 'your-text-domain' ),
            'required' => true,
        );

        return $form;
    },
    1000
);

// Enqueue JavaScript to load states based on selected country
add_action( 'wp_enqueue_scripts', function() {
    if ( is_user_logged_in() ) {
        wp_enqueue_script( 'load-states', get_stylesheet_directory_uri() . '/js/hivepress/hivepress-state-country-woocommerce-ajax.js', array('jquery'), null, true );
        wp_localize_script( 'load-states', 'ajax_object', array(
            'ajax_url' => admin_url( 'admin-ajax.php' ),
            'states' => WC()->countries->get_states(),
        ));
    }
});

// AJAX to retrieve states based on selected country
add_action( 'wp_ajax_get_states', 'get_states' );
add_action( 'wp_ajax_nopriv_get_states', 'get_states' );

function get_states() {
    $country = $_POST['parent_value'] ?? 'UA';
    $states = WC()->countries->get_states( $country );
    wp_send_json( $states );
}



add_filter('hivepress/v1/models/user', function ($model) {

    $model['fields']['billing_first_name'] = [
        'type' => 'text',
        '_external' => true,
    ];

    $model['fields']['billing_last_name'] = [
        'type' => 'text',
        '_external' => true,
    ];


    $model['fields']['billing_city'] = [
        'type' => 'text',
        '_external' => true,
    ];

    $model['fields']['billing_country'] = [
        'type' => 'text',
        '_external' => true,
    ];

    $model['fields']['billing_state'] = [
        'type' => 'text',
        '_external' => true,
    ];

    $model['fields']['billing_zip'] = [
        'type' => 'text',
        '_external' => true,
    ];

    $model['fields']['billing_phone'] = [
        'type' => 'number',
        '_external' => true,
    ];


    return $model;



});

Here is my JS that handle automatic load of Country/State:

jQuery(document).ready(function($) {
    $('select[name="billing_country"]').change(function() {
        var country = $(this).val();
        console.log(country)
        $.ajax({
            type: 'POST',
            url: ajax_object.ajax_url,
            data: {
                action: 'get_states',
                country: country,
            },
            success: function(response) {
                var $stateField = $('select[name="billing_state"]');
                $stateField.empty(); // Clear existing options
                if (response) {
                    $.each(response, function(key, value) {
                        $stateField.append($('<option></option>').attr('value', key).text(value));
                    });
                }
            }
        });
    });
});

Thanks for looking into this.

Hi,

Unfortunately the JS solution will not work in this case, because when the form is submitted there’s a back-end PHP validation which compares the selected option with the list of all options, and since options are loaded via JS on the front-end only, the validation fails.

I highly recommend adding new fields as user attributes in Users/Attributes (or via the hivepress/v1/models/user/attributes filter hook if you’re familiar with coding, using this approach allows more configuration than using the attributes UI), and then syncing them with WooCommerce field values when the user profile is saved (hivepress/v1/models/user/update action hook). Syncing is needed because HivePress fields and WooCommerce fields have different names in the database.

Hope this helps

@ihor thanks for the response.

Is there way to add an action parameter with the value get_states to the body of the ajax request?

$form['fields']['billing_state'] = array(
            'type' => 'select',
            'source' =>  admin_url( 'admin-ajax.php' ),
             'label' => __( 'State', 'your-text-domain' ),
            'required' => false,
            'options' =>   [], // To be populated dynamically based on country
            'class' => array('select2'), // Add class for Select2

            'attributes' => [
                'data-parent' => 'billing_country',
                'data-options' => wp_json_encode(
                    [
                    ]

                )

            ]
        );

Yes, but the “source” parameter should point to the URL which returns a list of options in JSON format, each JSON item with “id” and “text” keys with corresponding values (the “id” is used for the option value, “text” for the option label.

If there’s a static list, I highly recommend just providing a PHP array in the “options” parameter, but if there’s a very big list which you want to load based on the user text input via AJAX, then the best way is to register an API endpoint register_rest_route() – Function | Developer.WordPress.org which returns a JSON format described above, and use this endpoint URL in the select “source” parameter. On the API endpoint PHP function side which generates a JSON, you can fetch the text typed by the user in the select from the “search” parameter of the request.

Hope this helps

@ihor thanks for the dirrection.

We’ve added new route:


// Регистрация API-метода
add_action('rest_api_init', function () {
    register_rest_route('custom/v1', '/get-states-by-country', [
        'methods' => 'GET',
        'callback' => 'get_states_by_country',
        'permission_callback' => '__return_true', // Если API закрыт, нужно добавить проверку прав доступа
    ]);
});

// Функция для получения списка областей по коду страны
function get_states_by_country(WP_REST_Request $request) {
    // Получаем код страны из запроса
    $country_code = $request->get_param('parent_value');


$search = $request->get_param('search');



    // Проверяем, что WooCommerce активен
    if (!function_exists('WC')) {
        return new WP_Error('woocommerce_not_active', 'WooCommerce не активен', ['status' => 404]);
    }

    // Получаем список областей из WooCommerce
    $states = WC()->countries->get_states($country_code);

    // Если областей для страны нет, возвращаем пустой массив
    if (empty($states)) {
        return [];
    }

    // Форматируем список областей
    $formatted_states = [];
    foreach ($states as $id => $state_name) {
        if ($search && stripos($state_name, $search) === false) {
            continue; // Пропустить области, если параметр поиска не совпадает
        }

        $formatted_states[] = [
            'id' => $id,
            'text' => $state_name,
        ];
    }

    // Возвращаем JSON ответ
    return ['data' => $formatted_states];
}

And here is a piece of code that loads country and state:

function( $form ) {
        $form['fields']['first_name']['required'] = true;
        $form['fields']['last_name']['required'] = true;

        // Add billing fields
        $form['fields']['billing_country'] = array(
            'type' => 'select',
            'label' => __( 'Country', 'your-text-domain' ),
            'required' => true,


            'options' => WC()->countries->get_allowed_countries(), // Get allowed countries


            'class' => array('select2'), // Add class for Select2
        );

        $form['fields']['billing_state'] = array(
            'type' => 'select',
            'source' =>  rest_url('custom/v1/get-states-by-country'),
             'label' => __( 'State', 'your-text-domain' ),
            'required' => true,
            'options' => [], // To be populated dynamically based on country
            'class' => array('select2'), // Add class for Select2

            'attributes' => [
                'data-parent' => 'billing_country',
                'data-options' => wp_json_encode(
                    [
                        'action' => 'get_states'
                    ]

                )

            ]
        );

Few moments here:

  1. At first glance code seems functional, but it doesn’t save the form with the ‘state’ field.

  2. Another thing, when I comment the block of code with ‘state’ to make it dysfunctional, it saves properly. But when I uncomment it back to make it functioning again, it doesn’t load appropriate ‘states’ array but instead loads array of all states from all countries (seems it doesn’t ‘pass parent_value’)

Thanks for any help toward this topic.

Sorry for the delay.

Please make sure that the code which fills the “options” parameter also runs on back-end. You can do this by adding a function for the hivepress/v1/forms/user_update/errors filter hook, it runs after all the values are filled so you can get the form values from the second argument $form->get_values() and error_log them, or you can also check the select field arguments this way via $form->get_fields().

Unfortunately I can’t say for sure which code part causes this without further testing. Another workaround may be used if you have Geolocation installed, if you enabled Regions you can add a function which will check the saved location regions (country/state) and save them for WooCommerce, so these would be pre-filled on checkout if user completed their profile or changed the location later.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.