Skip to content

Commit

Permalink
fix: enhance once before passing value to context
Browse files Browse the repository at this point in the history
  • Loading branch information
iamogbz committed Sep 21, 2020
1 parent c8faabc commit 6687a3f
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 39 deletions.
6 changes: 3 additions & 3 deletions src/components/Provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ActionTypes } from "../utils/actionTypes";
import { createAction } from "../createAction";
import { useGetter } from "../hooks/useGetter";
import { useObservable } from "../hooks/useObservable";
import { useOnce } from "../hooks/useOnce";

export function Provider<S, T extends string, P>({
children,
Expand Down Expand Up @@ -33,11 +34,10 @@ export function Provider<S, T extends string, P>({

const observable = useObservable(useGetter(value));

React.useEffect(function enhanceAndIntialise() {
useOnce(function enhanceAndIntialise() {
const enhanced = enhancer?.(observable) ?? observable;
enhanced.dispatch(createAction<T, P, S>(ActionTypes.INIT as T)());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
});

return <Context.Provider value={observable}>{children}</Context.Provider>;
}
6 changes: 2 additions & 4 deletions src/hooks/useObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,8 @@ export function useObservable<V>(getter: () => V): V & Observable {
);

const next = React.useCallback(
function next() {
observers.forEach((observer) => {
observer.next(nextValue);
});
function observeNext() {
observers.forEach((observer) => observer.next(nextValue));
},
[observers, nextValue],
);
Expand Down
8 changes: 8 additions & 0 deletions src/hooks/useOnce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as React from "react";

export function useOnce(callback: React.EffectCallback): void {
const isFirstRun = React.useRef(true);
if (!isFirstRun.current) return;
callback();
isFirstRun.current = false;
}
105 changes: 73 additions & 32 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import { createStructuredSelector } from "reselect";
import { act, cleanup, render } from "@testing-library/react";
import { RenderResult, act, cleanup, render } from "@testing-library/react";
import {
GlobalContext,
Provider,
Expand Down Expand Up @@ -315,40 +315,16 @@ describe("integration", () => {
});

describe("contextSubscribe", () => {
const listener = jest.fn();
function Sample() {
const value = React.useContext(GlobalContext);
React.useEffect(
function contextSubscribe() {
value.subscribe(listener);
},
[value],
);
const increment = useDispatch(rootDuck.actions.counter.increment);
const init = useSelector(rootDuck.selectors.init?.get);
return (
<div>
<button disabled={!init} onClick={increment}>
increment
</button>
</div>
);
}

afterEach(listener.mockClear);

it("subscribes successfully to context value changes", async () => {
const result = render(
<RootProvider>
<Sample />
</RootProvider>,
);
expect(listener).toHaveBeenCalledTimes(2);
async function runAssertions(
result: RenderResult,
listener: jest.Mock,
) {
expect(listener).toHaveBeenCalledTimes(1);
const button = await result.findByText("increment");
act(() => button.click());
expect(listener).toHaveBeenCalledTimes(3);
expect(listener).toHaveBeenCalledTimes(2);
act(() => button.click());
expect(listener).toHaveBeenCalledTimes(4);
expect(listener).toHaveBeenCalledTimes(3);
expect(listener).toHaveBeenLastCalledWith(
expect.objectContaining({
dispatch: expect.any(Function),
Expand All @@ -364,6 +340,71 @@ describe("integration", () => {
);
const [[lastCallArg0]] = listener.mock.calls.slice(-1);
expect(lastCallArg0[Symbol.observable]()).toBe(lastCallArg0);
}

it("subscribes successfully to context value changes from component", async () => {
expect.assertions(5);
const listener = jest.fn();
function Sample() {
const value = React.useContext(GlobalContext);
React.useEffect(
function contextSubscribe() {
value.subscribe(listener);
},
[value],
);
const increment = useDispatch(
rootDuck.actions.counter.increment,
);
const init = useSelector(rootDuck.selectors.init?.get);
return (
<div>
<button disabled={!init} onClick={increment}>
increment
</button>
</div>
);
}
const result = render(
<RootProvider>
<Sample />
</RootProvider>,
);
await runAssertions(result, listener);
});

it("subscribes successfully to context value changes from enhancer", async () => {
expect.assertions(5);
const listener = jest.fn();
const enhancer = jest.fn(function enhancer(value) {
value.subscribe(listener);
return value;
});
const Context = createContext(
rootDuck.reducer,
rootDuck.initialState,
enhancer,
);
function Sample() {
const increment = useDispatch(
rootDuck.actions.counter.increment,
Context,
);
const init = useSelector(rootDuck.selectors.init?.get, Context);
return (
<div>
<button disabled={!init} onClick={increment}>
increment
</button>
</div>
);
}
const result = render(
<Provider Context={Context}>
<Sample />
</Provider>,
);
await runAssertions(result, listener);
});
});
});

0 comments on commit 6687a3f

Please sign in to comment.