Skip to content

Instantly share code, notes, and snippets.

@alexeyraspopov
Last active April 3, 2022 23:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexeyraspopov/800c423dc281509f4e503f15cfe1dded to your computer and use it in GitHub Desktop.
Save alexeyraspopov/800c423dc281509f4e503f15cfe1dded to your computer and use it in GitHub Desktop.
import { useCallback, useMemo, useEffect, useSyncExternalStore } from "react";
import { BehaviorSubject, from, firstValueFrom } from "rxjs";
export function useSubjectQuery(query, deps) {
let subject$ = useMemo(() => new BehaviorSubject(), []);
useEffect(() => {
let subscription = from(query()).subscribe({
next: (value) => subject$.next(value),
error: (error) => subject$.error(error),
});
return () => subscription.unsubscribe();
}, deps);
return subject$;
}
export function useSubjectValue(subject$) {
let subscribe = useCallback(
(cb) => {
let subscription = subject$.subscribe({ next: cb, error: cb });
return () => subscription.unsubscribe();
},
[subject$],
);
let value = useSyncExternalStore(subscribe, () => subject$.value);
if (value === undefined) throw firstValueFrom(subject$);
return value;
}
import "@testing-library/jest-dom";
import { useSubjectQuery, useSubjectValue } from "./SubjectHooks";
import { act, render, screen } from "@testing-library/react";
import { Suspense, Component, memo } from "react";
beforeAll(() => {
window.addEventListener("error", (event) => event.preventDefault());
});
class ErrorBoundary extends Component {
state = { error: null };
static getDerivedStateFromError(error) {
return { error };
}
render() {
return this.state.error != null ? this.props.fallback : this.props.children;
}
}
test("test", async () => {
function Parent({ promise }) {
let subject$ = useSubjectQuery(() => promise, [promise]);
return (
<ErrorBoundary fallback={<p role="error">error</p>}>
<Suspense fallback={<p role="loading">loading</p>}>
<Child subject$={subject$} />
</Suspense>
</ErrorBoundary>
);
}
let Child = memo(function Child({ subject$ }) {
let value = useSubjectValue(subject$);
return <p role="value">{value}</p>;
});
let { rerender } = render(<Parent promise={Promise.resolve(13)} />);
expect(screen.getByRole("loading")).toHaveTextContent("loading");
await act(() => Promise.resolve());
expect(screen.getByRole("value")).toHaveTextContent(13);
rerender(<Parent promise={Promise.resolve(26)} />);
expect(screen.getByRole("value")).toHaveTextContent(13);
await act(() => Promise.resolve());
expect(screen.getByRole("value")).toHaveTextContent(26);
rerender(<Parent promise={Promise.reject(new Error("boo"))} />);
expect(screen.getByRole("value")).toHaveTextContent(26);
await act(() => Promise.resolve());
expect(screen.getByRole("error")).toHaveTextContent("error");
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment