diff --git a/components/elements/index.js b/components/elements/index.js
index 76031fe3..3134fee3 100644
--- a/components/elements/index.js
+++ b/components/elements/index.js
@@ -29,6 +29,37 @@ import { RenderFileUpload } from "./fields/renderFileUpload";
export { RenderSubFields } from "./fields/renderSubFields";
export { FieldArrayForm } from "./fields/fieldArrayForm";
+const getValidationMessages = (t) => ({
+ requiredMessage: t("newappeal:is-required-label"),
+ emojiNotAllowedMessage: t("newappeal:emojis-not-allowed-label"),
+ invalidPostcodeMessage: t("newappeal:invalid-postcode-label")
+});
+
+const hasParentFieldValue = ({ parentField, form, formProps }) =>
+ parentField != false &&
+ !_.isEmpty(formProps[form]) &&
+ _.has(formProps[form].values, parentField);
+
+const isVisibleByEquality = ({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+}) =>
+ (hasParentFieldValue({ parentField, form, formProps }) &&
+ formProps[form].values[parentField]) == parentFieldShowOnValue;
+
+const isVisibleByInclusion = ({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+}) =>
+ hasParentFieldValue({ parentField, form, formProps }) &&
+ parentFieldShowOnValue.indexOf(
+ formProps[form].values[parentField].toString()
+ ) > -1;
+
export function Textfield(props) {
const {
name,
@@ -128,19 +159,22 @@ export function Textfield(props) {
>
);
} else {
- var showIfHasParentShowValue =
- (parentField != false &&
- !_.isEmpty(formProps[form]) &&
- _.has(formProps[form].values, parentField) &&
- formProps[form].values[parentField]) == parentFieldShowOnValue;
+ var showIfHasParentShowValue = isVisibleByEquality({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+ });
// console.log(parentField, showIfHasParentShowValue);
// console.log(parentField, formProps, form);
//console.log("validation props:", validation);
- const requiredMessage = t("newappeal:is-required-label");
- const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
- const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
+ const {
+ requiredMessage,
+ emojiNotAllowedMessage,
+ invalidPostcodeMessage
+ } = getValidationMessages(t);
return (
{parentField != false ? (
@@ -222,13 +256,12 @@ export function MultiLinefield(props) {
return errors;
};
- var showIfHasParentShowValue =
- parentField != false &&
- !_.isEmpty(formProps[form]) &&
- _.has(formProps[form].values, parentField) &&
- parentFieldShowOnValue.indexOf(
- formProps[form].values[parentField].toString()
- ) > -1;
+ var showIfHasParentShowValue = isVisibleByInclusion({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+ });
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
@@ -237,9 +270,8 @@ export function MultiLinefield(props) {
let { t } = useTranslation();
const translatedLabel = FieldsTranslations(props.label);
- const requiredMessage = t("newappeal:is-required-label");
- const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
- const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
+ const { requiredMessage, emojiNotAllowedMessage, invalidPostcodeMessage } =
+ getValidationMessages(t);
return (
<>
{parentField != false ? (
@@ -335,22 +367,20 @@ export function RichMultiLinefield(props) {
const required = (value) =>
value ? undefined : t("newappeal:is-required-label");
- var showIfHasParentShowValue =
- parentField != false &&
- !_.isEmpty(formProps[form]) &&
- _.has(formProps[form].values, parentField) &&
- parentFieldShowOnValue.indexOf(
- formProps[form].values[parentField].toString()
- ) > -1;
+ var showIfHasParentShowValue = isVisibleByInclusion({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+ });
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
//console.log(showIfHasParentShowValue, formProps[form].values[parentField]);
let { t } = useTranslation();
- const requiredMessage = t("newappeal:is-required-label");
- const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
- const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
+ const { requiredMessage, emojiNotAllowedMessage, invalidPostcodeMessage } =
+ getValidationMessages(t);
return (
<>
{parentField != false ? (
@@ -445,15 +475,15 @@ export function DateFieldPicker(props) {
let dateEnd = props.dateEnd;
let { t } = useTranslation();
const required = (value) => (value ? undefined : "Required");
- var showIfHasParentShowValue =
- (parentField != false &&
- !_.isEmpty(formProps[form]) &&
- _.has(formProps[form].values, parentField) &&
- formProps[form].values[parentField]) == parentFieldShowOnValue;
+ var showIfHasParentShowValue = isVisibleByEquality({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+ });
- const requiredMessage = t("newappeal:is-required-label");
- const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
- const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
+ const { requiredMessage, emojiNotAllowedMessage, invalidPostcodeMessage } =
+ getValidationMessages(t);
// a and b are javascript Date objects
function dateDiffInDays(a, b) {
@@ -621,13 +651,12 @@ export function YesNofield(props) {
const yesNo = router.locale == "cy" ? ["Ydw", "Na"] : ["Yes", "No"];
const required = (value) => (value ? undefined : "Required");
- var showIfHasParentShowValue =
- parentField != false &&
- !_.isEmpty(formProps[form]) &&
- _.has(formProps[form].values, parentField) &&
- parentFieldShowOnValue.indexOf(
- formProps[form].values[parentField].toString()
- ) > -1;
+ var showIfHasParentShowValue = isVisibleByInclusion({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+ });
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
@@ -697,20 +726,18 @@ export function Radiofield(props) {
.split(",");
//parentFieldShowOnValue
- var showIfHasParentShowValue =
- parentField != false &&
- !_.isEmpty(formProps[form]) &&
- _.has(formProps[form].values, parentField) &&
- parentFieldShowOnValue.indexOf(
- formProps[form].values[parentField].toString()
- ) > -1;
+ var showIfHasParentShowValue = isVisibleByInclusion({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+ });
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
- const requiredMessage = t("newappeal:is-required-label");
- const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
- const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
+ const { requiredMessage, emojiNotAllowedMessage, invalidPostcodeMessage } =
+ getValidationMessages(t);
return (
<>
{parentField != false ? (
@@ -849,21 +876,19 @@ export function NumericField(props) {
: undefined;
//parentFieldShowOnValue
- var showIfHasParentShowValue =
- parentField != false &&
- !_.isEmpty(formProps[form]) &&
- _.has(formProps[form].values, parentField) &&
- parentFieldShowOnValue.indexOf(
- formProps[form].values[parentField].toString()
- ) > -1;
+ var showIfHasParentShowValue = isVisibleByInclusion({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+ });
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
//console.log("parentField:", parentField, showIfHasParentShowValue);
- const requiredMessage = t("newappeal:is-required-label");
- const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
- const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
+ const { requiredMessage, emojiNotAllowedMessage, invalidPostcodeMessage } =
+ getValidationMessages(t);
return (
<>
{parentField != false ? (
@@ -968,13 +993,12 @@ export function DecimalField(props) {
: undefined;
//parentFieldShowOnValue
- var showIfHasParentShowValue =
- parentField != false &&
- !_.isEmpty(formProps[form]) &&
- _.has(formProps[form].values, parentField) &&
- parentFieldShowOnValue.indexOf(
- formProps[form].values[parentField].toString()
- ) > -1;
+ var showIfHasParentShowValue = isVisibleByInclusion({
+ parentField,
+ form,
+ formProps,
+ parentFieldShowOnValue
+ });
(showIfHasParentShowValue == parentField) != false &&
showIfHasParentShowValue;
@@ -999,9 +1023,8 @@ export function DecimalField(props) {
return undefined;
};
- const requiredMessage = t("newappeal:is-required-label");
- const emojiNotAllowedMessage = t("newappeal:emojis-not-allowed-label");
- const invalidPostcodeMessage = t("newappeal:invalid-postcode-label");
+ const { requiredMessage, emojiNotAllowedMessage, invalidPostcodeMessage } =
+ getValidationMessages(t);
return (
<>
{parentField != false ? (
diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md
index b3d55e9f..d0f4f422 100644
--- a/memory-bank/change-log.md
+++ b/memory-bank/change-log.md
@@ -3555,3 +3555,33 @@ Validation:
Follow-ups:
- Continue Phase 2 with bounded renderer/module extractions from `components/elements/index.js` (one cohesive bundle per commit).
+
+---
+
+### CL-100: 22500 `components/elements/index.js` helper normalization (validation messages + visibility checks)
+
+date: 2026-04-07
+author: Cline
+scope: `components/elements/index.js`
+type: change
+rationale: Apply the requested next bounded refactor slice by consolidating repeated validation message setup and parent-field visibility logic into shared local helpers.
+impact: No intended behavior change; EN/CY and accessibility behavior preserved while reducing repeated logic and future drift risk.
+status: completed
+
+Summary:
+
+- Added `getValidationMessages(t)` helper for repeated `required/emoji/postcode` message retrieval.
+- Added visibility helpers:
+ - `hasParentFieldValue(...)`
+ - `isVisibleByEquality(...)`
+ - `isVisibleByInclusion(...)`
+- Replaced repeated inline visibility and validation-message setup across field wrappers with helper usage (Textfield, MultiLinefield, RichMultiLinefield, DateFieldPicker, YesNofield, Radiofield, NumericField, DecimalField).
+- Kept existing field render paths, conditions, and validation calls intact.
+
+Validation:
+
+- `npx eslint components/elements/index.js` -> pass
+
+Follow-ups:
+
+- Continue bounded no-behavior-change slices by removing dead locals/comments and extracting one additional low-risk field wrapper at a time.