Replacement field components¶
This page lists the modules re-exported from replacement_components/field_components/index.js. Each is a local copy or fork of a field component that exists in invenio_rdm_records (or closely related packages), maintained so the modular deposit form can use shared widgets (TextField, RemoteSelectField, …) and consistent error visibility (notably “touched” rules) without patching node_modules.
For how these plug into layout/registry, see Built-in field widget components.
Form feedback: the modular deposit FormFeedback UI is not listed here; it lives in replacement_components/alternate_components/FormFeedback.jsx with replacement_components/alternate_components/form_feedback_components/FormFeedbackSummary.jsx (paths relative to invenio_modular_deposit_form/assets/semantic-ui/js/invenio_modular_deposit_form/). See Form feedback (errors and action state).
Warning
Draft sections may evolve as upstream InvenioRDM changes. When in doubt, read the file header in each source module.
Top-level widget shims¶
In addition to field_components/*, the top-level replacement_components/index.js exports shim/adapter widgets for stock custom-field ui_widget names:
Input-> stock-like adapter over localTextField
These are used by the custom-field widget loader fallback (@js/invenio_modular_deposit_form) so stock widget names can resolve to touched-aware local replacements without changing backend ui_widget names.
The same top-level barrel also exports core replacement widgets directly:
SelectFieldDropdown(stock-like adapter over localSelectField)AutocompleteDropdown(stock-like adapter over localRemoteSelectField)TextFieldTextAreaRemoteSelectFieldMultiInput
For Input, Dropdown, and AutocompleteDropdown, the local replacements delegate to TextField, SelectField, and RemoteSelectField. They pass description and helpText as separate props: optional copy above the control (description) and below (helpText), matching replacement TextField, TextArea, MultiInput, and SelectField. This differs from stock react-invenio-forms, which typically uses helpText ?? description as a single string below the field. PID fields and ResourceTypeSelectorField are exceptions with their own helptext behavior.
SelectField and Formik touched¶
Stock react-invenio-forms SelectField wires onBlur={handleBlur} on Form.Dropdown. Formik’s handleBlur decides which field to mark touched from event.target.name or event.target.id. For a search dropdown, the element that blurs is often not labeled with the Formik path, so touch can fail for the real fieldPath. The local replacement keeps stock-style handleBlur(e) and also calls form.setFieldTouched(fieldPath, true, false) on blur so touched-aware error gating (see file header) works. It also gates visible messages from form.errors on form.touched while leaving the stock error prop and initial-value / initialErrors branch unchanged.
Chained onBlur (departure from stock spread order): if the field receives an onBlur prop (e.g. from RemoteSelectField), the local implementation does not rely on spreading that prop onto Form.Dropdown last (which would replace the default blur handler and drop setFieldTouched). Instead it destructures onBlur from incoming props and invokes onBlurFromProps(e, { formikProps }) only after handleBlur and setFieldTouched. Callers therefore extend blur behavior without re-implementing touch parity.
RemoteSelectField (departures from stock)¶
Upstream: react-invenio-forms RemoteSelectField (often consumed via invenio_rdm_records deposit). Local module: replacement_components/RemoteSelectField.js.
Topic |
Stock (typical) |
Local replacement |
|---|---|---|
|
Package |
Local |
|
Not written |
On add/change, maps selected options to |
Search text vs debounce |
Debounced search only |
|
Unmount |
Request cancel only |
Also |
|
N/A |
Opt-in (default |
|
N/A |
Opt-in (default |
|
N/A |
Opt-in |
RemoteSelectField passes searchInput={{ id: fieldPath, … }}, which can still help handleBlur’s id fallback on the inner search input.
Creators flat UI: alternate_components/creatibutor_components/CreatibutorsFormBody.js enables hideAdditionMenuItem, commitSearchOnBlur, and focusFieldPathAfterSelect on the person family name names API field when the given-name column is shown (!namesSearchOnly || personDetailsExpanded, matching the sibling TextField). Other uses (e.g. AutocompleteDropdown) keep defaults unless they opt in.
Internal design notes (not built by Sphinx as a manual page): docs/internal/creatibutors-field-flat-person-names.md for the flat creatibutor name UX.
FieldComponentWrapper and labelIcon¶
Built-in section components wrap their default field widget in FieldComponentWrapper, which merges layout config (label_modifications, icon_modifications in deposit config, etc.) and passes props to the inner widget via React.cloneElement. The wrapper passes the field label icon as labelIcon (aligned with invenio_rdm_records field components such as AccessRightField). Legacy props icon on the wrapper or on merged custom-field props are still folded into the computed labelIcon so existing custom_fields.ui / YAML using icon continues to work. Replacement TextField, TextArea, and MultiInput take labelIcon only for that label icon (adapters such as Input / Dropdown may still accept stock icon and map it to FieldLabel).
Enumeration (matches field_components/index.js)¶
The barrel file path is:
invenio_modular_deposit_form/assets/semantic-ui/js/invenio_modular_deposit_form/replacement_components/field_components/index.js
Export |
Typical upstream analogue |
Role of this replacement |
|---|---|---|
|
|
Stock copy; swaps in local replacement widgets ( |
|
|
Same pattern: additional titles UI with local widgets. |
|
|
Stock copy; uses |
|
|
Fork: uses a local |
|
|
Fork: same layout and |
|
|
Stock copy; uses local rich/text widgets where configured. |
|
|
Fork: row wrapper is bare |
|
|
Stock copy; uses local |
|
|
Larger fork (see below): touched-aware errors via |
|
|
Stock copy; local replacement widgets. |
|
|
Fork: same layout as stock; local |
|
|
Stock copy; may use local vocabulary/select widgets. |
|
|
Stock copy; |
|
|
Stock copy; local widgets. |
PIDField (detailed)¶
Upstream layout (for comparison):
invenio_rdm_records/.../src/deposit/fields/Identifiers/PIDField/
Local layout:
index.js,PIDFieldCmp.js,RequiredPIDField.js,OptionalPIDField.jsat the folder root (same roles as upstream).pid_components/— not a full mirror of upstreamcomponents/*. It holds:fieldErrorsForDisplay.js— addsgetFieldErrorsForDisplay(aligned withreplacement_components/TextField.js); stock only hasgetFieldErrors.pickDisplayableErrormerges nested Yup messages (e.g.errors.pids.doi.identifier) so the FastField bound topids.doistill receives a single string for SUIerror=.UnmanagedIdentifierCmp.js,ManagedIdentifierCmp.js— identifier UIs with the new error helper and, for managed,@jsimports for deposit API/state/buttons.
The deposit form imports PIDField from this tree (e.g. DoiComponent in field_components.jsx) so DOI/PID errors do not appear before touch in a way that disagrees with other fields.
Formik touched and this fork¶
getFieldErrorsForDisplay shows validation errors when form.touched[fieldPath] is truthy (among other branches). Stock PID inputs are not plain Formik <Field> scalars, so nothing was marking the PID path touched. This fork wires touched explicitly (we use form.setFieldTouched(fieldPath) on unmanaged blur, not raw field.onBlur(e): Formik’s blur handler uses e.target.name, which Semantic UI Form.Input often omits, so touch would incorrectly apply to undefined).
Interaction |
Where |
|---|---|
Mount, empty identifier, per |
|
User blurs the unmanaged identifier text input |
|
User changes the managed / unmanaged radios ( |
|
User changes optional DOI radios ( |
|
Blur/input paths set touched so getFieldErrorsForDisplay can show errors after the user interacts. Radio toggles set touched to false and shouldValidate to false on that setFieldTouched call, so switching branches does not immediately validate or show errors for an empty PID.
Initial pids.<scheme> shape vs default_selected¶
Deposit config exposes per-scheme UI default as default_selected ("yes" / "no" / "not_needed" for optional DOI), passed to PIDField as doiDefaultSelection.
Stock RequiredPIDField (upstream invenio_rdm_records)¶
No
componentDidMount— Formik is not normalized fromdefault_selectedon mount.Constructor: if
record.is_draft === trueand the field has a non-empty identifier and a non-externalprovider, local stateisManagedSelectedis initialized totrue(managed). Otherwise it isundefined.Render: if state is
undefined, managed vs unmanaged is inferred as
hasManagedIdentifier || (empty identifier && doiDefaultSelection === "no").
So{ provider: "external", identifier: "" }withdefault_selected === "no"still infers managed after remount (local state is lost), which disagrees with Formik.Errors:
getFieldErrors(no touched gating like this package).Radio:
onManagedUnmanagedChangedoes not callsetFieldTouchedon the PID path.
This package’s RequiredPIDField (additional / changed behavior)¶
componentDidMount: if the identifier is empty,doiDefaultSelection === "yes"sets{ provider: "external", identifier: "" }whenprovideris missing or not"external";doiDefaultSelection === "no"sets{}only when the value still has keys andprovideris not"external"(avoids clearing explicit unmanaged shape from the API or after user choice).getFieldErrorsForDisplayon the label row and identifier components;setFieldTouched(fieldPath, false, false)when managed/unmanaged radios change (see table above).Managed / unmanaged branch:
values.ui.<fieldPath>holdsmanaged_selection(managed/unmanaged) anddraft_unmanaged_pid_backup/draft_managed_pid_backup(for DOI,fieldPathispids.doi, so keys mirrorOptionalPIDFieldundervalues.ui.pids.doi.*).renderreadsmanaged_selectiononly (no upstream-styleuseState/isManagedSelected).restoreFromBackupruns on radio change; unmanaged typing updates the unmanaged backup (debounced); while the managed branch is selected,componentDidUpdaterefreshesdraft_managed_pid_backupwhenpids.<scheme>’s Formik value reference changes (reserve/discard).doiDefaultSelectionPropTypes usestring(stock incorrectly types it asobject).ManagedUnmanagedSwitchdisabled: same split as stock —hasDoifromrecord.pids?.doi?.identifier(“backend already has this PID”),isDoiCreatedfrom the draftfield.value.identifier(user-visible / Formik value).
This package’s OptionalPIDField (additional / changed behavior)¶
OptionalPIDField does not mount-seed. values.ui.pids.doi.managed_selection holds the optional-DOI radio choice (managed / unmanaged / not_needed). When set, computeManagedUnmanaged uses it instead of inferring only from PID shape and doiDefaultSelection—so clearing pids or remounting the field does not incorrectly revert the UI branch (e.g. empty pids plus default_selected "no" inferring managed while the user had chosen unmanaged). When managed_selection is unset, behaviour matches the prior heuristic derivation (same inputs as stock-style logic: identifiers, provider, draft, parent/record DOI, not_needed default). Seeding external on mount would make Yup treat the field as an external DOI branch and can flag an empty identifier when optional DOI should not require one. The unmanaged radio avoids provider: "external" until input: it clears pids (same as managed / not-needed), and provider: "external" is written only when the user types in the unmanaged identifier input (onExternalIdentifierChanged). Under normal PIDField wiring, OptionalPIDField mounts only with required === false; the !required guard on the managed branch’s Redux / managed_selection updates is for hypothetical direct reuse and does not change that path today.
Upstream changes that could remove these replacements¶
This section describes what would need to exist in stock invenio_rdm_records (and sometimes react-invenio-forms) so this package could import field components only from @js/invenio_rdm_records and delete the corresponding files under replacement_components/field_components/. It is not a commitment to upstream work; it is a design checklist.
PIDField specifically¶
getFieldErrorsForDisplay(or equivalent) in upstream
ExtendPIDField/components/helpers.js(or export a sibling) with a function that gates visible errors the same way as other fields, or add ashowErrorWhenprop onPIDField/ identifier components.Consistent
touchedfor non-Fieldcontrols
If upstream ensuredtouchedforpids.<scheme>when users blur the unmanaged input or change managed/unmanaged (and optional-DOI) radios—e.g. by using FormikField/useFieldfor those controls or by documentingsetFieldTouchedin the stock handlers—apps would not need a local fork only for touch parity.Resolvable imports from consuming apps
Export PID subcomponents (e.g.UnmanagedIdentifierCmp) from the@js/invenio_rdm_recordspublic API so apps do not need relative paths; and/or export deposit context and action types from stable entry points (already partly true via deep paths).Optional composition
Allow passing customManagedIdentifierCmp/UnmanagedIdentifierCmpas props so a host app can inject behavior without replacing the whole tree.
CreatibutorsField / modal¶
Lifecycle hooks on stock
CreatibutorsModal
An optionalonModalClose(oronDismiss) callback invoked for every close path would let the parent callsetFieldTouchedwithout forking the modal.
CreatibutorsField-level error display¶
If upstream CreatibutorsField (or FeedbackLabel usage) respected the same touched rules as TextField, the local field-level wrapper for “general creatibutors error” might be unnecessary.
Operational note¶
Even if upstream implements the above, version alignment matters: this package pins a specific invenio-rdm-records release. Removals here should be one field at a time behind a compatibility check.