Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

run focus event listener after existing onFocus handlers #4755

Merged
merged 2 commits into from
Jan 10, 2022

Conversation

jhurwitz
Copy link
Contributor

@jhurwitz jhurwitz commented Dec 30, 2021

Description
As described in the issue, the useFocused hook was returning incorrect/unexpected values. I debugged and realized it was displaying old values because of the order in which event listeners were running.

Issue
Fixes: #4754

Example
Here I repro the exact two behaviors I described in the linked issue, but with this fix in place. (View the GIFs in the issue to see what they looked like before this fix.)
GIF 12-30-21 at 3 49 48 PM
GIF 12-30-21 at 3 50 21 PM

Context
Based on my testing, I think there was a race condition between the focus/blur event listeners in components/slate.tsx (added in #3987) and the onFocus/onBlur handlers in components/editable.tsx. The IS_FOCUSED map was eventually consistent/had the correct values after all handlers ran, but the event listeners in slate.tsx were running before the handlers in editable.tsx and therefore the isFocused hook was displaying the old values, from before the focus/blur handlers ran.

Adding a setTimeout with a delay of 0 cause the event listeners in slate.tsx to run after the handlers in editable.tsx, thereby fixing the issue.

Checks

  • The new code matches the existing patterns and styles.
  • The tests pass with yarn test.
  • The linter passes with yarn lint. (Fix errors with yarn fix.)
  • The relevant examples still work. (Run examples with yarn start.)
  • You've added a changeset if changing functionality. (Add one with yarn changeset add.)

@changeset-bot
Copy link

changeset-bot bot commented Dec 30, 2021

🦋 Changeset detected

Latest commit: b885d86

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
slate-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@jhurwitz
Copy link
Contributor Author

jhurwitz commented Jan 3, 2022

I believe the integration test failure is a flaky test. That test passes locally, and it's failing on this line which suggests to me that maybe the wait just isn't long enough?

    cy.findByRole('textbox')
      // need wait() here otherwise the slate component is not fully mounted yet sometimes
      .wait(1000)
      .type(...

Copy link
Collaborator

@dylans dylans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually @jhurwitz , I wonder if we really want to use window.setTimeout inside useIsomorphicLayoutEffect instead of some other timeout mechanism that doesn't rely on the presence of the window object?

@jhurwitz
Copy link
Contributor Author

jhurwitz commented Jan 4, 2022

Actually @jhurwitz , I wonder if we really want to use window.setTimeout inside useIsomorphicLayoutEffect instead of some other timeout mechanism that doesn't rely on the presence of the window object?

Ah, good call. Would changing to just setTimeout work? That should call the same method in the browser, and seems to also work in Node/server-side environments where there is no window object.

@363676727
Copy link

363676727 commented Jan 6, 2022

I use the newest recently appeared.
It seems that the react on your demo is not the same.
Or is it the browser difference?
Kapture 2022-01-06 at 12 14 01

@jhurwitz
Copy link
Contributor Author

jhurwitz commented Jan 6, 2022

I use the newest recently appeared.
It seems that the react on your demo is not the same.
Or is it the browser difference?
Kapture 2022-01-06 at 12 14 01

Thanks for the comment. Can you please give more details?

Are you running this out of the Codesandbox or locally? I suspect you might be running it locally, because the gray background in your screen capture does not appear in the Codesandbox.

And just to confirm -- are you testing on the main branch of slate-react, or on the branch containing this PR?

I use the newest recently appeared.

The newest what, exactly?

It seems that the react on your demo is not the same.

Are you saying you're using a different version of React.js (if so, which version? the codesandbox uses 17.0.2, the latest version), or are you saying that the reaction of the useFocused hook is different?

Or is it the browser difference?

What browser and OS are you using?

@zhugexinxin
Copy link
Contributor

zhugexinxin commented Jan 6, 2022

Actually @jhurwitz , I wonder if we really want to use window.setTimeout inside useIsomorphicLayoutEffect instead of some other timeout mechanism that doesn't rely on the presence of the window object?

Ah, good call. Would changing to just setTimeout work? That should call the same method in the browser, and seems to also work in Node/server-side environments where there is no window object.

Kapture 2022-01-06 at 20 38 27

my steps

  1. git clone slate
  2. modified plaintext.tsx
  3. yarn start
import React, { useState, useMemo } from 'react'
import { createEditor, Descendant } from 'slate'
import { Slate, Editable, withReact, useFocused } from 'slate-react'
import { withHistory } from 'slate-history'
const FocusState = () => {
  const editorHasFocus = useFocused();
  if (editorHasFocus) {
    return <div>focused</div>;
  } else {
    return <div>not focused</div>;
  }
}

const PlainTextExample = () => {
  const [value, setValue] = useState<Descendant[]>(initialValue)
  const editor = useMemo(() => withHistory(withReact(createEditor())), [])
  return (
    <Slate editor={editor} value={value} onChange={value => setValue(value)}>
      <Editable placeholder="Enter some plain text..." />
      <FocusState />

    </Slate>
  )
}

const initialValue: Descendant[] = [
  {
    type: 'paragraph',
    children: [
      { text: '' },
    ],
  },
]

export default PlainTextExample

Environment

running on locally

Slate Version: 0.72.3
Operating System: macOS 10.15.7
Browser: Chrome 96.0.4664.55
react 16.12.0

@dylans dylans merged commit 8daa77e into ianstormtaylor:main Jan 10, 2022
@jhurwitz jhurwitz deleted the jhurwitz/focus branch January 10, 2022 23:22
This was referenced Jan 10, 2022
@bryanph
Copy link
Contributor

bryanph commented Jan 25, 2022

This causes a regression for me somehow when selecting between two different editors. It moves the cursor back to the previous selection instead. Trying to investigate why exactly but I narrowed it down to the setTimeout introduced here.

Perhaps a better way to solve this generally is to set the FocusedContext state in the Editable directly instead? (where IS_FOCUSED is set). Overall not a fan of using setTimeout to perform logic to make sure things run in the right order as it breaks very easily.

bryanph added a commit to bryanph/slate that referenced this pull request Jan 25, 2022
bryanph added a commit to bryanph/slate that referenced this pull request Mar 8, 2022
@bryanph bryanph mentioned this pull request Mar 8, 2022
5 tasks
dylans pushed a commit that referenced this pull request Mar 9, 2022
@dylans dylans mentioned this pull request Mar 9, 2022
DougReeder pushed a commit to DougReeder/slate that referenced this pull request Apr 3, 2022
…lor#4755)

* run focus event listener after existing onFocus handlers

* change window.setTimeout to setTimeout
DougReeder pushed a commit to DougReeder/slate that referenced this pull request Apr 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

useFocused hook displays old value
5 participants