React Aptor
React Aptor is a minimal API connector for React applications, currently at version 2.0.0. It provides a structured and opinionated way to integrate vanilla JavaScript third-party libraries, especially those that interact directly with the DOM, into React components. The library addresses common integration challenges such as finding DOM nodes, preventing excessive re-renders, and managing API lifecycles without relying on global scope or introducing unnecessary abstraction layers. Emphasizing a "zero-dependency," "tree-shakeable," and "side-effect free" design, React Aptor boasts a minimal bundle size (less than 1 kilobyte). While a strict release cadence is not specified, recent updates, including the significant v2.0.0 release, indicate active development focused on improving the build process, testing infrastructure, and project consistency. Its core differentiator is empowering developers with full control over their API definitions and their connection to React, aiming to be anti-pattern-free within the React ecosystem.
Common errors
-
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
cause The `useAptor` hook is being called outside of a React function component or a custom React hook.fixEnsure `useAptor` is only called at the top level of a React function component or another custom React hook (e.g., not inside loops, conditions, or nested functions). -
TypeError: aptorAPI.someMethod is not a function
cause The `getAPI` function provided to `useAptor` is not correctly returning an object that includes `someMethod`, or `someMethod` does not exist on the underlying third-party instance.fixVerify that your `getAPI` function correctly accesses and exposes all desired methods from the `instance` argument. Debug the `instance` object within `getAPI` to confirm the availability of methods. -
TypeError: Cannot read properties of null (reading 'current') when trying to pass ref to instantiate function
cause The `useRef` hook used for the DOM element has not yet been attached to an actual DOM element when `useAptor` attempts to access its `.current` property, or the component has unmounted.fixEnsure the `useRef` is properly initialized with `null` and then attached to a real DOM element (e.g., `<div ref={containerRef}></div>`). The `useAptor` hook internally handles the lifecycle, so just ensure the ref is placed correctly.
Warnings
- breaking The `destroy` function, if provided in your `instantiate` function's return, became optional in `v1.2.1`. While not strictly a breaking change for existing valid implementations, types might have changed, and relying on its mandatory presence might require adjustment.
- breaking React `Ref` types were updated to align with `forwardedRef` in `v1.2.0`. This internal change could subtly affect how refs are processed or typed if you are passing complex ref objects to `useAptor` or relying on specific ref behaviors.
- gotcha Directly using `ReactDOM.findDOMNode` within your React components is an anti-pattern that `react-aptor` aims to circumvent. While `react-aptor` helps integrate third-party libraries that *do* manipulate the DOM, you should avoid `findDOMNode` in your own React code.
- gotcha The `v2.0.0` release is described as the 'most significant' to date, primarily focusing on build process improvements, testing, and tooling (e.g., SWC, esbuild, Rollup configurations). While API breaking changes are not explicitly detailed in the provided release notes, it's advisable to thoroughly review the full changelog on GitHub if migrating from `v1.x` to check for any subtle behavioral changes or undocumented API adjustments.
Install
-
npm install react-aptor -
yarn add react-aptor -
pnpm add react-aptor
Imports
- useAptor
const { useAptor } = require('react-aptor')import { useAptor } from 'react-aptor' - AptorInstance
import type { AptorInstance } from 'react-aptor' - AptorAPI
import type { AptorAPI } from 'react-aptor'
Quickstart
import React, { useRef, useEffect, useState } from 'react';
import { useAptor } from 'react-aptor';
// 1. Define the instantiate function for a dummy third-party library
// Let's imagine a simple counter library that updates a DOM element.
class CounterLib {
private count: number = 0;
private displayElement: HTMLElement;
constructor(node: HTMLElement, initialCount: number = 0) {
this.displayElement = node;
this.count = initialCount;
this.render();
}
increment = () => {
this.count++;
this.render();
};
decrement = () => {
this.count--;
this.render();
};
getCount = () => this.count;
destroy = () => {
this.displayElement.innerHTML = ''; // Clean up DOM
console.log('CounterLib destroyed');
};
private render = () => {
this.displayElement.innerHTML = `Current count: ${this.count}`;
};
}
const instantiateCounter = (node: HTMLElement, initialCount: number) => {
return new CounterLib(node, initialCount);
};
// 2. Define the get API function
const getCounterAPI = (instance: CounterLib) => ({
increment: instance.increment,
decrement: instance.decrement,
getCount: instance.getCount,
});
// 3. Connect API to react by useAptor
function CounterComponent() {
const containerRef = useRef<HTMLDivElement>(null);
const { api: counterApi, instance: counterInstance, isReady } = useAptor(
instantiateCounter,
getCounterAPI,
containerRef, // The node for the third-party lib
[0] // params for instantiateCounter: initialCount
);
useEffect(() => {
if (isReady && counterApi) {
console.log('Counter API is ready, current count:', counterApi.getCount());
}
}, [isReady, counterApi]);
return (
<div style={{ padding: '20px', border: '1px solid #ccc' }}>
<h2>React Aptor Counter Example</h2>
<div ref={containerRef} style={{ marginBottom: '10px' }}>
{/* CounterLib will render here */}
</div>
{isReady && counterApi ? (
<div>
<button onClick={counterApi.increment} style={{ marginRight: '10px' }}>Increment</button>
<button onClick={counterApi.decrement}>Decrement</button>
<p>Count from React state: {counterApi.getCount()}</p>
</div>
) : (
<p>Loading Counter...</p>
)}
</div>
);
}
// Example usage in an App component
const App = () => <CounterComponent />;
export default App;