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

Bump preact from 10.5.13 to 10.5.15 #3845

Merged
merged 2 commits into from
Nov 11, 2021

Conversation

dependabot[bot]
Copy link
Contributor

@dependabot dependabot bot commented on behalf of github Oct 18, 2021

Bumps preact from 10.5.13 to 10.5.15.

Release notes

Sourced from preact's releases.

10.5.15

Fixes

Types

Maintenance

10.5.14

Features

Bug Fixes

Size

Typings

... (truncated)

Commits
  • bd52611 10.5.15 (#3282)
  • 9ae9f03 Avoid leaking internal vnodes (#3106)
  • 51bfe15 Use Ref to accept RefCallback (#3214)
  • d78746c [compat] Skip rendering <noscript> contents on the client (#3238)
  • 6d0f682 Update babel.config.js (#3265)
  • 4aaecc1 Merge pull request #3271 from RRDAWLX/master
  • 2d7ba34 polish: delete redundant previousComponent in hooks
  • 98f130e Merge pull request #3260 from preactjs/bugfix-suspended-missing-children
  • 2624d51 Merge branch 'master' into bugfix-suspended-missing-children
  • fe3d375 Merge pull request #2859 from nmondon/fix-dominant-baseline-attr
  • Additional commits viewable in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot merge will merge this PR after your CI passes on it
  • @dependabot squash and merge will squash and merge this PR after your CI passes on it
  • @dependabot cancel merge will cancel a previously requested merge and block automerging
  • @dependabot reopen will reopen this PR if it is closed
  • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

@dependabot dependabot bot added dependencies Pull requests that update a dependency file javascript labels Oct 18, 2021
@dependabot dependabot bot force-pushed the dependabot/npm_and_yarn/preact-10.5.15 branch from c8509b1 to b524ce9 Compare October 25, 2021 11:29
@codecov
Copy link

codecov bot commented Oct 27, 2021

Codecov Report

Merging #3845 (2dd33bf) into master (8e2d612) will not change coverage.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #3845   +/-   ##
=======================================
  Coverage   98.98%   98.98%           
=======================================
  Files         211      211           
  Lines        7904     7904           
  Branches     1870     1871    +1     
=======================================
  Hits         7824     7824           
  Misses         80       80           
Impacted Files Coverage Δ
src/annotator/util/emitter.js 100.00% <ø> (ø)
...ar/components/Annotation/AnnotationShareControl.js 100.00% <ø> (ø)
src/annotator/components/NotebookModal.js 100.00% <100.00%> (ø)
src/sidebar/components/Excerpt.js 92.10% <100.00%> (ø)
src/sidebar/components/MarkdownEditor.js 91.97% <100.00%> (ø)
src/sidebar/components/MarkdownView.js 100.00% <100.00%> (ø)
src/sidebar/components/Menu.js 100.00% <100.00%> (ø)
src/sidebar/components/MenuItem.js 96.55% <100.00%> (ø)
src/sidebar/components/MenuKeyboardNavigation.js 100.00% <100.00%> (ø)
src/sidebar/components/SearchInput.js 95.65% <100.00%> (ø)
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8e2d612...2dd33bf. Read the comment docs.

@robertknight
Copy link
Member

robertknight commented Oct 27, 2021

I have added a commit which shows a possible solution to the increased useRef strictness in this Preact update. If we like it then we'll want to use it in other projects too, so we should move it into @hypothesis/frontend-shared or another shared package.

An alternative approach would be to patch the Preact typings locally to revert useRef to the way it worked before. The idiom we used for element refs was always kind of a hack though.

Bumps [preact](https://github.com/preactjs/preact) from 10.5.13 to 10.5.15.
- [Release notes](https://github.com/preactjs/preact/releases)
- [Commits](preactjs/preact@10.5.13...10.5.15)

---
updated-dependencies:
- dependency-name: preact
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
}, [collapsedHeight, onCollapsibleChanged, overflowThreshold]);
}, [
collapsedHeight,
contentElement,
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately ESLint plugin for React doesn't know that useElementRef returns an immutable value that therefore doesn't need to be listed in the dependencies array. Would be worth checking if there is a config option to avoid this.

Copy link
Member

Choose a reason for hiding this comment

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

Unfortunately it looks like there isn't a config option to declare that a custom hook returns a stable result (ie. one that does not change across renders). See facebook/react#22539.

@@ -141,6 +140,7 @@ export default function MenuItem({
// The menu item is a link
menuItem = (
<a
// @ts-ignore
Copy link
Member

Choose a reason for hiding this comment

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

This is a hack to deal with the fact that this ref may be assigned to either an HTMLAnchorElement or an HTMLDivElement and the useElementRef API only supports one type. I'm sure there will be a better way to handle this.

@@ -144,5 +144,5 @@ export function useStoreProxy() {
return cleanup;
}, [cache, store]);

return proxy.current;
return /** @type {SidebarStore} */ (proxy.current);
Copy link
Member

Choose a reason for hiding this comment

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

This is non-null cast here, which we know is safe in this context.

* @return {{ current: InstanceType<T> }}
*/
// eslint-disable-next-line no-unused-vars
export function useElementRef(type) {
Copy link
Member

@robertknight robertknight Oct 28, 2021

Choose a reason for hiding this comment

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

The type argument is currently unused and is purely a hint to TS. We could perhaps use it for an instanceof check in the current field setter in future, maybe only in development builds.

setGroupId(groupId);
}
);
const newEmitter = eventBus.createEmitter();
Copy link
Member

Choose a reason for hiding this comment

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

These changes fix two complaints about emitter.current being possibly null. In the subscribe call we know that's not the case. In the cleanup callback that useEffect returns however there is the possibility that emitter.current gets re-assigned before the callback is run. The change here avoids that hazard.

@esanzgar esanzgar self-requested a review November 10, 2021 14:29
@esanzgar
Copy link
Contributor

I believe it is a good thing that preact has made the refs nullable. I think that would prevent the pitfall of believing a ref has been assigned when it didn't because it was conditionally created or rendered.

Instead of the solution presented in this PR, I would suggest to evaluate all our current usage of useRef.

I would suggest this more concise version to define the types:

const inputRef = useRef(/** @type {HTMLInputElement?} */ (null)); (notice no null in the type).

If we know for sure that certain ref will be assigned, I suggest this other type (which is not much longer):

const shareRef = /** @type {{current: HTMLDivElement}} */ (useRef());

@robertknight robertknight force-pushed the dependabot/npm_and_yarn/preact-10.5.15 branch 2 times, most recently from 5797efb to 2bfa29e Compare November 10, 2021 16:57
@robertknight
Copy link
Member

Hi @esanzgar - I agree with your suggestion. Casting the result of useRef does seem like a better solution. Using a custom hook for such a common use case did feel a bit un-idiomatic. I do have a slight revision to suggest though which is to make the two cases more symmetric so we don't have to rethink which to use in a given situation. Also I preferred T|null rather than T? just for consistency with existing types in the codebase.

I have pushed a replacement second commit on this branch which changes all instances of:

const someRef = useRef(/** @type {T|null} */ (null));

To one of:

const nonNullRef = /** @type {{ current: T }} */ (useRef());
const nullRef = /** @type {{ current: T|null }} */ (useRef(null));

Given how common this is in the code, we could maybe shorten it down in future by introducing a global Ref<T> alias for { current: T }.

@@ -60,6 +60,9 @@ function AnnotationShareControl({
const closePanel = () => setOpen(false);

// Interactions outside of the component when it is open should close it
//
// @ts-expect-error - `useElementShouldClose` requires a non-nullable ref,
// but `shareRef` is nullable.
useElementShouldClose(shareRef, isOpen, closePanel);
Copy link
Member

Choose a reason for hiding this comment

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

There is a bug here, but since it is not a new one, I just suppressed the error for the moment.

Copy link
Contributor

Choose a reason for hiding this comment

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

shareRef is a non null ref, why not to use the pattern you suggested: const shareRef = /** @type {{ current: HTMLDivElement }} */ (useRef());

Copy link
Member

Choose a reason for hiding this comment

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

shareRef is a nullable ref! - There is a return null statement in the component mid-way down with an issue link above it.

Copy link
Contributor

@esanzgar esanzgar Nov 11, 2021

Choose a reason for hiding this comment

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

Yes, you are right. I was looking for return null but closer to the return of the component and I didn't find it...

Then, yes I agree that adding the ts-expect-error is the best option for now.

@robertknight robertknight force-pushed the dependabot/npm_and_yarn/preact-10.5.15 branch from 2bfa29e to 89201db Compare November 10, 2021 17:06
robertknight added a commit to hypothesis/lms that referenced this pull request Nov 11, 2021
Adapt to changes in useRef types in Preact v10.5.15 by using the pattern
described in hypothesis/client#3845 (comment)
to cast the result of `useRef`.
@robertknight
Copy link
Member

I applied the pattern mention in #3845 (comment) in the LMS app: hypothesis/lms#3234.

robertknight added a commit to hypothesis/lms that referenced this pull request Nov 11, 2021
Adapt to changes in useRef types in Preact v10.5.15 by using the pattern
described in hypothesis/client#3845 (comment)
to cast the result of `useRef`.
Copy link
Contributor

@esanzgar esanzgar left a comment

Choose a reason for hiding this comment

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

I believe that the input ref in the MarkdownEditor is conditionally rendered.

You have suggested two patterns for the useRef's type:

const nonNullRef = /** @type {{ current: T }} */ (useRef());
const nullRef = /** @type {{ current: T|null }} */ (useRef(null));

However, the pattern: const nullRef = useRef(/** @type {T|null} */ (null)); is still present. Do you have plans to change that for the new nullRef style you suggest above?

@@ -60,6 +60,9 @@ function AnnotationShareControl({
const closePanel = () => setOpen(false);

// Interactions outside of the component when it is open should close it
//
// @ts-expect-error - `useElementShouldClose` requires a non-nullable ref,
// but `shareRef` is nullable.
useElementShouldClose(shareRef, isOpen, closePanel);
Copy link
Contributor

Choose a reason for hiding this comment

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

shareRef is a non null ref, why not to use the pattern you suggested: const shareRef = /** @type {{ current: HTMLDivElement }} */ (useRef());

@@ -398,7 +398,7 @@ export default function MarkdownEditor({
const [preview, setPreview] = useState(false);

// The input element where the user inputs their comment.
const input = useRef(/** @type {HTMLTextAreaElement|null} */ (null));
const input = /** @type {{ current: HTMLTextAreaElement }} */ (useRef());
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this ref could be null (it is conditionally rendered).

Copy link
Member

Choose a reason for hiding this comment

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

Oh indeed, you're right. I'll correct this.

const emitter = useRef(
/** @type {ReturnType<eventBus['createEmitter']>|null} */ (null)
);
const emitterRef = /** @type {{ current: Emitter|null }} */ (useRef(null));
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe trimming start and end whitespaces?

Suggested change
const emitterRef = /** @type {{ current: Emitter|null }} */ (useRef(null));
const emitterRef = /** @type {{current: Emitter|null}} */ (useRef(null));

Copy link
Member

@robertknight robertknight Nov 11, 2021

Choose a reason for hiding this comment

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

I don't have a hugely strong aesthetic preference here, but I used whitespace because that's how Prettier formats a type in TS (ie. if you write type Ref = {current: HTMLElement} in a TS file and format it) and also how the majority of our current inline object types are formatted.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, sure.

`useRef(null as T|null)` now returns a `{ current: T|null }` instead of
`{ current: T }` as it did before. ie. it no longer drops the the null.
This makes sense but conflicted with a pattern we used in many places to
create a non-null ref: `useRef(/** @type {T|null} */ (null))`.

Resolve this by changing all non-nullable refs, for elements which are
set after the initial render, to cast the `useRef` result instead of the
init value.

```
const nonNullRef = /** @type {{ current: T }} */ (useRef());
```
@robertknight robertknight force-pushed the dependabot/npm_and_yarn/preact-10.5.15 branch from 11fd7d1 to 2dd33bf Compare November 11, 2021 16:12
@robertknight
Copy link
Member

However, the pattern: const nullRef = useRef(/** @type {T|null} */ (null)); is still present. Do you have plans to change that for the new nullRef style you suggest above?

Hmm. Looking at this again today made me change my mind. It would have made the two styles of element ref consistent, but it would then be inconsistent with other non-element refs. I've changed it back to be much more like your original suggestion, with the only change being to use an explicit |null instead of ?, because that's how we already write nullable types in most places.

@@ -60,6 +60,9 @@ function AnnotationShareControl({
const closePanel = () => setOpen(false);

// Interactions outside of the component when it is open should close it
//
// @ts-expect-error - `useElementShouldClose` requires a non-nullable ref,
// but `shareRef` is nullable.
useElementShouldClose(shareRef, isOpen, closePanel);
Copy link
Contributor

@esanzgar esanzgar Nov 11, 2021

Choose a reason for hiding this comment

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

Yes, you are right. I was looking for return null but closer to the return of the component and I didn't find it...

Then, yes I agree that adding the ts-expect-error is the best option for now.

@robertknight robertknight merged commit 8ee4cec into master Nov 11, 2021
@robertknight robertknight deleted the dependabot/npm_and_yarn/preact-10.5.15 branch November 11, 2021 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants