ianstormtaylor/slate

Rerendering problem: misspelled words flicker while typing elsewhere

Closed this issue · 14 comments

Do you want to request a feature or report a bug?

  • A bug

What's the current behavior?

  • As you type, in a spellchecked field, the red underlines flash/flicker as content changes.

flash

OS Browser
macOS High Sierra Chrome 63.0.3239.132

What's the expected behavior?

  • Misspelled words should have a constant, not flashing or flickering, red underline.

It seems like this may have been caused by the removal of isNative? (#829 (comment))

@ianstormtaylor would love to jump in and try and contribute a fix here, but unfamiliar with Slate’s internals and the history of changes (e.g. removing isNative).

Any recommendations on where to start?

Hey @steobrien, thanks for bringing this up. I think the first thing I’d do is investigate if there is any way to re-render the DOM while preserving the spellcheck styles. If there isn’t, this is going to be pretty hard to do I think.

Couple observations with other editors:

  • On Medium.com something similar happens, though it manifests a big differently. The spell-check underline disappears (which is much worse than flickering if you ask me). I think that's how Draft.js does it too.
  • Google Docs have their own spell check that they run against their servers.

@dmitrizzle, that's not quite what I see in Medium’s editor or Draft.js…

Medium appears to rerender after each word/space:
medium

Draft.js appears identical to native textarea:
draft

My bad about Draft.js - must have been an earlier version. But Medium on Safari 11.0.1/macOS 10.12.6 still does that:

We’ve worked around this in our application with something like the following:

const whiteSpaceAndPunctuation = [".", ",", ";", ":", "'", '"', " ", "Enter"]



onKeyDown = event => {
  const spellCheckEnabled = whiteSpaceAndPunctuation.includes(event.key)
  this.setState({ spellCheckEnabled })
}



<Editor  spellCheck={this.state.spellCheckEnabled} />

This solves the greatest problem here which is that Slate will highlight the word you're currently typing, if what's typed so far is incorrect, which can be super distracting when thinking & writing.

@steobrien that is definitely a big problem. Do you know what specific browser-level behavior is causing that?

Unfortunately I don't.

From a quick investigation, it appears to me that (from a high-level DOM changes point of view) what Slate and Draft.js are doing is equivalent. My best guess would be something to do with cursor position, momentary focus state change, or the implementation detail used to update the DOM.

I can confirm that this problem was not in Slate version 0.21.4, we noticed it when upgrading to 0.32.2.

I think momentary focus change could definitely be it. It seems like browsers keep the most recently typed word exempt from spellcheck, so if we're moving the cursor that could remove that exemption.

This is now better described in #2061. Closing in favor of it.

We’ve worked around this in our application with something like the following:

const whiteSpaceAndPunctuation = [".", ",", ";", ":", "'", '"', " ", "Enter"]



onKeyDown = event => {
  const spellCheckEnabled = whiteSpaceAndPunctuation.includes(event.key)
  this.setState({ spellCheckEnabled })
}



<Editor  spellCheck={this.state.spellCheckEnabled} />

This solves the greatest problem here which is that Slate will highlight the word you're currently typing, if what's typed so far is incorrect, which can be super distracting when thinking & writing.

Hi @steobrien , thanks for sharing your solution. I tried to implement it as I think it's an elegant quick win. I have an issue though: after I type a first character that's not contained in whiteSpaceAndPunctuation, if I press the backspace key for instance, I need to do it twice for it to work.

I'd be so grateful if you could have a quick look at my code, RichText file, line 23 onKeyDown.

that's how I implemented it:

const [spellCheckEnabled, setSpellCheckEnabled] = useState(true);

  const onKeyDown = (event, editor, next) => {
    const containsWhiteSpaceAndPunctuation = whiteSpaceAndPunctuation.includes(
      event.key
    );
    if (!containsWhiteSpaceAndPunctuation) {
      next();
    }
    setSpellCheckEnabled(containsWhiteSpaceAndPunctuation);
  };

Thank you so much!

Looks reasonable, @gregoryforel!

Hey, thanks for answering. Have you tried the codesandbox I provide? Try typing anything, then backspace or left arrow. You'll see you will always to backspace twice before it works.

Excuse the delay @gregoryforel, I am very busy at the moment!

Looking at your codesandbox quickly, I see the problem but am not exactly sure what's causing it. Here's a quick workaround though.

// before
setSpellCheckEnabled(containsWhiteSpaceAndPunctuation)

// after
requestAnimationFrame(() => setSpellCheckEnabled(containsWhiteSpaceAndPunctuation))

Hope this helps!