Common errors
As Modes UI is garnering a larger user base, some users have reported and asked about some issues with integrating Modes UI in their project. As projects have vastly varying dependencies and configurations, there are many unforeseen issues that may arise when adding Modes UI as a dependency. This documentation page covers some of the frequest issues and errors that have come up, and potential solutions.
Component importing
Components can either be imported from the index
import { MOButton } from '@metsooutotec/modes-web-components/dist/react';
or as individiual component modules from
import MOButton from '@metsooutotec/modes-web-components/dist/react/button';
Some configurations of create-react-app and webpack give a warning message if components are lazy-loaded using the first method:
Critical dependency: the request of a dependency is an expression
This may be fixed by importing the components individually from their respective modules. Individual imports will also ensure that tree-shaking works as expected, potentially resulting in lower production bundle size.
Testing with React + Jest
As mentioned in the React integration section, Web Components can have some issues when running in a Node environment (not an actual browser). Web Component rendering and importing has some differences to standard React components, and Jest may give you these error messages:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Error: Unable to import outside of a module
The solution to this issue can be found in
the React integration - Transform ES Modules
section. In short, Modes UI components are ES Modules, Jest expects CommonJS modules. Adding an exception to
Jest’s transformIgnorePatterns
option should solve this issue. Follow the steps in the page
mentioned above to achieve this.
Icons not loading
When importing the Modes UI components and using them directly in HTML, the library will automatically
import and find the icons to use. However, when using a bundled version, particularly in React, the library
requires some additional configuration to be able to access the assets.
The React integration briefly mentions this, but it is easy
to miss. Essentially, Modes UI comes with a setBasePath
function that needs to be executed to
show where the assets are located. Another caveat is that setting the base path as follows:
import { setBasePath } from '@metsooutotec/modes-web-components'; setBasePath('@metsooutotec/modes-web-components/dist/assets');
will not work. The assets cannot be accessed from the node_modules folder, as they will not
be part of the production build. In order to ensure Modes UI assets are properly accessed and shown on
components such as MOSelect
, the build script of your project must first copy the assets to an
accessible folder. In React a good example of this is the public
folder, and its relative path
is also easy to access. A correct folder structure looks like this:
public │ └───assets │ │ │ └───icons │ │ │ 3D-rotate.svg │ │ │ alarm-fill.svg │ │ │ ... │ └──-fonts │ │ │ │───GT-Eesti-Pro-Text │ │ | GT-Eesti-Pro-Text-Light.otf │ │ | ...
In this case the public path should be set as in the React integration example:
// App.tsx import { setBasePath } from '@metsooutotec/modes-web-components/dist/utilities/base-path'; // Set base path to the public directory setBasePath(process.env.PUBLIC_URL);
Note that you do not need to use the public
directory. If your project is already using e.g.,
an assets
directory for your own assets, you can copy the fonts
and
icons
folders there, and then setBasePath
to the relative path of the
assets
folder.
Sidebar/dialog closing unexpectedly
As components such as mo-dialog
, mo-sidebar
and mo-dropdown
share the
same mo-after-hide
, mo-hide
, etc. custom events, these events can bubble up the
tree, causing unexpected effects when listening to them. For example including an
mo-dropdown
inside either an mo-dialog
or mo-sidebar
can cause the
dialog or sidebar to close when it closes. This is caused by event bubbling, which means
the events emitted by a component will bubble up to its parent elements.
In this example, closing the dropdown on the mo-select
will also close the dialog:
<mo-dialog label="Are you sure you want to confirm this?" class="dialog-incorrect"> <div> <mo-select hoist label="Choose option" required placeholder="Select one"> <mo-option value="option-1">Option 1</mo-option> <mo-option value="option-2">Option 2</mo-option> <mo-option value="option-3">Option 3</mo-option> </mo-select> </div> <mo-button slot="footer" variant="primary">Close</mo-button> </mo-dialog> <mo-button>Open Dialog</mo-button> <script> let isOpen = false; const dialog = document.querySelector('.dialog-incorrect'); dialog.open = isOpen; const openButton = dialog.nextElementSibling; const closeButton = dialog.querySelector('mo-button[slot="footer"]'); const openDialog = () => { dialog.setAttribute('open', ''); }; const closeDialog = () => { dialog.removeAttribute('open'); }; // incorrect, will close both dialog.addEventListener('mo-after-hide', () => { console.log('after hide'); closeDialog(); }); openButton.addEventListener('click', openDialog); closeButton.addEventListener('click', closeDialog); </script>
Adding an additional check to the mo-after-hide
event listener can fix the issue:
<script> // incorrect, will close both dialog.addEventListener('mo-after-hide', () => { console.log('after hide'); closeDialog(); }); // correct, will check if dialog is the source dialog.addEventListener('mo-after-hide', event => { if (event.target === dialog) { console.log('after hide'); closeDialog(); } }); // also correct, checks if event originated from the target dialog.addEventListener('mo-after-hide', event => { if (event.eventPhase === Event.AT_TARGET) { console.log('after hide'); closeDialog(); } }); </script>
The select in this example will no longer cause the dialog:
<mo-dialog label="Are you sure you want to confirm this?" class="dialog-correct"> <div> <mo-select hoist label="Choose option" required placeholder="Select one"> <mo-option value="option-1">Option 1</mo-option> <mo-option value="option-2">Option 2</mo-option> <mo-option value="option-3">Option 3</mo-option> </mo-select> </div> <mo-button slot="footer" variant="primary">Close</mo-button> </mo-dialog> <mo-button>Open Dialog</mo-button> <script> let isOpen = false; const dialog = document.querySelector('.dialog-correct'); dialog.open = isOpen; const openButton = dialog.nextElementSibling; const closeButton = dialog.querySelector('mo-button[slot="footer"]'); const openDialog = () => { dialog.setAttribute('open', ''); }; const closeDialog = () => { dialog.removeAttribute('open'); }; // correct, will check if dialog is the source dialog.addEventListener('mo-after-hide', event => { if (event.target === dialog) { console.log('after hide'); closeDialog(); } }); openButton.addEventListener('click', openDialog); closeButton.addEventListener('click', closeDialog); </script>
If you find and solve other issues with integration, please contact the design system team, or directly contribute to this page through a pull request so that your solution can reach others dealing with a similar issue.