# Custom React Spellcheck using Lint example as base

**URL:** https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352
**Category:** Uncategorized
**Created:** [May 25, 2018, 9:16am UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352 "2018-05-25T09:16:10Z")
**Posts on this page:** 10
**Page:** 1

<div class="post-metadata">

### Author: ![Sauco82](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/sauco82/32/646_2.png) [@Sauco82](https://discuss.prosemirror.net/u/Sauco82)
#### Post date: [May 25, 2018, 9:16am UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/1 "2018-05-25T09:16:10Z")

</div>

I’m trying to implement some basic spellchecking functionality using React and Prosemirror using [prosemirror lint example](https://prosemirror.net/examples/lint/) as a base.

So far the code snippet I’m pasting works fine and highlights the words contained in `errors` without an issue, but the problem comes when I want to make `errors` a dynamic state inside React.

I’m aware that I could just use a pointer to said state and have it working on Prosemirror state update but I was thinking about getting the new errors highlighted as soon as they came in. For that I thought about using the view props and update them on `errors` update but don’t know how to access the view from the plugin for that to work.

Any suggestions about how to approach this ?

```
import React from "react";` 

// Prosemirror
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "prosemirror-schema-basic";
import { Plugin } from "prosemirror-state";

import { Decoration, DecorationSet } from "prosemirror-view";

let errors = ["hola"];

// Spellcheck plugin logic
// ================================================================================
function errorsRegexp(errors) {
  return RegExp("\\b(" + errors.join("|") + ")\\b", "gi")
}

function lint(doc) {
  let result = [];

  function record(from, to, word) {
    result.push({ from, to, word });
  }

  doc.descendants((node, currentPosition) => {
    let match,
        rgx = errorsRegexp(errors);

    if (node.isText) {
      while (match = rgx.exec(node.text)) record(
        currentPosition + match.index,
        currentPosition + match.index + match[0].length,
        match[0]
      );
    }
  });

  return result;
}

function lintDeco(doc) {
  let decorations = [];

  lint(doc).forEach(mistake => {
    decorations.push(Decoration.inline(mistake.from, mistake.to, { class: "spellcheck_mistake" }));
  });

  return DecorationSet.create(doc, decorations);
}

let lintPlugin = new Plugin({
  state: {
    init: (_, state ) => lintDeco(state.doc),
    apply: (tr, old, oldState, newState) => tr.docChanged ? lintDeco(newState.doc) : old
  },
  props: {
    decorations: function(state) { return this.getState(state); }
  }
});
// ================================================================================
// end spellcheck logic

class Editor extends React.Component {
  constructor() {
    super();

    let editorState = EditorState.create({ schema, plugins: [lintPlugin] });

    this.state = { editorState };
    this.dispatchTransaction = this.dispatchTransaction.bind(this);
  }

  componentDidMount() {
    this.view = new EditorView(document.querySelector("#test"), {
      state: this.state.editorState,
      dispatchTransaction: this.dispatchTransaction,
      errors: ["potato"]
    });
  }

  dispatchTransaction(transaction) {
    let editorState = this.state.editorState.apply(transaction);

    this.setState({ editorState });
    this.view.updateState(editorState);
  }

  render() {
    return (
      <div>
        <div ref={editor => this.editor = editor} id="test" spellCheck="false"></div>
      </div>
    );
  }
}

export default Editor;
```

---

<div class="post-metadata">

### Author: ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)
#### Post date: [May 25, 2018, 9:30am UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/2 "2018-05-25T09:30:40Z")

</div>

> [@Sauco82](#):
>
> For that I thought about using the view props and update them on `errors` update but don’t know how to access the view from the plugin for that to work.

Plugins can expose their own props, so the way you’d do this is have your plugin store the highlight decorations in its state field, and return that from its `decorations` prop. Then when information comes in you fire a transaction with the new information in a meta property, and have the plugin update its state when it sees such a transaction.

---

<div class="post-metadata">

### Author: ![Sauco82](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/sauco82/32/646_2.png) [@Sauco82](https://discuss.prosemirror.net/u/Sauco82)
#### Post date: [May 25, 2018, 10:42am UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/3 "2018-05-25T10:42:15Z")

</div>

Hi Marijin, thanks a lot for the answer and your great work with prosemirror!

I managed to get it working using your answer as inspiration but not sure if this is the right way or I’m doing something horribly wrong within Prosemirror’s way of doing things.

```
let lintPlugin = new Plugin({
  state: {
    init: function(_, state) { return lintDeco(state.doc, this.spec.state.errors) },
    apply: function(tr, old, oldState, newState) {
      if (tr.meta.newError) {
        this.spec.state.errors.push(tr.meta.newError);
      }
      return tr.docChanged || tr.meta.newError ? lintDeco(newState.doc, this.spec.state.errors) : old;
    },
    errors: ["hola"]
  },
  props: {
    decorations: function(state) { return this.getState(state); },
  }
});
```

---

<div class="post-metadata">

### Author: ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)
#### Post date: [May 25, 2018, 10:58am UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/4 "2018-05-25T10:58:56Z")

</div>

You’re mutating state stored in the editor state (`this.spec.state.errors`), which means you won’t be able to use `EditorState` as if it is immutable anymore. Also, depending on how many errors there are, it might be much cheaper to call [`old.map`](http://prosemirror.net/docs/ref/#view.DecorationSet.map) when the doc changes, and then add the new error, if any, with its [`add` method](http://prosemirror.net/docs/ref/#view.DecorationSet.add), instead of rebuilding the whole set.

---

<div class="post-metadata">

### Author: ![Sauco82](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/sauco82/32/646_2.png) [@Sauco82](https://discuss.prosemirror.net/u/Sauco82)
#### Post date: [May 25, 2018, 11:15am UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/5 "2018-05-25T11:15:53Z")

</div>

I suspected that I was mutating the state of the Plugin but I haven’t found any method to do so, so I thought that was the way. What would be the inmutable way to update `errors`?

Regarding the errors and the length of the text they both will be short, the final UI would have multiple single paragraph editors, so I’m not very concerned over there.

---

<div class="post-metadata">

### Author: ![marijn](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/marijn/32/15_2.png) [@marijn](https://discuss.prosemirror.net/u/marijn)
#### Post date: [May 25, 2018, 11:42am UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/6 "2018-05-25T11:42:23Z")

</div>

> [@Sauco82](#):
>
> What would be the inmutable way to update `errors` ?

Don’t put stateful things in your plugin spec. You can put them in a field in your plugin state (make sure you update `decorations` to return only the decorations field), and make the state something like `{decorations, errors}`, creating a new object and using `concat` to append errors every time you update it.

(If you only have a few errors at any time, you don’t even need to cache the decoration set—you could just compute the decoration set in the `decorations` prop. ProseMirror compares these structurally so it won’t needlessly redraw the DOM for decorations that are the same.)

---

<div class="post-metadata">

### Author: ![Sauco82](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/sauco82/32/646_2.png) [@Sauco82](https://discuss.prosemirror.net/u/Sauco82)
#### Post date: [May 25, 2018, 1:12pm UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/7 "2018-05-25T13:12:33Z")

</div>

I think I finally got it. Thank you very much! 😀

I guess that I have been very confused. So I will be leaving the explanation here just in case someone struggles though the same mistakes I committed:

For what I see I’ve been interpreting all this time that the state should go PluginSpec’s state which I was mixing with Plugin’s state. Reason being I don’t see any way to set a state for a Plugin outside PluginSpecs, no matter how much I look at the docs.

By looking at what getState returns then it finally made sense, the state is what `init` and `apply` returns. With that in mind (and removing the caching) the code ends like this:

```
function lintState(doc, errors) {
  let decorations = [];

  lint(doc, errors).forEach(mistake => {
    decorations.push(Decoration.inline(mistake.from, mistake.to, { class: "spellcheck_mistake" }));
  });

  return {decorations: DecorationSet.create(doc, decorations), errors };
}

let lintPlugin = new Plugin({
  state: {
    init: function(_, state) { return lintState(state.doc, ["hola"]) },
    apply: function(tr, old, oldState, newState) {
      let errors = this.props.errors(oldState);

      if (tr.meta.newError) errors = errors.concat(tr.meta.newError);

      return lintState(newState.doc, errors);
    }
  },
  props: {
    decorations: function(state) {
      return this.getState(state).decorations;
    },
    errors: function (state) {
      return this.getState(state).errors;
    },
  }
});
```

---

<div class="post-metadata">

### Author: ![chrisbanks](https://discuss.prosemirror.net/letter_avatar_proxy/v4/letter/c/dec6dc/32.png) [@chrisbanks](https://discuss.prosemirror.net/u/chrisbanks)
#### Post date: [June 4, 2018, 3:21pm UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/8 "2018-06-04T15:21:05Z")

</div>

Our plugin shows you how you can implement spelling and grammar checking in ProseMirror. The code for the plugin is Open Source so you can use it for your own grammar checker.

> **[prowriting/beyondgrammar-prosemirror](https://github.com/prowriting/beyondgrammar-prosemirror)**
>
> Add real-time grammar checking, spellchecking, style improvements and terminology checking to ProseMirror - prowriting/beyondgrammar-prosemirror

---

<div class="post-metadata">

### Author: ![Sauco82](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/sauco82/32/646_2.png) [@Sauco82](https://discuss.prosemirror.net/u/Sauco82)
#### Post date: [June 5, 2018, 7:47am UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/9 "2018-06-05T07:47:49Z")

</div>

Thank you Chris!

---

<div class="post-metadata">

### Author: ![julia.shaptala](https://discuss.prosemirror.net/user_avatar/discuss.prosemirror.net/julia.shaptala/32/858_2.png) [@julia.shaptala](https://discuss.prosemirror.net/u/julia.shaptala)
#### Post date: [February 4, 2019, 9:44pm UTC](https://discuss.prosemirror.net/t/custom-react-spellcheck-using-lint-example-as-base/1352/10 "2019-02-04T21:44:02Z")

</div>

There is an alternative solution (Wproofreader) for check spelling and grammar in ProseMirror that you can consider: [https://webspellchecker.com/wsc-proofreader/#proofreader-prosemirror](https://webspellchecker.com/wsc-proofreader/#proofreader-prosemirror)

I have just published a post about it: [WProofreader Spelling and Grammar Checker for ProseMirror](https://discuss.prosemirror.net/t/wproofreader-spelling-and-grammar-checker-for-prosemirror/1777)

This is not an open source solution but it provides nice benefits along with the extensive language support.
