The Utility Component for an Annotated Text
Tags: webdev, javascript, react, opensource
Published: 2022-11-18 11:00:31


![Example of the Configured Marked Input Component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zi36dr4ki6a5bb7ldnf.png)
This article is exploring the [rc-marked-input](https://github.com/Nowely/marked-input) react library that appears you to combine any components with editable text via annotations.

## What is the problem?

An annotation is extra information associated with a piece of text. It can be used to add information about the desired visual presentation, as markup languages like HTML do:


![HTML Markup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4sldg4ntarru3x89e8h0.png)

As an annotation, it is not always handy to use the HTML markup for display:


![Custom Markup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnykt8h8iyvjvw253k5i.png)

Then control over a display component allow to simplify markup and use exotic visual representation and inner logic:

![Custom Display](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hqv04dcxysz9rqcojo5j.png)

Moreover, it allows to use a markup to forward information for conditional display:


![Conditional Custom Display](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vtex5vtbbc6at183iug0.png)

In summary, would like to have the following:

- Editable text;
- Custom markup;
- Mark - a component for display and/or change annotations;
- Overlay - a trigger component for annotations manipulating.

Combine these features appear to realize different components like highlight, autocomplete, mentions, tagged input, rich editor, etc. The Marked Input is the key component provided by the [rc-marked-input](https://github.com/Nowely/marked-input) library makes it possible to achieve this. 

## Marked Input

The following shows ways to interact with annotations, such as display, edit, add, and configure.

### Mark to Display Annotations

The Marked Input is similar to the common input that allow to edit a text but required for the `Mark` prop. It is a component that used for render annotations. 

By default, the component uses the `@[__label__](__value__)` markup to detect annotations. Then `__label__` and `__value__` from an annotation passed in the `Mark` component like the **label** and the **value** prop.

Review the Clickable Highlight component:

```tsx
import {MarkedInput} from "rc-marked-input";

const Mark = (props) => <mark onClick={_ => alert(props.value)}>{props.label}</mark>

const Marked = () => {
    const [value, setValue] = useState("Hello, clickable marked @[world](Hello! Hello!)!")
    return <MarkedInput Mark={Mark} value={value} onChange={setValue}/>
}
```

It uses the mark tag to display the label and the alert to display the value by the click of an annotation.

{% embed https://codesandbox.io/embed/marked-input-ywnplp?autoresize=1&fontsize=14&hidenavigation=1&theme=dark %}

Using annotations to store the necessary information, a Mark component can use it to implement any logic with any exotic view.

### Dynamic Mark to Edit Annotations

Sometimes just displaying a mark is not enough, need a way to edit it itself. For it in case, the library provides the `useMark` hook.

The `useMark` hook returns the `label` and the `value` of annotation as part of the state, the `onChange` and the `onRemove`  handlers, also the `reg` for register the ref of a mark to do it focusable by key operations.

In this example, let’s expand the Mark component do it is editable:

```tsx
import {MarkedInput, useMark} from "rc-marked-input";

const Mark = () => {
    const {label, onChange} = useMark()

    const handleInput = (e) =>
        onChange({label: e.currentTarget.textContent ?? "", value: " "}, {silent: true})

    return <mark contentEditable onInput={handleInput} children={label}/>
}

export const Dynamic = () => {
    const [value, setValue] = useState("Hello, dynamical mark @[world]( )!")
    return <MarkedInput Mark={Mark} value={value} onChange={setValue}/>
}
```

The value is white space because of the default markup and unused.

The `silent: true` option means no re-render itself by an annotation changed that can be useful for uncontrolled components.

{% embed https://codesandbox.io/embed/dynamic-mark-m5uv58?autoresize=1&fontsize=14&hidenavigation=1&theme=dark %}

The `useMark` allow to avoid props passing and create a Dynamic Mark that can change its annotation and increase the level of interaction. 

### Custom Overlay to Add Annotations

We have the way to display and edit annotations. The last thing missing is the adding a new one. An Overlay that appears by a trigger is a good way to do it. 

By default, a trigger is the `@` character. After entering this or select position after its invoking an Overlay. 

In this example, the header used like overlay:

```tsx
import {MarkedInput} from "rc-marked-input";

const Overlay = () => <h1>I am the overlay</h1>
export const CustomOverlay = () => {
    const [value, setValue] = useState("Hello, custom overlay by trigger @!")
    return <MarkedInput Mark={Mark} Overlay={Overlay} value={value} onChange={setValue}/>
}
```

As props in overlay passed the `onSelect` handler to add a new annotation instead of a triggered value, the `trigger` object with triggered value string, and the `style` object with the `left` and the `right` prop of the current caret position.

{% embed https://codesandbox.io/embed/custom-overlay-8c8e2r?autoresize=1&fontsize=14&hidenavigation=1&theme=dark %}

The overlay can be used for creating suggestions, pop-up, modal window, event listener, etc. 

Data for these components can use the usual ways to get it, such as fetch, context, etc.

### Configure for Multi Annotations

There is a need to configure the Marked Input in advanced cases as:

- Customize `markup`
- Customize `trigger`
- Pass `data` for an overlay (or for the default `Suggestions` component)
- Multi `Option`'s for some type of mark and/or overlay
- Adapting the passing props with the `initMark` and the `initOverlay`

For example, let’s configure the Marked Input for two types of the Button as a mark component.

Markups:

```tsx
const Primary = "@[__label__](primary:__value__)"
const Default = "@[__label__](default)"
```

Data for the Suggestions used to add a new annotation:

```tsx
const Data = ["First", "Second", "Third", "Fourth", "Fifth", "Sixth"]
const AnotherData = ["Seventh", "Eight", "Ninth"]
```

Then the Marked Input would look like this:

```tsx
import {MarkedInput, Option} from "rc-marked-input";

export const App = () => {
    const [value, setValue] = useState(
        "Enter the '@' for creating @[Primary Mark](primary:Hello!) or '/' for @[Default mark](default)!"
    )

    return (
        <MarkedInput Mark={Button} value={value} onChange={setValue}>
            <Option
                markup={Primary}
                data={Data}
                initMark={({label, value}) => ({label, primary: true, onClick: () => alert(value)})}
            />
            <Option
                markup={Default}
                trigger="/"
                data={AnotherData}
            />
        </MarkedInput>
    )
}
```

- The `Option` component used to configure the Marked Input.
- The `initMark` for pass extra associative information like the `primary` flag or the `onClick` handler.
- In the second `Option` overridden the `trigger` prop because need to pass another data for suggestions.

The resulting structure doesn't look nice. To correct this injustice is the `createMarkedInput` helper. It allows to encapsulate static props and to create the one row component input:

```tsx
import {createMarkedInput} from "rc-marked-input";

const ConfiguredMarkedInput = createMarkedInput(Button, [{
    markup: Primary,
    data: Data,
    initMark: ({label, value}) => ({label, primary: true, onClick: () => alert(value)})
}, {
    trigger: '/',
    markup: Default,
    data: AnotherData
}])

const App = () => {
    const [value, setValue] = useState(
        "Enter the '@' for creating @[Primary Mark](primary:Hello!) or '/' for @[Default mark](default)!"
    )
    return <ConfiguredMarkedInput value={value} onChange={setValue}/>
}
```

In addition, the library provides also the `annotate` for create annotations and the `denote` for transform annotations to text helpers.

The sand box of configured Marked Input:

{% embed https://codesandbox.io/embed/configured-marked-input-dnvuv9?autoresize=1&fontsize=14&hidenavigation=1&theme=dark %}

Operations with options appear to realize a conditional display or a logic that can help to create more complex components.

The provided helpers allow to close some cases, not relying only on the Marked Input.

## Conclusion

The [rc-marked-input](https://github.com/Nowely/marked-input) library appears to use directly realized such components like a Mark and an Overlay. In combination with editable text, it becoming a kind of utility that helps easy to create impressive high-level components.

---

This library is young and stays in the active development. If you want to contribute, submit a PR, open an issue, or start a discussion!

More examples in [Storybook](https://marked-input.vercel.app/). Source code in [GitHub](https://github.com/Nowely/marked-input).