
Background
mirador-annotations is a plugin that adds annotation functionality to the IIIF viewer Mirador.
The previous project configuration was as follows:
- Build tool: nwb (Create React App based)
- UI library: Material-UI v4
- Mirador: 3.x
- React: 17.x
However, the following issues had arisen:
- nwb maintenance discontinued - nwb had not been updated for a long time, and dependency conflicts occurred frequently
- npm install failures - Old dependencies made setup in new environments difficult
- Security vulnerabilities - Numerous vulnerability warnings from outdated packages
To resolve these issues, we decided to migrate to:
- Build tool: Vite
- UI library: MUI v7
- Mirador: 4.x
- React: 18.x
Migration Overview
1. Build Tool Migration (nwb to Vite)
We deleted the nwb configuration files and created a new vite.config.js.
Key points:
// vite.config.js
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
// Because draft-js references global
define: {
global: 'globalThis',
},
// Resolve duplicate packages
resolve: {
dedupe: [
'@emotion/react',
'@emotion/styled',
'react',
'react-dom',
],
},
};
});
2. Material-UI Migration (v4 to v7)
- Changed
@material-ui/*to@mui/* - Replaced
makeStyleswithsxprop - Adapted to Grid component API changes (
itemandxsprops consolidated intosize)
// Before (MUI v4)
Grid item xs={12}>
// After (MUI v7)
Grid size={12}>
3. Adapting to Mirador 4.x
In Mirador 4.x, the import methods for actions and selectors changed:
// Before (Mirador 3.x)
import { actions, selectors } from 'mirador';
actions.receiveAnnotation(...)
selectors.getVisibleCanvases(...)
// After (Mirador 4.x)
import { receiveAnnotation, getVisibleCanvases } from 'mirador';
receiveAnnotation(...)
getVisibleCanvases(...)
Pitfalls Encountered
1. menuItemRef.current.focus is not a function Error
Symptom
When navigating to a canvas with annotations, the following error occurred:
TypeError: menuItemRef.current.focus is not a function
Cause
Mirador 4.x’s CanvasAnnotations component uses <MenuList autoFocusItem>. This autoFocusItem feature automatically focuses on the first item in the list, but requires the focused element to have a focus() method.
The problem was in the CanvasListItem component:
// Problematic code
class CanvasListItem extends Component {
render() {
return (
div> {/* div receives ref, but has no focus() method */}
{this.props.children}
div>
);
}
}
MUI’s MenuItem uses refs internally for focus control, but since CanvasListItem was a class component wrapping a <div>, refs were not forwarded correctly, resulting in the focus() method not being found.
Solution
We rewrote CanvasListItem as a function component, using forwardRef to forward the ref directly to the <li> element:
import React, { useState, useContext, forwardRef } from 'react';
const CanvasListItem = forwardRef(({
annotationid, children, ...otherProps
}, ref) => {
const [isHovering, setIsHovering] = useState(false);
// ...
return (
li
ref={ref} // Forward ref directly to li element
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
{...otherProps}
>
{/* ... */}
{children}
li>
);
});
Lessons Learned
- MUI v5+’s
MenuList’sautoFocusItemexpects child components to properly forward refs - When creating custom list item components,
forwardRefmust be used to pass refs to DOM elements - When migrating from class components to function components, careful attention to ref handling is required
2. CompanionWindow Not Displaying
Symptom
Clicking the “Create new annotation” button displayed nothing. Checking the console revealed:
- The
addCompanionWindowaction was dispatched normally - The
AnnotationCreationcomponent’srender()was being called - However, nothing appeared on screen
Additionally, the following warning was being issued:
Warning: Failed prop type: The prop `direction` is marked as required
in `CompanionWindow`, but its value is `undefined`.
Cause
Mirador exports two versions of the CompanionWindow component:
CompanionWindow- Base component (props must be passed manually)ConnectedCompanionWindow- Redux-connected version (props are automatically retrieved from the store)
The pre-migration code was using CompanionWindow directly, but in Mirador 4.x, ConnectedCompanionWindow needed to be used instead.
// Problematic code
import { CompanionWindow } from 'mirador';
// CompanionWindow requires direction, position, etc. to be
// passed manually, but these are in the Redux store
Solution
Changed to import ConnectedCompanionWindow:
// Fixed code
import { ConnectedCompanionWindow as CompanionWindow } from 'mirador';
This allowed the required props such as direction and position to be automatically retrieved from the Redux store.
Lessons Learned
- Mirador components may have both a base version and a Redux-connected version
- When developing plugins, it is necessary to verify which version should be used
- When “a component is being rendered but nothing is displayed,” suspect that required props may be missing
Other Issues Addressed
draft-js global Reference Error
draft-js references the global object, which does not exist in browser environments. Resolved through Vite configuration:
define: {
global: 'globalThis',
}
@emotion/react Duplication Warning
Occurred because both Mirador and this plugin bundled @emotion/react. Resolved with resolve.dedupe:
resolve: {
dedupe: ['@emotion/react', '@emotion/styled', 'react', 'react-dom'],
}
Continued Need for legacy-peer-deps
Because @psychobolt/react-paperjs requires React 17 as a peer dependency, legacy-peer-deps=true in .npmrc is still necessary.
Summary
The two issues that took the most time during this migration were:
- ref forwarding issue - Required understanding MUI’s internal behavior and handling refs correctly
- Using Connected components - Required understanding Mirador’s export structure
Both were resolved by understanding the internal implementations of the libraries. During migration work, it is important to examine not only error messages but also the library source code.
Demo
The post-migration behavior can be tested at the following demo site:
https://nakamura196.github.io/mirador-annotations/