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:

  1. Download the project sample for your uStore version from the table above.

  2. Extract the downloaded ZIP file into a new directory.

  3. In the new directory, run npm install

  4. In the new directory, rename Sample.duc.js (the name should start with a capital letter and end with the suffix .duc.js)

  5. 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' to name: 'NewNameOfDuc' (the name should have no spaces)
    • Change component: Sample to component: NewNameOfDuc
    • Change displayName: 'Sample duc name' to displayName: 'New DUC display name'. The display name will be displayed in property edit beside the default DUCs.
    • Change export default Sample to export default NewNameOfDuc
    • Change configSample: {} to the JSON configuration you need for your custom DUC.
    • Change the thumbnail.png file to a new image with the same name that will be used as a thumbnail for the DUC in the uStore admin.
  6. Point the sample test at the renamed DUC. Until you do, npm test fails to even collect it — Failed to resolve import "../Sample.duc".

    • Change import Sample from '../Sample.duc' to import NewNameOfDuc from '../NewNameOfDuc.duc'
    • Update its two other uses: component: Sample in the registration test, and render(<Sample {...props} />) inside the renderDuc helper
    • In the registration test, change the expected name and displayName to your values
    • Optional: rename tests/Sample.duc.test.js and its describe title. Vitest collects any tests/**/*.test.js, so the file name is yours to choose
    • Run npm test — the sample tests should pass against your renamed DUC
  7. In the terminal, run npm run build. When the script is done, you will find a ZIP file in the dist directory. The file should be named NewNameOfDuc.zip.

  8. Upload the new DUC in uStore admin (Global Settings > Input Control Management).

  9. Create a new property in your product and select the new custom DUC.

  10. In the theme directory, start development by running npm start -- ducbuild=/path/to/duc/directory

  11. 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 through onChange and onBlur, and honours readonly, disabled and required. Start your own cases from it.
  • tests/helpers/ducProps.js — the prop contract the dynamic form hands every DUC. Call ducProps({ 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-dom matchers, JSX inside .js files, SCSS imports ignored, SVG imports stubbed. jsdom implements no layout, so stub ResizeObserver, IntersectionObserver or element scrolling in tests/setup.js if 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) or this.props.onBlur(this.props.id, newValue) in case of focus lost.
  • The DUC should take into account the readonly, required, disabled properties to behave correctly and be influenced by the form.
  • The DUC accepts the uiSchema and jsonschema parameters, that describe the configuration and validation of the DUC by the form. See json-schema.org to understand the format.