Billing State / County is a required field. Error on Woocommerce/WordPress
When customers select specific country like France, South Korea, Vietnam, Germany, the file State/County with be hidden. In a fresh Woocommerce/WordPress site, this field will be not required when it is hidden with certain country. However, in some site, the Billing State / County is a required field
warning pops up suggesting the field is still required. Here, we will explore how this occur and how to resolve it.
Primary Plugins
- Elementor
- Woolementor
- Use Billing Address widget
- Woocommerce
Ideas
- Explore how Woocommerce handle address field among various country
- Explore how Woolementor's Address Form behave differently compared to woocommerce
- Best Guess: Woocommerce's validation library(js) does not refect on the changes Woocommerce made for certain country(in making certain fields invisable)
Finding
- Billing Address Widget does not work properly on none checkout page
- Woolementor Billing Address Widget Demo
- An extra "District" field appear on the demo, but disappear after messing around with the country dropdown (for instance, select China then change it back to France)
Woocommerce
how Woocommerce behave on changed country, in terms of interaction with the server (Ajax)
- select Country/Region
- ajax to
/?wc-ajax=update_order_review
- update
.woocommerce-checkout-review-order-table
&.woocommerce-checkout-payment
according to resule
How #billing_state_field
is hidden
since no information regarding the appearance of the state/county field is transferred after updating the country field, it is likly JavaScript controlling the process
- some .js inject
display: none
in the DOM's style validate-required
class is removed<abbr class="required" title="required">*</abbr>
inside<label>
is replace by<span class="optional">(optional)</span>
<input>
type and class is change tohidden
- after searching for code targetting
#billing_state_field
, foundaddress-i18n.min.js?ver=5.2.2
Relevant source code
In countries that require state/county field
<p class="form-row address-field validate-state validate-required form-row-wide" id="billing_state_field"
data-priority="80" data-o_class="form-row form-row-wide address-field validate-state" style="">
<label
for="billing_state" class="">State / County
<abbr class="required" title="required">*</abbr>
</label>
<span
class="woocommerce-input-wrapper">
<input type="text" id="billing_state" name="billing_state" placeholder=""
data-input-classes="" class="input-text">
</span>
</p>
For countries that don't require state/county field
<p class="form-row address-field validate-state form-row-wide" id="billing_state_field"
data-priority="80" data-o_class="form-row form-row-wide address-field validate-state" style="display: none;">
<label for="billing_state" class="">State / County
<span class="optional">(optional)</span>
</label>
<span class="woocommerce-input-wrapper">
<input type="hidden" id="billing_state" name="billing_state" placeholder="" data-input-classes="" class="hidden">
</span>
</p>
l(document.body).on('country_to_state_changing', function (e, a, i) {
var d = i,
r = 'undefined' != typeof n[a] ? n[a] : n['default'],
t = d.find('#billing_postcode_field, #shipping_postcode_field'),
i = d.find('#billing_city_field, #shipping_city_field'),
a = d.find('#billing_state_field, #shipping_state_field');
t.attr('data-o_class') || (t.attr('data-o_class', t.attr('class')), i.attr('data-o_class', i.attr('class')), a.attr('data-o_class', a.attr('class')));
a = JSON.parse(wc_address_i18n_params.locale_fields);
l.each(a, function (e, a) {
var i = d.find(a),
a = l.extend(!0, {
}, n['default'][e], r[e]);
'undefined' != typeof a.label && i.find('label').html(a.label),
'undefined' != typeof a.placeholder && (i.find(':input').attr('placeholder', a.placeholder), i.find(':input').attr('data-placeholder', a.placeholder), i.find('.select2-selection__placeholder').text(a.placeholder)),
'undefined' != typeof a.placeholder || 'undefined' == typeof a.label || i.find('label').length || (i.find(':input').attr('placeholder', a.label), i.find(':input').attr('data-placeholder', a.label), i.find('.select2-selection__placeholder').text(a.label)),
'undefined' != typeof a.required ? o(i, a.required) : o(i, !1),
'undefined' != typeof a.priority && i.data('priority', a.priority),
'state' !== e && ('undefined' != typeof a.hidden && !0 === a.hidden ? i.hide().find(':input').val('') : i.show()),
Array.isArray(a['class']) && (i.removeClass('form-row-first form-row-last form-row-wide'), i.addClass(a['class'].join(' ')))
}),
Woolementor
How the Billing Address Widget in Woolementor is handling the process
Comparing to the original Woocommerce, for a country required state/county
- class
validate-state
is missing data-o_class
attribute is missing in page load, but reappears after country selectiondata-o_class="form-row form-row-wide address-field validate-required"
- Woocommerce's
validate-state
is replaced byvalidate-required
<p class="form-row form-row-wide address-field validate-required" id="billing_state_field" data-priority="10">
<label
for="billing_state" class="">State / County
<abbr class="required" title="required">*</abbr>
</label>
<span
class="woocommerce-input-wrapper">
<input type="text" class="input-text " name="billing_state" id="billing_state"
placeholder="" value="" autocomplete="address-level1">
</span>
</p>
For a country don't require state/county field
- in attribute
data-o_class
,validate-required
is kept (Possible cause of the warning)
<p class="form-row address-field form-row-wide" id="billing_state_field" data-priority="10" style="display: none;"
data-o_class="form-row form-row-wide address-field validate-required">
<label for="billing_state" class="">State /
County
<span class="optional">(optional)</span>
</label>
<span class="woocommerce-input-wrapper">
<input
type="hidden" id="billing_state" name="billing_state" placeholder="" class="hidden undefined">
</span>
</p>
Solution
Keep the warning(/validation) but make the field state/county reappear
Hardcoding
states.php
'FR' => array('A', 'B', 'C'), /* add state/county */
class-wc-countries.php
'FR' => array(
'postcode' => array(
'priority' => 65,
),
'state' => array(
'required' => true, /* change to true */
),
),
Hook
enable state/county field
function filter_woocommerce_get_country_locale($locales)
{
foreach ($locales as $key => $value) {
if ($key == "FR") {
$locales[$key]['state']['required'] = true;
$locales[$key]['state']['hidden'] = false;
$locales[$key]['state']['placeholder'] = '';
/* $locales[$key]['state']['label'] = 'Region'; */
}
}
return $locales;
}
add_filter('woocommerce_get_country_locale', 'filter_woocommerce_get_country_locale', 10, 1);
function custom_woocommerce_states($states)
{
$states['FR'] = ''; /* display as text input */
$states['FR'] = array( /* display as dropdown */
'ABBRA' => 'display namea',
'ABBRB' => 'display nameb',
);
return $states;
}
add_filter('woocommerce_states', 'custom_woocommerce_states');