diff --git a/compat/src/index.d.ts b/compat/src/index.d.ts index 08cb01999f..5b8d48b289 100644 --- a/compat/src/index.d.ts +++ b/compat/src/index.d.ts @@ -27,6 +27,7 @@ declare namespace React { export import Inputs = _hooks.Inputs; export import PropRef = _hooks.PropRef; export import Reducer = _hooks.Reducer; + export import Dispatch = _hooks.Dispatch; export import Ref = _hooks.Ref; export import StateUpdater = _hooks.StateUpdater; export import useCallback = _hooks.useCallback; @@ -49,6 +50,7 @@ declare namespace React { ): T; // Preact Defaults + export import Context = preact.Context; export import ContextType = preact.ContextType; export import RefObject = preact.RefObject; export import Component = preact.Component; @@ -138,6 +140,10 @@ declare namespace React { ) => boolean ): C; + export interface RefAttributes extends preact.Attributes { + ref?: preact.Ref | undefined; + } + export interface ForwardFn

{ (props: P, ref: ForwardedRef): preact.ComponentChild; displayName?: string; @@ -147,6 +153,11 @@ declare namespace React { current: T; } + export interface ForwardRefExoticComponent

+ extends preact.FunctionComponent

{ + defaultProps?: Partial

| undefined; + } + export type ForwardedRef = | ((instance: T | null) => void) | MutableRefObject diff --git a/debug/src/debug.js b/debug/src/debug.js index 50f273e3ab..b38d4ccf0b 100644 --- a/debug/src/debug.js +++ b/debug/src/debug.js @@ -34,14 +34,16 @@ export function initDebug() { let oldVnode = options.vnode; let oldCatchError = options._catchError; let oldRoot = options._root; + let oldRender = options._render; let oldHook = options._hook; + const warnedComponents = !isWeakMapSupported ? null : { useEffect: new WeakMap(), useLayoutEffect: new WeakMap(), lazyPropTypes: new WeakMap() - }; + }; const deprecations = []; options._catchError = (error, vnode, oldVNode) => { @@ -282,6 +284,13 @@ export function initDebug() { if (oldBeforeDiff) oldBeforeDiff(internal, vnode); }; + options._render = vnode => { + if (oldRender) { + oldRender(vnode); + } + hooksAllowed = true; + }; + options._hook = (internal, index, type) => { if (!internal || !hooksAllowed) { throw new Error('Hook can only be invoked from render methods.'); diff --git a/hooks/src/index.d.ts b/hooks/src/index.d.ts index a33efeafec..8e61b300a6 100644 --- a/hooks/src/index.d.ts +++ b/hooks/src/index.d.ts @@ -15,6 +15,7 @@ export function useState(): [ ]; export type Reducer = (prevState: S, action: A) => S; +export type Dispatch = (action: A) => void; /** * An alternative to `useState`. * @@ -27,7 +28,7 @@ export type Reducer = (prevState: S, action: A) => S; export function useReducer( reducer: Reducer, initialState: S -): [S, (action: A) => void]; +): [S, Dispatch]; /** * An alternative to `useState`. @@ -43,7 +44,7 @@ export function useReducer( reducer: Reducer, initialArg: I, init: (arg: I) => S -): [S, (action: A) => void]; +): [S, Dispatch]; /** @deprecated Use the `Ref` type instead. */ type PropRef = MutableRef; diff --git a/hooks/src/index.js b/hooks/src/index.js index 177183e4c8..4ea3c9c201 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -54,6 +54,7 @@ options._render = internal => { currentInternal.data.__hooks._pendingEffects.forEach(invokeCleanup); currentInternal.data.__hooks._pendingEffects.forEach(invokeEffect); currentInternal.data.__hooks._pendingEffects = []; + currentIndex = 0; } } previousInternal = internal; diff --git a/hooks/test/browser/useEffect.test.js b/hooks/test/browser/useEffect.test.js index 50c510bbb7..808061af20 100644 --- a/hooks/test/browser/useEffect.test.js +++ b/hooks/test/browser/useEffect.test.js @@ -1,4 +1,4 @@ -import { act } from 'preact/test-utils'; +import { act, teardown as teardownAct } from 'preact/test-utils'; import { createElement, render, Fragment, Component } from 'preact'; import { useEffect, useState, useRef } from 'preact/hooks'; import { setupScratch, teardown } from '../../../test/_util/helpers'; @@ -371,6 +371,51 @@ describe('useEffect', () => { ); }); + it('hooks should be called in right order', async () => { + teardownAct(); + + let increment; + + const Counter = () => { + const [count, setCount] = useState(0); + useState('binggo!!'); + const renderRoot = useRef(); + useEffect(() => { + const div = renderRoot.current; + render(, div); + }, [count]); + + increment = () => { + setCount(x => x + 1); + return Promise.resolve().then(() => setCount(x => x + 1)); + }; + + return ( +

+
Count: {count}
+
+
+ ); + }; + + const Dummy = () => { + useState(); + return
dummy
; + }; + + render(, scratch); + + expect(scratch.innerHTML).to.equal( + '
Count: 0
' + ); + /** Using the act function will affect the timing of the useEffect */ + await increment(); + + expect(scratch.innerHTML).to.equal( + '
Count: 2
dummy
' + ); + }); + it('handles errors correctly', () => { class ErrorBoundary extends Component { constructor(props) { diff --git a/src/jsx.d.ts b/src/jsx.d.ts index 712c327fa0..e03eca627c 100644 --- a/src/jsx.d.ts +++ b/src/jsx.d.ts @@ -1072,6 +1072,9 @@ export namespace JSXInternal { WheelEvent >; + export type TargetedPictureInPictureEvent = + TargetedEvent; + export interface EventHandler { (this: void, event: E): void; } @@ -1114,6 +1117,8 @@ export namespace JSXInternal { export type WheelEventHandler = EventHandler< TargetedWheelEvent >; + export type PictureInPictureEventHandler = + EventHandler>; export interface DOMAttributes extends PreactDOMAttributes { @@ -1142,6 +1147,10 @@ export namespace JSXInternal { // Details Events onToggle?: GenericEventHandler | undefined; + // Dialog Events + onClose?: GenericEventHandler | undefined; + onCancel?: GenericEventHandler | undefined; + // Focus Events onFocus?: FocusEventHandler | undefined; onFocusCapture?: FocusEventHandler | undefined; @@ -1315,8 +1324,22 @@ export namespace JSXInternal { onAnimationIterationCapture?: AnimationEventHandler | undefined; // Transition Events + onTransitionCancel?: TransitionEventHandler; + onTransitionCancelCapture?: TransitionEventHandler; onTransitionEnd?: TransitionEventHandler; onTransitionEndCapture?: TransitionEventHandler; + onTransitionRun?: TransitionEventHandler; + onTransitionRunCapture?: TransitionEventHandler; + onTransitionStart?: TransitionEventHandler; + onTransitionStartCapture?: TransitionEventHandler; + + // PictureInPicture Events + onEnterPictureInPicture?: PictureInPictureEventHandler; + onEnterPictureInPictureCapture?: PictureInPictureEventHandler; + onLeavePictureInPicture?: PictureInPictureEventHandler; + onLeavePictureInPictureCapture?: PictureInPictureEventHandler; + onResize?: PictureInPictureEventHandler; + onResizeCapture?: PictureInPictureEventHandler; } // All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/ @@ -1332,6 +1355,16 @@ export namespace JSXInternal { 'aria-autocomplete'?: Signalish< 'none' | 'inline' | 'list' | 'both' | undefined >; + /** + * Defines a string value that labels the current element, which is intended to be converted into Braille. + * @see aria-label. + */ + 'aria-braillelabel'?: Signalish; + /** + * Defines a human-readable, author-localized abbreviated description for the role of an element, which is intended to be converted into Braille. + * @see aria-roledescription. + */ + 'aria-brailleroledescription'?: Signalish; /** Indicates an element is being modified and that assistive technologies MAY want to wait until the modifications are complete before exposing them to the user. */ 'aria-busy'?: Signalish; /**