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

rect: Fix issues with the observer between renders #622

Merged
merged 3 commits into from Jun 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 15 additions & 16 deletions packages/alert/src/index.tsx
Expand Up @@ -61,7 +61,7 @@ let renderTimer: number | null;
* @see Docs https://reacttraining.com/reach-ui/alert
*/
export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
{ children, type = "polite", ...props },
{ children, type: regionType = "polite", ...props },
forwardedRef
) {
const ownRef = useRef(null);
Expand All @@ -75,7 +75,7 @@ export const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
// eslint-disable-next-line react-hooks/exhaustive-deps
[children, props]
);
useMirrorEffects(type, child, ownRef);
useMirrorEffects(regionType, child, ownRef);

return child;
});
Expand Down Expand Up @@ -141,8 +141,8 @@ function renderAlerts() {
}
renderTimer = window.setTimeout(() => {
Object.keys(elements).forEach((elementType) => {
let type: RegionTypes = elementType as RegionTypes;
let container = liveRegions[type]!;
let regionType: RegionTypes = elementType as RegionTypes;
let container = liveRegions[regionType]!;
if (container) {
render(
<VisuallyHidden>
Expand All @@ -154,47 +154,46 @@ function renderAlerts() {
// will send out an accessible status event to assistive
// technology products which can then notify the user about it.
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_status_role
role={type === "assertive" ? "alert" : "status"}
aria-live={type}
role={regionType === "assertive" ? "alert" : "status"}
aria-live={regionType}
>
{Object.keys(elements[type]).map((key) =>
React.cloneElement(elements[type][key], {
{Object.keys(elements[regionType]).map((key) =>
React.cloneElement(elements[regionType][key], {
key,
ref: null,
})
)}
</div>
</VisuallyHidden>,
liveRegions[type]
liveRegions[regionType]
);
}
});
}, 500);
}

function useMirrorEffects(
type: RegionTypes,
regionType: RegionTypes,
element: JSX.Element,
ref: React.RefObject<any>
) {
const prevType = usePrevious<RegionTypes>(type);
const prevType = usePrevious<RegionTypes>(regionType);
const mirror = useRef<Mirror | null>(null);
const mounted = useRef(false);
useEffect(() => {
const ownerDocument = getOwnerDocument(ref.current) || document;
if (!mounted.current) {
mounted.current = true;
mirror.current = createMirror(type, ownerDocument);
mirror.current = createMirror(regionType, ownerDocument);
mirror.current.mount(element);
} else if (prevType !== type) {
} else if (prevType !== regionType) {
mirror.current && mirror.current.unmount();
mirror.current = createMirror(type, ownerDocument);
mirror.current = createMirror(regionType, ownerDocument);
mirror.current.mount(element);
} else {
mirror.current && mirror.current.update(element);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [element, type, prevType]);
}, [element, regionType, prevType, ref]);

useEffect(() => {
return () => {
Expand Down
29 changes: 29 additions & 0 deletions packages/rect/examples/change-observed-ref.example.tsx
@@ -0,0 +1,29 @@
import React from "react";
import { useRect } from "@reach/rect";

let name = "Change the observed ref";

function Example() {
const refLeft = React.useRef<HTMLTextAreaElement>(null);
const refRight = React.useRef<HTMLTextAreaElement>(null);
const [whichRect, setWhichRect] = React.useState(true);
const rect = useRect(whichRect ? refLeft : refRight);
return (
<div>
<pre>
{whichRect ? "left" : "right"}: {JSON.stringify(rect, null, 2)}
</pre>
<button onClick={() => setWhichRect(!whichRect)}>
Toggle Observed Ref
</button>
<div>
<textarea ref={refLeft} defaultValue="resize this" />
<textarea ref={refRight} defaultValue="resize this" />
</div>
</div>
);
}

Example.story = { name };
export const Comp = Example;
export default { title: "Rect" };
2 changes: 1 addition & 1 deletion packages/rect/package.json
Expand Up @@ -13,7 +13,7 @@
"build": "ts-node ../../scripts/build"
},
"dependencies": {
"@reach/observe-rect": "1.1.0",
"@reach/observe-rect": "1.2.0",
"@reach/utils": "0.10.4",
"prop-types": "^15.7.2",
"tslib": "^2.0.0"
Expand Down
59 changes: 36 additions & 23 deletions packages/rect/src/index.tsx
Expand Up @@ -65,7 +65,10 @@ export type RectProps = {
*
* @see Docs https://reacttraining.com/reach-ui/rect#rect-onchange
*/
children(args: { rect: PRect | null; ref: React.Ref<any> }): JSX.Element;
children(args: {
rect: PRect | null;
ref: React.RefObject<any>;
}): JSX.Element;
};

if (__DEV__) {
Expand All @@ -86,48 +89,58 @@ if (__DEV__) {
* @param observe
* @param onChange
*/
export function useRect<T extends HTMLElement = HTMLElement>(
nodeRef: React.RefObject<T>,
export function useRect<T extends Element = HTMLElement>(
nodeRef: React.RefObject<T | undefined | null>,
observe: boolean = true,
onChange?: (rect: DOMRect) => void
): null | DOMRect {
let [element, setElement] = useState(nodeRef.current);
let initialRectSet = useRef(false);
let [rect, setRect] = useState<DOMRect | null>(null);
let observerRef = useRef<any>(null);
let onChangeRef = useRef<typeof onChange>();

useIsomorphicLayoutEffect(() => {
const cleanup = () => {
observerRef.current && observerRef.current.unobserve();
};
onChangeRef.current = onChange;
});

if (!nodeRef.current) {
console.warn("You need to place the ref");
return cleanup;
useIsomorphicLayoutEffect(() => {
if (nodeRef.current !== element) {
setElement(nodeRef.current);
}
});

if (!observerRef.current) {
observerRef.current = observeRect(nodeRef.current, (rect: DOMRect) => {
onChange && onChange(rect);
setRect(rect);
});
useIsomorphicLayoutEffect(() => {
if (element && !initialRectSet.current) {
initialRectSet.current = true;
setRect(element.getBoundingClientRect());
}
}, [element]);

if (!initialRectSet.current) {
initialRectSet.current = true;
setRect(nodeRef.current.getBoundingClientRect());
useIsomorphicLayoutEffect(() => {
let observer: ReturnType<typeof observeRect>;
if (!element) {
console.warn("You need to place the ref");
return cleanup;
}

observe && observerRef.current.observe();
observer = observeRect(element, (rect) => {
onChangeRef.current && onChangeRef.current(rect);
setRect(rect);
});

observe && observer.observe();
return cleanup;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [observe, onChange]);

function cleanup() {
observer && observer.unobserve();
}
}, [observe, element]);

return rect;
}

export default Rect;

export type PartialRect = Partial<PRect>;

export type PRect = Partial<DOMRect> & {
readonly bottom: number;
readonly height: number;
Expand Down
8 changes: 4 additions & 4 deletions packages/utils/src/index.tsx
Expand Up @@ -157,10 +157,10 @@ export function boolOrBoolString(value: any): value is "true" | true {
}

export function canUseDOM() {
return (
return !!(
typeof window !== "undefined" &&
typeof window.document !== "undefined" &&
typeof window.document.createElement !== "undefined"
window.document &&
window.document.createElement
);
}

Expand Down Expand Up @@ -615,7 +615,7 @@ export function useForkedRef<RefValueType = any>(
});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, refs);
}, [...refs]);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Expand Up @@ -3112,10 +3112,10 @@
dependencies:
"@types/node" ">= 8"

"@reach/observe-rect@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.1.0.tgz#4e967a93852b6004c3895d9ed8d4e5b41895afde"
integrity sha512-kE+jvoj/OyJV24C03VvLt5zclb9ArJi04wWXMMFwQvdZjdHoBlN4g0ZQFjyy/ejPF1Z/dpUD5dhRdBiUmIGZTA==
"@reach/observe-rect@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==

"@reach/router@^1.2.1", "@reach/router@^1.3.3":
version "1.3.3"
Expand Down