Step 2 - Using Hooks
Now let's take our first steps into the RemNote API by modifying our SelectedTextDictionary
component to:
- Display the user's currently selected text rather than "Hello World!".
- Keep the widget up to date by rerendering the widget every time the user's selected text changes.
Open the selected_text_dictionary.tsx
file and make the following changes to the body of the SelectedTextDictionary
component:
import {
usePlugin,
renderWidget,
useTracker,
SelectionType,
} from '@remnote/plugin-sdk';
function SelectedTextDictionary() {
const plugin = usePlugin();
const selText = useTracker(async (reactivePlugin) => {
const sel = await reactivePlugin.editor.getSelection();
if (sel?.type == SelectionType.Text) {
return await plugin.richText.toString(sel.richText);
} else {
return '';
}
});
return <div>{selText}</div>;
}
renderWidget(SelectedTextDictionary);
Don't worry if this code looks intimidating, we are going to break it down line by line.
Since we want to access the value of the user's selected text, our first instinct might be to call the plugin.editor.getSelection(...)
function. This seems like a reasonable approach, but it doesn't allow us to update our component in response to changes in the selected text.
What we need is a way to access the latest selected text value in our component every time it changes. While it would be possible to do this with useEffect
and subscribing to selected text change events using plugin.event.addListener
, this would make our code a bit messy and more difficult to understand. Thankfully there is a much better option available: Reactive Hooks!
The Tracker System (Reactive Hooks)
const selText = useTracker(async (reactivePlugin) => {
const sel = await reactivePlugin.editor.getSelection();
if (sel?.type == SelectionType.Text) {
return await plugin.richText.toString(sel.richText);
} else {
return '';
}
});
Reactive functions allow you to tell the plugin system that your widget component wants to be notified every time the value of some variable changes, so it can rerender in response to the change. So in our example, the useTracker
hook wraps the normal plugin API methods and automatically subscribes to relevant events which tells the plugin system that our component doesn't just want to get the current value of the selected text, but it wants to rerender every time the selected text changes.
Reactive hooks are explained in depth in our reactive method hooks documentation.
Rich Text
If you hover over the sel.richText
variable, your IDE should tell you that the type of this variable is RichTextInterface
. RichTextInterface
represents the underlying rich text storage format in RemNote. Rather than storing text as a string using some kind of markup language, text is stored internally as an array of rich text elements where each element could represent plain text, bold text, text with color, embedded media like images and audio etc. We won't delve into the intricacies of this data type here.
We simply use the plugin.richText.toString()
method to convert from the RichTextInterface
representation to a human-readable string so we can display it to the user.
Let's test our new component!
If you still have the plugin running from the test we did earlier, the plugin should have automatically refreshed itself in response to the changes you made. Otherwise you will need to open a new terminal inside the plugin folder, and run:
npm run dev
Open a Rem, highlight some text and click on the dictionary tab icon. While the selected text menu is open, try changing the highlighted text and make sure that the displayed value of the selected text in the component updates correctly.
Excellent. Now let's start working on the dictionary integration.