Input Control Development Guide - XMPieLab/uStore-NG GitHub Wiki
Prerequisites
- uStore 26.3 and above: Node.js 24 (22.12 is the minimum the build tooling accepts).
- uStore 26.2 and below: Node.js 16 and above.
React version: a DUC never bundles its own React. Its build externalizes react, react-dom and prop-types to the globals the storefront publishes, so a DUC always runs against the host storefront's React — 19.2.8 on uStore 26.3, 18.2.0 on 25.2–26.2. Do not add React to your DUC's bundle.
Downloads
| uStore version | Project sample | Fully compiled sample |
|---|---|---|
| 26.3 and above | ng_duc_project_sample_26.3.zip | ng_duc_project_sample_compiled_26.3.zip |
| 26.2 and below | ng_duc_project_sample.zip | ng_duc_project_sample_compiled.zip |
The two versions need separate downloads because both the Node floor and the React version changed; one package cannot serve both.
You can find some examples of user-made custom input controls in the uStore Input Controls page in the uStore JS Wiki.
To develop a custom input control (also known as DUC) follow these steps:
-
Download the project sample for your uStore version from the table above.
-
Extract the downloaded ZIP file into a new directory.
-
In the new directory, run
npm install -
In the new directory, rename Sample.duc.js (the name should start with a capital letter and end with the suffix .duc.js)
-
Inside the file, do the following:
- Change the class name (for example NewNameOfDuc)
- Locate these lines
if (typeof window !== 'undefined') { window.uStoreDucs = window.uStoreDucs || [] window.uStoreDucs.push({ name: 'Sample', component: Sample, displayName: 'Sample duc name' , configSample: {}}) } export default Sample- Change
name: 'Sample'toname: 'NewNameOfDuc'(the name should have no spaces) - Change
component: Sampletocomponent: NewNameOfDuc - Change
displayName: 'Sample duc name'todisplayName: 'New DUC display name'. The display name will be displayed in property edit beside the default DUCs. - Change
export default Sampletoexport default NewNameOfDuc - Change
configSample: {}to the JSON configuration you need for your custom DUC. - Change the
thumbnail.pngfile to a new image with the same name that will be used as a thumbnail for the DUC in the uStore admin.
-
Point the sample test at the renamed DUC. Until you do,
npm testfails to even collect it —Failed to resolve import "../Sample.duc".- Change
import Sample from '../Sample.duc'toimport NewNameOfDuc from '../NewNameOfDuc.duc' - Update its two other uses:
component: Samplein the registration test, andrender(<Sample {...props} />)inside therenderDuchelper - In the registration test, change the expected
nameanddisplayNameto your values - Optional: rename
tests/Sample.duc.test.jsand itsdescribetitle. Vitest collects anytests/**/*.test.js, so the file name is yours to choose - Run
npm test— the sample tests should pass against your renamed DUC
- Change
-
In the terminal, run
npm run build. When the script is done, you will find a ZIP file in thedistdirectory. The file should be named NewNameOfDuc.zip. -
Upload the new DUC in uStore admin (Global Settings > Input Control Management).
-
Create a new property in your product and select the new custom DUC.
-
In the theme directory, start development by running
npm start -- ducbuild=/path/to/duc/directory -
You can now modify the files inside the DUC's directory. Note that when you update, the DUC's files will be compiled but the application will not be reloaded in the browser; you will need to do it manually.
Unit tests
The project ships Vitest and one worked example. Run npm test, npm run test:watch or npm run test:coverage.
tests/Sample.duc.test.js— renders the DUC the way the dynamic form does and checks that it registers itself, shows the incoming value, reports edits throughonChangeandonBlur, and honoursreadonly,disabledandrequired. Start your own cases from it.tests/helpers/ducProps.js— the prop contract the dynamic form hands every DUC. CallducProps({ value: 'x' })and override only what a case is about.vitest.config.mjs,tests/setup.js,tests/svgStub.jsx— the environment: jsdom,@testing-library/jest-dommatchers, JSX inside.jsfiles, SCSS imports ignored, SVG imports stubbed. jsdom implements no layout, so stubResizeObserver,IntersectionObserveror element scrolling intests/setup.jsif a library you add needs them.
Enzyme is no longer supported and is not part of the project. If you are upgrading a DUC written before 26.3, its enzyme tests will not run — port them to @testing-library/react.
Third-party packages that cap at React 18
Some React libraries still declare a peer range ending at React 18 and will refuse to install next to React 19. Do not use --legacy-peer-deps: it permits a nested React install, which means two React runtimes and every hook inside your DUC throwing. Add an overrides entry instead, so the package resolves to the project's pinned React:
"overrides": {
"react-mobile-cropper": { "react": "$react" },
"react-advanced-cropper": { "react": "$react" },
"react-popper": { "react": "$react", "react-dom": "$react-dom" }
}
$react refers to the root react dependency, so the override never drifts from the pin. Those three entries ship in the sample project because the product's own DUCs need them; add yours the same way.
If a package genuinely breaks on React 19 rather than merely declaring an old peer range, it has to be replaced, not overridden.
DUC development guidelines
- A DUC should be treated as an independent component and should not expect libraries to be present. Install the packages you need to use.
- DUC parameters provide only the data that is related to the DUC and not a global model from uStore.
- For changes to take effect, you need to call
this.props.onChange(newValue)orthis.props.onBlur(this.props.id, newValue)in case of focus lost. - The DUC should take into account the
readonly,required,disabledproperties to behave correctly and be influenced by the form. - The DUC accepts the
uiSchemaandjsonschemaparameters, that describe the configuration and validation of the DUC by the form. See json-schema.org to understand the format.