Skip to content

Commit

Permalink
Wrap reducer wrapper in React.useRef to avoid multiple wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
jihchi committed Dec 30, 2019
1 parent 22a85ee commit 75b57ad
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 97 deletions.
44 changes: 44 additions & 0 deletions __tests__/ReludeReact_test.re
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
open Jest;
open Expect;
open ReactTestingLibrary;

module Counter = {
type state = int;

let initialState = 0;

type action =
| Inc;

let reducer: ReludeReact.Reducer.reducer(action, state) =
(state: state, action: action) => {
switch (action) {
| Inc =>
Js.log("Inc");
Update(state + 1);
};
};

[@react.component]
let make = () => {
let (state, send) =
ReludeReact_Reducer.useReducer(reducer, initialState);

<div>
{React.string("Count: " ++ string_of_int(state))}
<button type_="button" onClick={_ => send(Inc)}>
{React.string("Increment")}
</button>
</div>;
};
};

describe("ReludeReact", () => {
test("one", () =>
Expand All @@ -12,4 +44,16 @@ describe("ReludeReact", () => {
test("three", () => {
expect(ReludeReact_Render.ifTrue(<br />, false)) |> toEqual(React.null)
});

test("Reducer should be called once", () => {
let wrapper = render(<Counter />);

let _ = [%raw {|jest.spyOn(console, 'log').mockImplementation(() => {})|}];
let _ = [%raw {|expect(console.log).toHaveBeenCalledTimes(0)|}];

wrapper |> getByText(~matcher=`Str("Increment")) |> FireEvent.click;

let _ = [%raw {|expect(console.log).toHaveBeenCalledTimes(1)|}];
pass;
});
});
1 change: 1 addition & 0 deletions bsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"bs-dev-dependencies": [
"@glennsl/bs-jest",
"bs-fetch",
"bs-react-testing-library",
"relude-fetch"
],
"warnings": {
Expand Down
70 changes: 70 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"bs-abstract": "~0.18.0",
"bs-fetch": "~0.5.0",
"bs-platform": "~7.0.1",
"bs-react-testing-library": "^0.6.0",
"bsb-js": "~1.1.7",
"parcel-bundler": "~1.12.4",
"reason-react": "~0.7.0",
Expand Down
197 changes: 100 additions & 97 deletions src/ReludeReact_Reducer.re
Original file line number Diff line number Diff line change
Expand Up @@ -49,111 +49,114 @@ type stateAndSideEffects('action, 'state) = {
};

let useReducer = (reducer: reducer('action, 'state), initialState: 'state) => {
let ({state, sideEffects}, send) =
React.useReducer(
({state, sideEffects} as stateAndSideEffects, action) => {
let update = reducer(state, action);

switch (update) {
| NoUpdate => stateAndSideEffects

| Update(state) => {...stateAndSideEffects, state}

| UpdateWithSideEffect(state, sideEffect) => {
state,
sideEffects:
ref(
Belt.Array.concat(
sideEffects^,
[|SideEffect.Uncancelable.lift(sideEffect)|],
),
let refReducer =
React.useRef(({state, sideEffects} as stateAndSideEffects, action) => {
let update = reducer(state, action);

switch (update) {
| NoUpdate => stateAndSideEffects

| Update(state) => {...stateAndSideEffects, state}

| UpdateWithSideEffect(state, sideEffect) => {
state,
sideEffects:
ref(
Belt.Array.concat(
sideEffects^,
[|SideEffect.Uncancelable.lift(sideEffect)|],
),
}

| UpdateWithCancelableSideEffect(state, cancelableSideEffect) => {
state,
sideEffects:
ref(
Belt.Array.concat(
sideEffects^,
[|SideEffect.Cancelable.lift(cancelableSideEffect)|],
),
),
}

| UpdateWithCancelableSideEffect(state, cancelableSideEffect) => {
state,
sideEffects:
ref(
Belt.Array.concat(
sideEffects^,
[|SideEffect.Cancelable.lift(cancelableSideEffect)|],
),
}

| SideEffect(uncancelableSideEffect) => {
...stateAndSideEffects,
sideEffects:
ref(
Belt.Array.concat(
stateAndSideEffects.sideEffects^,
[|SideEffect.Uncancelable.lift(uncancelableSideEffect)|],
),
),
}

| SideEffect(uncancelableSideEffect) => {
...stateAndSideEffects,
sideEffects:
ref(
Belt.Array.concat(
stateAndSideEffects.sideEffects^,
[|SideEffect.Uncancelable.lift(uncancelableSideEffect)|],
),
}

| CancelableSideEffect(cancelableSideEffect) => {
...stateAndSideEffects,
sideEffects:
ref(
Belt.Array.concat(
stateAndSideEffects.sideEffects^,
[|SideEffect.Cancelable.lift(cancelableSideEffect)|],
),
),
}

| CancelableSideEffect(cancelableSideEffect) => {
...stateAndSideEffects,
sideEffects:
ref(
Belt.Array.concat(
stateAndSideEffects.sideEffects^,
[|SideEffect.Cancelable.lift(cancelableSideEffect)|],
),
),
}

| UpdateWithIO(state, ioAction) =>
// The IO must have an 'action type for both the success and error channels - this
// way we know that the errors have been properly handled and translated to the appropriate action.
// Run the IO to get the success and error actions, then just send them.
// TODO: we don't have cancelable IOs (yet?)
let sideEffect: SideEffect.t('action, 'state) = (
context => {
ioAction
|> Relude.IO.unsafeRunAsync(
fun
| Ok(action) => context.send(action)
| Error(action) => context.send(action),
);
None;
}

| UpdateWithIO(state, ioAction) =>
// The IO must have an 'action type for both the success and error channels - this
// way we know that the errors have been properly handled and translated to the appropriate action.
// Run the IO to get the success and error actions, then just send them.
// TODO: we don't have cancelable IOs (yet?)
let sideEffect: SideEffect.t('action, 'state) = (
context => {
ioAction
|> Relude.IO.unsafeRunAsync(
fun
| Ok(action) => context.send(action)
| Error(action) => context.send(action),
);
None;
}
);
{
state,
sideEffects:
ref(
Belt.Array.concat(
stateAndSideEffects.sideEffects^,
[|sideEffect|],
),
);
{
state,
sideEffects:
ref(
Belt.Array.concat(
stateAndSideEffects.sideEffects^,
[|sideEffect|],
),
};

| IO(ioAction) =>
let sideEffect: SideEffect.t('action, 'state) = (
context => {
ioAction
|> Relude.IO.unsafeRunAsync(
fun
| Ok(action) => context.send(action)
| Error(action) => context.send(action),
);
None;
}
);
{
...stateAndSideEffects,
sideEffects:
ref(
Belt.Array.concat(
stateAndSideEffects.sideEffects^,
[|sideEffect|],
),
),
};

| IO(ioAction) =>
let sideEffect: SideEffect.t('action, 'state) = (
context => {
ioAction
|> Relude.IO.unsafeRunAsync(
fun
| Ok(action) => context.send(action)
| Error(action) => context.send(action),
);
None;
}
);
{
...stateAndSideEffects,
sideEffects:
ref(
Belt.Array.concat(
stateAndSideEffects.sideEffects^,
[|sideEffect|],
),
};
),
};
},
};
});

let ({state, sideEffects}, send) =
React.useReducer(
refReducer |> React.Ref.current,
{state: initialState, sideEffects: ref([||])},
);

Expand Down

0 comments on commit 75b57ad

Please sign in to comment.