Skip to content

Commit

Permalink
improve the fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Jan 9, 2025
1 parent 9efe706 commit 59b9c85
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 30 deletions.
15 changes: 15 additions & 0 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,20 @@ const WithCustomFont = () => {
};

const WithFormReset = () => {
const ref = React.useRef<HTMLFormElement>(null);
return (
<div>
<h2>{'Resettable form.'}</h2>
<div>{'Resizes once the form gets reset.'}</div>
<form ref={ref}>
<TextareaAutosize />
<input type="reset" />
</form>
</div>
);
};

const WithManualFormReset = () => {
const ref = React.useRef<HTMLFormElement>(null);
return (
<div>
Expand Down Expand Up @@ -232,6 +246,7 @@ const Demo = () => {
<MultipleTextareas />
<WithCustomFont />
<WithFormReset />
<WithManualFormReset />
</div>
);
};
Expand Down
27 changes: 12 additions & 15 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,34 @@ type InferEvent<
: Event;

function useListener<
TTarget extends EventTarget | null | undefined,
TType extends InferEventType<NonNullable<TTarget>>,
TTarget extends EventTarget,
TType extends InferEventType<TTarget>,
>(
target: (() => TTarget) | TTarget,
target: TTarget,
type: TType,
listener: (event: InferEvent<NonNullable<TTarget>, TType>) => void,
listener: (event: InferEvent<TTarget, TType>) => void,
) {
const latestListener = useLatest(listener);
React.useLayoutEffect(() => {
const handler: typeof listener = (ev) => latestListener.current(ev);
const resolvedTarget = typeof target === 'function' ? target() : target;

// might happen if document.fonts is not defined, for instance
if (!resolvedTarget) {
if (!target) {
return;
}
resolvedTarget.addEventListener(type, handler);
return () => resolvedTarget.removeEventListener(type, handler);
target.addEventListener(type, handler);
return () => target.removeEventListener(type, handler);
}, []);
}

export const useFormResetListener = (
libRef: React.MutableRefObject<HTMLTextAreaElement | null>,
listener: (event: Event) => any,
) => {
useListener(() => libRef.current?.form, 'reset', listener);
useListener(document.body, 'reset', (ev) => {
if (libRef.current?.form === ev.target) {
listener(ev);
}
});
};

export const useWindowResizeListener = (listener: (event: UIEvent) => any) => {
Expand All @@ -60,8 +62,3 @@ export const useWindowResizeListener = (listener: (event: UIEvent) => any) => {
export const useFontsLoadedListener = (listener: (event: Event) => any) => {
useListener(document.fonts, 'loadingdone', listener);
};

export const useForceRerender = () => {
const [, setState] = React.useState({});
return React.useCallback(() => setState({}), []);
};
22 changes: 7 additions & 15 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
useWindowResizeListener,
useFontsLoadedListener,
useFormResetListener,
useForceRerender,
} from './hooks';
import { noop } from './utils';

Expand Down Expand Up @@ -99,23 +98,16 @@ const TextareaAutosize: React.ForwardRefRenderFunction<
};

if (isBrowser) {
const forceRerender = useForceRerender();
React.useLayoutEffect(resizeTextarea);
useFormResetListener(libRef, () => {
if (!isControlled) {
// force rerender is used here because form reset doesn't trigger React's onChange:
// https://github.com/facebook/react/issues/19078
//
// the problem with a reset listener is that it's called before the value gets actually changed
// the event itself can, after all, be even .preventDefault()ed
// so given it's not possible to know if the reset will actually happen, we "schedule" a rerender so our resizing layout effect can take care of it
//
// this doesn't work with <input type="reset" /> though
// updates scheduled by reset handlers called called by those happen synchronously~
// React is eager to rerender this before the reset action actually takes place
//
// it might be a good idea to use a native change listener on the textarea itself to workaround this
forceRerender();
const node = libRef.current!;
const currentValue = node.value;
requestAnimationFrame(() => {
if (currentValue !== node.value) {
resizeTextarea();
}
});
}
});
useWindowResizeListener(resizeTextarea);
Expand Down

0 comments on commit 59b9c85

Please sign in to comment.