From 795203b672e4799e49ba2e1a5af3ebd726177f44 Mon Sep 17 00:00:00 2001 From: Amirali Esmaeili Date: Thu, 29 Jul 2021 23:18:45 +0430 Subject: [PATCH 1/9] Update files to rescript syntax --- __tests__/reductiveContext_test.re | 309 ---------------- __tests__/reductiveContext_test.res | 282 +++++++++++++++ bsconfig.json | 1 - examples/basic/basicEntry.re | 48 --- examples/basic/basicEntry.res | 46 +++ .../{complexStore.re => complexStore.res} | 27 +- .../basic/{simpleStore.re => simpleStore.res} | 8 +- .../immutable/{appState.re => appState.res} | 4 +- .../{immutableEntry.re => immutableEntry.res} | 4 +- examples/immutable/immutableRenderer.re | 50 --- examples/immutable/immutableRenderer.res | 50 +++ examples/immutable/timeTravelStore.re | 94 ----- examples/immutable/timeTravelStore.res | 90 +++++ examples/{middleware.re => middleware.res} | 15 +- examples/react/dataRenderer.re | 42 --- examples/react/dataRenderer.res | 43 +++ examples/react/reactEntry.re | 7 - examples/react/reactEntry.res | 5 + examples/react/reduxThunk.re | 4 - examples/react/reduxThunk.res | 4 + examples/react/thunkedStore.re | 43 --- examples/react/thunkedStore.res | 43 +++ .../{renderEntryV2.re => renderEntryV2.res} | 115 +++--- .../{renderEntry.re => renderEntry.res} | 93 +++-- examples/todomvc/todomvcEntry.re | 338 ------------------ examples/todomvc/todomvcEntry.res | 294 +++++++++++++++ package.json | 9 +- src/reductive.re | 164 --------- src/reductive.rei | 163 --------- src/reductive.res | 138 +++++++ src/reductive.resi | 140 ++++++++ src/reductiveContext.re | 63 ---- src/reductiveContext.rei | 34 -- src/reductiveContext.res | 57 +++ src/reductiveContext.resi | 23 ++ src/subscription.re | 13 - src/subscription.res | 13 + yarn.lock | 21 +- 38 files changed, 1359 insertions(+), 1538 deletions(-) delete mode 100644 __tests__/reductiveContext_test.re create mode 100644 __tests__/reductiveContext_test.res delete mode 100644 examples/basic/basicEntry.re create mode 100644 examples/basic/basicEntry.res rename examples/basic/{complexStore.re => complexStore.res} (64%) rename examples/basic/{simpleStore.re => simpleStore.res} (89%) rename examples/immutable/{appState.re => appState.res} (57%) rename examples/immutable/{immutableEntry.re => immutableEntry.res} (86%) delete mode 100644 examples/immutable/immutableRenderer.re create mode 100644 examples/immutable/immutableRenderer.res delete mode 100644 examples/immutable/timeTravelStore.re create mode 100644 examples/immutable/timeTravelStore.res rename examples/{middleware.re => middleware.res} (85%) delete mode 100644 examples/react/dataRenderer.re create mode 100644 examples/react/dataRenderer.res delete mode 100644 examples/react/reactEntry.re create mode 100644 examples/react/reactEntry.res delete mode 100644 examples/react/reduxThunk.re create mode 100644 examples/react/reduxThunk.res delete mode 100644 examples/react/thunkedStore.re create mode 100644 examples/react/thunkedStore.res rename examples/render-v2/{renderEntryV2.re => renderEntryV2.res} (52%) rename examples/render/{renderEntry.re => renderEntry.res} (55%) delete mode 100644 examples/todomvc/todomvcEntry.re create mode 100644 examples/todomvc/todomvcEntry.res delete mode 100644 src/reductive.re delete mode 100644 src/reductive.rei create mode 100644 src/reductive.res create mode 100644 src/reductive.resi delete mode 100644 src/reductiveContext.re delete mode 100644 src/reductiveContext.rei create mode 100644 src/reductiveContext.res create mode 100644 src/reductiveContext.resi delete mode 100644 src/subscription.re create mode 100644 src/subscription.res diff --git a/__tests__/reductiveContext_test.re b/__tests__/reductiveContext_test.re deleted file mode 100644 index 6756bbb..0000000 --- a/__tests__/reductiveContext_test.re +++ /dev/null @@ -1,309 +0,0 @@ -[@bs.config {jsx: 3}]; -open Jest; -open Expect; -open ReasonHooksTestingLibrary; - -module TestStoreContext = { - type testState = { - counter: int, - content: string, - }; - - type testAction = - | Increment - | Decrement - | AppendA - | AppendB - | Reset; - - let testReducer = (state, action) => - switch (action) { - | Increment => {...state, counter: state.counter + 1} - | Decrement => {...state, counter: state.counter - 1} - | AppendA => {...state, content: state.content ++ "A"} - | AppendB => {...state, content: state.content ++ "B"} - | Reset => {counter: 0, content: ""} - }; - - let appStore = - Reductive.Store.create( - ~reducer=testReducer, - ~preloadedState={counter: 0, content: ""}, - (), - ); - - include ReductiveContext.Make({ - type action = testAction; - type state = testState; - }); -}; - -module App = { - [@react.component] - let make = (~children) => { - - children - ; - }; -}; - -type selectorMockTest = {. "selector": TestStoreContext.testState => int}; -type spyReturnMockValue = string; - -let getResult = container => - container->Testing.Result.result->Testing.Result.current; - -describe("reductiveContext", () => { - open Testing; - open ReactTestingLibrary; - - let options = Options.t(~wrapper=App.make, ()); - beforeEach(() => - Reductive.Store.dispatch(TestStoreContext.appStore, Reset) - ); - - describe("useSelector", () => { - test("sets initial state correctly", () => { - let selector = (state: TestStoreContext.testState) => state.counter; - - let container = - renderHook( - () => TestStoreContext.useSelector(selector), - ~options, - (), - ); - - expect(getResult(container)) |> toEqual(0); - }); - - test("selects the state and re-renders component on store updates", () => { - let selector = (state: TestStoreContext.testState) => state.counter; - - let container = - renderHook( - () => TestStoreContext.useSelector(selector), - ~options, - (), - ); - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - (); - }); - - expect(getResult(container)) |> toEqual(1); - }); - - test("always gives back the latest state", () => { - let selector = (state: TestStoreContext.testState) => state.counter; - let renderedStates: ref(array(int)) = ref([||]); - - module Comp = { - [@react.component] - let make = () => { - let counter = TestStoreContext.useSelector(selector); - renderedStates := Belt.Array.concat(renderedStates^, [|counter|]); -
; - }; - }; - - let element = ; - render(element) |> ignore; - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - Reductive.Store.dispatch(TestStoreContext.appStore, Decrement); - (); - }); - - expect(renderedStates^) |> toEqual([|0, 1, 0|]); - }); - - test("prevents re-render if selected state is referentially equal", () => { - let renderedStates: ref(array(int)) = ref([||]); - let selector = (state: TestStoreContext.testState) => state.counter; - - module Comp = { - [@react.component] - let make = () => { - let counter = TestStoreContext.useSelector(selector); - renderedStates := Belt.Array.concat(renderedStates^, [|counter|]); -
; - }; - }; - - let element = ; - render(element) |> ignore; - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, AppendA); - Reductive.Store.dispatch(TestStoreContext.appStore, AppendB); - (); - }); - - expect(renderedStates^) |> toEqual([|0|]); - }); - - test("correctly updates selected state if selector depends on props", () => { - let renderedStates: ref(array(int)) = ref([||]); - - module Comp = { - [@react.component] - let make = (~prop) => { - let selector = - React.useCallback1( - (s: TestStoreContext.testState) => s.counter + prop, - [|prop|], - ); - - let counter = TestStoreContext.useSelector(selector); - - renderedStates := Belt.Array.concat(renderedStates^, [|counter|]); -
; - }; - }; - - let updateProp = ref(() => ()); - module Parent = { - [@react.component] - let make = () => { - let (prop, dispatch) = React.useReducer((s, _) => s + 1, 0); - updateProp := dispatch; - ; - }; - }; - let element = ; - - render(element) |> ignore; - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); // state.counter - 1, prop - 0, total - 1 - updateProp^(); // state.counter - 1, prop - 1, total - 2 - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); // state.counter - 2, prop - 1, total - 3 - updateProp^(); // state.counter - 2, prop - 2, total - 4 - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); // state.counter - 3, prop - 2, total - 5 - (); - }); - - // changing selector function that depends on props leads to double re-render and duplicated state values - let distinctRenderState = - (renderedStates^) - ->Belt.Array.keepWithIndex((value, index) => - (renderedStates^) - ->Belt.Array.getIndexBy(v => v === value) - ->Belt.Option.getWithDefault(-1) - === index - ); - expect(distinctRenderState) |> toEqual([|0, 1, 2, 3, 4, 5|]); - }); - - test("removes subscription on unmount", () => { - let setupSpy: selectorMockTest => spyReturnMockValue = [%bs.raw - {| - function (obj) { - return jest.spyOn(obj, "selector"); - } - |} - ]; - - let selector = (state: TestStoreContext.testState) => state.counter; - let selectorSpyObject = ref({"selector": selector}); - - let selectStateFromSpy = state => { - let spySelector = (selectorSpyObject^)##selector; - spySelector(state); - }; - - module Comp = { - [@react.component] - let make = () => { - let _ = TestStoreContext.useSelector(selectStateFromSpy); -
; - }; - }; - - let element = ; - render(element) |> unmount() |> ignore; - - // start spying after unmount - let spy = setupSpy(selectorSpyObject^); - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - Reductive.Store.dispatch(TestStoreContext.appStore, Decrement); - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - (); - }); - - let expectSelectorToHaveBeenCalled: spyReturnMockValue => unit = [%bs.raw - {| - function (spy) { - console.log(spy) - return expect(spy).toHaveBeenCalledTimes(0) - } - |} - ]; - - expectSelectorToHaveBeenCalled(spy); - expect(true) |> toEqual(true); - }); - }); - - describe("useStore", () => { - test("gets the store", () => { - let container = - renderHook(() => TestStoreContext.useStore(), ~options, ()); - - expect(getResult(container)) |> toEqual(TestStoreContext.appStore); - }); - - test("re-renders component on store updates", () => { - let container = - renderHook(() => TestStoreContext.useStore(), ~options, ()); - - act(() => { - Reductive.Store.dispatch(TestStoreContext.appStore, Increment); - (); - }); - - let state = container->getResult->Reductive.Store.getState; - expect(state.counter) |> toEqual(1); - }); - }); - - describe("useDispatch", () => - test("has stable reference", () => { - let dispatchRerenders: ref(int) = ref(0); - let forceRender = ref(() => ()); - - module Comp = { - [@react.component] - let make = () => { - let (_, setState) = React.useState(() => 0); - forceRender := (() => setState(prev => prev + 1)); - - let dispatch = TestStoreContext.useDispatch(); - React.useEffect1( - () => { - dispatchRerenders := dispatchRerenders^ + 1; - None; - }, - [|dispatch|], - ); -
; - }; - }; - - let element = ; - render(element) |> ignore; - - act(() => { - forceRender^(); - forceRender^(); - forceRender^(); - (); - }); - expect(dispatchRerenders^) |> toEqual(1); - }) - ); -}); diff --git a/__tests__/reductiveContext_test.res b/__tests__/reductiveContext_test.res new file mode 100644 index 0000000..a863a68 --- /dev/null +++ b/__tests__/reductiveContext_test.res @@ -0,0 +1,282 @@ +@@bs.config({jsx: 3}) +open Jest +open Expect +open ReasonHooksTestingLibrary + +module TestStoreContext = { + type testState = { + counter: int, + content: string, + } + + type testAction = + | Increment + | Decrement + | AppendA + | AppendB + | Reset + + let testReducer = (state, action) => + switch action { + | Increment => {...state, counter: state.counter + 1} + | Decrement => {...state, counter: state.counter - 1} + | AppendA => {...state, content: state.content ++ "A"} + | AppendB => {...state, content: state.content ++ "B"} + | Reset => {counter: 0, content: ""} + } + + let appStore = Reductive.Store.create( + ~reducer=testReducer, + ~preloadedState={counter: 0, content: ""}, + (), + ) + + include ReductiveContext.Make({ + type action = testAction + type state = testState + }) +} + +module App = { + @react.component + let make = (~children) => + + children + +} + +type selectorMockTest = {"selector": TestStoreContext.testState => int} +type spyReturnMockValue = string + +let getResult = container => container->Testing.Result.result->Testing.Result.current + +describe("reductiveContext", () => { + open Testing + open ReactTestingLibrary + + let options = Options.t(~wrapper=App.make, ()) + beforeEach(() => Reductive.Store.dispatch(TestStoreContext.appStore, Reset)) + + describe("useSelector", () => { + test("sets initial state correctly", () => { + let selector = (state: TestStoreContext.testState) => state.counter + + let container = renderHook(() => TestStoreContext.useSelector(selector), ~options, ()) + + expect(getResult(container)) |> toEqual(0) + }) + + test("selects the state and re-renders component on store updates", () => { + let selector = (state: TestStoreContext.testState) => state.counter + + let container = renderHook(() => TestStoreContext.useSelector(selector), ~options, ()) + + act(() => { + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) + () + }) + + expect(getResult(container)) |> toEqual(1) + }) + + test("always gives back the latest state", () => { + let selector = (state: TestStoreContext.testState) => state.counter + let renderedStates: ref> = ref([]) + + module Comp = { + @react.component + let make = () => { + let counter = TestStoreContext.useSelector(selector) + renderedStates := Belt.Array.concat(renderedStates.contents, [counter]) +
+ } + } + + let element = + render(element) |> ignore + + act(() => { + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) + Reductive.Store.dispatch(TestStoreContext.appStore, Decrement) + () + }) + + expect(renderedStates.contents) |> toEqual([0, 1, 0]) + }) + + test("prevents re-render if selected state is referentially equal", () => { + let renderedStates: ref> = ref([]) + let selector = (state: TestStoreContext.testState) => state.counter + + module Comp = { + @react.component + let make = () => { + let counter = TestStoreContext.useSelector(selector) + renderedStates := Belt.Array.concat(renderedStates.contents, [counter]) +
+ } + } + + let element = + render(element) |> ignore + + act(() => { + Reductive.Store.dispatch(TestStoreContext.appStore, AppendA) + Reductive.Store.dispatch(TestStoreContext.appStore, AppendB) + () + }) + + expect(renderedStates.contents) |> toEqual([0]) + }) + + test("correctly updates selected state if selector depends on props", () => { + let renderedStates: ref> = ref([]) + + module Comp = { + @react.component + let make = (~prop) => { + let selector = React.useCallback1( + (s: TestStoreContext.testState) => s.counter + prop, + [prop], + ) + + let counter = TestStoreContext.useSelector(selector) + + renderedStates := Belt.Array.concat(renderedStates.contents, [counter]) +
+ } + } + + let updateProp = ref(() => ()) + module Parent = { + @react.component + let make = () => { + let (prop, dispatch) = React.useReducer((s, _) => s + 1, 0) + updateProp := dispatch + + } + } + let element = + + render(element) |> ignore + + act(() => { + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) // state.counter - 1, prop - 0, total - 1 + updateProp.contents() // state.counter - 1, prop - 1, total - 2 + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) // state.counter - 2, prop - 1, total - 3 + updateProp.contents() // state.counter - 2, prop - 2, total - 4 + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) // state.counter - 3, prop - 2, total - 5 + () + }) + + // changing selector function that depends on props leads to double re-render and duplicated state values + let distinctRenderState = + renderedStates.contents->Belt.Array.keepWithIndex((value, index) => + renderedStates.contents + ->Belt.Array.getIndexBy(v => v === value) + ->Belt.Option.getWithDefault(-1) === index + ) + expect(distinctRenderState) |> toEqual([0, 1, 2, 3, 4, 5]) + }) + + test("removes subscription on unmount", () => { + let setupSpy: selectorMockTest => spyReturnMockValue = %raw(` + function (obj) { + return jest.spyOn(obj, "selector"); + } + `) + + let selector = (state: TestStoreContext.testState) => state.counter + let selectorSpyObject = ref({"selector": selector}) + + let selectStateFromSpy = state => { + let spySelector = selectorSpyObject.contents["selector"] + spySelector(state) + } + + module Comp = { + @react.component + let make = () => { + let _ = TestStoreContext.useSelector(selectStateFromSpy) +
+ } + } + + let element = + render(element) |> unmount() |> ignore + + // start spying after unmount + let spy = setupSpy(selectorSpyObject.contents) + + act(() => { + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) + Reductive.Store.dispatch(TestStoreContext.appStore, Decrement) + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) + () + }) + + let expectSelectorToHaveBeenCalled: spyReturnMockValue => unit = %raw(` + function (spy) { + console.log(spy) + return expect(spy).toHaveBeenCalledTimes(0) + } + `) + + expectSelectorToHaveBeenCalled(spy) + expect(true) |> toEqual(true) + }) + }) + + describe("useStore", () => { + test("gets the store", () => { + let container = renderHook(() => TestStoreContext.useStore(), ~options, ()) + + expect(getResult(container)) |> toEqual(TestStoreContext.appStore) + }) + + test("re-renders component on store updates", () => { + let container = renderHook(() => TestStoreContext.useStore(), ~options, ()) + + act(() => { + Reductive.Store.dispatch(TestStoreContext.appStore, Increment) + () + }) + + let state = container->getResult->Reductive.Store.getState + expect(state.counter) |> toEqual(1) + }) + }) + + describe("useDispatch", () => + test("has stable reference", () => { + let dispatchRerenders: ref = ref(0) + let forceRender = ref(() => ()) + + module Comp = { + @react.component + let make = () => { + let (_, setState) = React.useState(() => 0) + forceRender := (() => setState(prev => prev + 1)) + + let dispatch = TestStoreContext.useDispatch() + React.useEffect1(() => { + dispatchRerenders := dispatchRerenders.contents + 1 + None + }, [dispatch]) +
+ } + } + + let element = + render(element) |> ignore + + act(() => { + forceRender.contents() + forceRender.contents() + forceRender.contents() + () + }) + expect(dispatchRerenders.contents) |> toEqual(1) + }) + ) +}) diff --git a/bsconfig.json b/bsconfig.json index ead74a2..17628d5 100644 --- a/bsconfig.json +++ b/bsconfig.json @@ -11,7 +11,6 @@ "reason-react" ], "bs-dev-dependencies": [ - "immutable-re", "@glennsl/bs-jest", "bs-react-testing-library", "reason-hooks-testing-library" diff --git a/examples/basic/basicEntry.re b/examples/basic/basicEntry.re deleted file mode 100644 index 45149ef..0000000 --- a/examples/basic/basicEntry.re +++ /dev/null @@ -1,48 +0,0 @@ -let unsubscribe = Reductive.Store.subscribe(SimpleStore.store, () => Js.log("store has updated")); - -let dispatch = Reductive.Store.dispatch(SimpleStore.store); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Decrement); - -dispatch(Increment); - -Reductive.Store.subscribe( - SimpleStore.store, - () => Js.log(Reductive.Store.getState(SimpleStore.store)) -); - -/* when replacing reducers, the action and state types must match*/ -Reductive.Store.replaceReducer(SimpleStore.store, SimpleStore.doubleCounter); - -dispatch(Increment); - -unsubscribe(); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -dispatch(Increment); - -Reductive.Store.subscribe( - ComplexStore.store, - () => Js.log(Reductive.Store.getState(ComplexStore.store)) -); - -Reductive.Store.dispatch(ComplexStore.store, ComplexStore.StringAction(A)); - -Reductive.Store.dispatch(ComplexStore.store, ComplexStore.StringAction(B)); - -Reductive.Store.dispatch(ComplexStore.store, ComplexStore.CounterAction(Increment)); diff --git a/examples/basic/basicEntry.res b/examples/basic/basicEntry.res new file mode 100644 index 0000000..de86e69 --- /dev/null +++ b/examples/basic/basicEntry.res @@ -0,0 +1,46 @@ +let unsubscribe = Reductive.Store.subscribe(SimpleStore.store, () => Js.log("store has updated")) + +let dispatch = Reductive.Store.dispatch(SimpleStore.store) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Decrement) + +dispatch(Increment) + +Reductive.Store.subscribe(SimpleStore.store, () => + Js.log(Reductive.Store.getState(SimpleStore.store)) +) + +/* when replacing reducers, the action and state types must match */ +Reductive.Store.replaceReducer(SimpleStore.store, SimpleStore.doubleCounter) + +dispatch(Increment) + +unsubscribe() + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +dispatch(Increment) + +Reductive.Store.subscribe(ComplexStore.store, () => + Js.log(Reductive.Store.getState(ComplexStore.store)) +) + +Reductive.Store.dispatch(ComplexStore.store, ComplexStore.StringAction(A)) + +Reductive.Store.dispatch(ComplexStore.store, ComplexStore.StringAction(B)) + +Reductive.Store.dispatch(ComplexStore.store, ComplexStore.CounterAction(Increment)) diff --git a/examples/basic/complexStore.re b/examples/basic/complexStore.res similarity index 64% rename from examples/basic/complexStore.re rename to examples/basic/complexStore.res index 024547f..fa43d47 100644 --- a/examples/basic/complexStore.re +++ b/examples/basic/complexStore.res @@ -1,34 +1,33 @@ -open SimpleStore; +open SimpleStore type stringAction = | A - | B; + | B let stringReduce = (state, action) => switch action { | A => state ++ "a" | B => state ++ "b" - }; + } type appActions = | StringAction(stringAction) - | CounterAction(action); + | CounterAction(action) type appState = { counter: int, - notACounter: string -}; + notACounter: string, +} let appReducer = (state, action) => switch action { | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} | CounterAction(action) => {...state, counter: counter(state.counter, action)} - }; + } -let store = - Reductive.Store.create( - ~reducer=appReducer, - ~preloadedState={counter: 0, notACounter: ""}, - ~enhancer=Middleware.logger, - () - ); +let store = Reductive.Store.create( + ~reducer=appReducer, + ~preloadedState={counter: 0, notACounter: ""}, + ~enhancer=Middleware.logger, + (), +) diff --git a/examples/basic/simpleStore.re b/examples/basic/simpleStore.res similarity index 89% rename from examples/basic/simpleStore.re rename to examples/basic/simpleStore.res index 63b4911..62ee3d2 100644 --- a/examples/basic/simpleStore.re +++ b/examples/basic/simpleStore.res @@ -1,17 +1,17 @@ type action = | Increment - | Decrement; + | Decrement let counter = (state, action) => switch action { | Increment => state + 1 | Decrement => state - 1 - }; + } let doubleCounter = (state, action) => switch action { | Increment => state + 2 | Decrement => state - 2 - }; + } -let store = Reductive.Store.create(~reducer=counter, ~preloadedState=0, ()); +let store = Reductive.Store.create(~reducer=counter, ~preloadedState=0, ()) diff --git a/examples/immutable/appState.re b/examples/immutable/appState.res similarity index 57% rename from examples/immutable/appState.re rename to examples/immutable/appState.res index 2615484..1d0e3cd 100644 --- a/examples/immutable/appState.re +++ b/examples/immutable/appState.res @@ -1,4 +1,4 @@ type appState = { counter: int, - notACounter: string -}; + notACounter: string, +} diff --git a/examples/immutable/immutableEntry.re b/examples/immutable/immutableEntry.res similarity index 86% rename from examples/immutable/immutableEntry.re rename to examples/immutable/immutableEntry.res index 6ae06ed..bc28554 100644 --- a/examples/immutable/immutableEntry.re +++ b/examples/immutable/immutableEntry.res @@ -1,7 +1,7 @@ -[@bs.config {jsx: 3}]; +@@bs.config({jsx: 3}) ReactDOMRe.renderToElementWithId( , "index", -); +) diff --git a/examples/immutable/immutableRenderer.re b/examples/immutable/immutableRenderer.re deleted file mode 100644 index b883db8..0000000 --- a/examples/immutable/immutableRenderer.re +++ /dev/null @@ -1,50 +0,0 @@ -[@bs.config {jsx: 3}]; - -let stateSelector = state => state; -[@react.component] -let make = () => { - let state = TimeTravelStore.useSelector(stateSelector); - let dispatch = TimeTravelStore.useDispatch(); - - let incrementIfOdd = - (store: Reductive.Store.t(ReduxThunk.thunk(AppState.appState), AppState.appState)) => - switch (Reductive.Store.getState(store)) { - | {counter} when counter mod 2 === 1 => - Reductive.Store.dispatch(store, TimeTravelStore.CounterAction(SimpleStore.Increment)) - | _ => () - }; - let incrementAsync = (store) => - ignore( - Js.Global.setTimeout( - () => - Reductive.Store.dispatch(store, TimeTravelStore.CounterAction(SimpleStore.Increment)), - 1000 - ) - ); - -
-
(ReasonReact.string("string: " ++ state.notACounter))
-
(ReasonReact.string("counter: " ++ string_of_int(state.counter)))
- - - - - - - -
; -}; diff --git a/examples/immutable/immutableRenderer.res b/examples/immutable/immutableRenderer.res new file mode 100644 index 0000000..22f4cda --- /dev/null +++ b/examples/immutable/immutableRenderer.res @@ -0,0 +1,50 @@ +@@bs.config({jsx: 3}) + +let stateSelector = state => state +@react.component +let make = () => { + let state = TimeTravelStore.useSelector(stateSelector) + let dispatch = TimeTravelStore.useDispatch() + + let incrementIfOdd = ( + store: Reductive.Store.t, AppState.appState>, + ) => + switch Reductive.Store.getState(store) { + | {counter} if mod(counter, 2) === 1 => + Reductive.Store.dispatch(store, TimeTravelStore.CounterAction(SimpleStore.Increment)) + | _ => () + } + let incrementAsync = store => + ignore( + Js.Global.setTimeout( + () => Reductive.Store.dispatch(store, TimeTravelStore.CounterAction(SimpleStore.Increment)), + 1000, + ), + ) + +
+
{ReasonReact.string("string: " ++ state.notACounter)}
+
{ReasonReact.string("counter: " ++ string_of_int(state.counter))}
+ + + + + + + +
+} diff --git a/examples/immutable/timeTravelStore.re b/examples/immutable/timeTravelStore.re deleted file mode 100644 index b4d403a..0000000 --- a/examples/immutable/timeTravelStore.re +++ /dev/null @@ -1,94 +0,0 @@ -open SimpleStore; - -type stringAction = - | A - | B; - -let stringReduce = (state, action) => - switch action { - | A => state ++ "a" - | B => state ++ "b" - }; - -type ReduxThunk.thunk(_) += - | StringAction (stringAction) - | CounterAction (action); - -type ReduxThunk.thunk('a) += - | ReplaceState ('a); - -let appReducter = (state: AppState.appState, action) => - switch action { - | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} - | CounterAction(action) => {...state, counter: counter(state.counter, action)} - | ReplaceState(replacedState) => replacedState - | _ => state - }; - -type ReduxThunk.thunk(_) += - | TravelBackward - | TravelForward; - -let past = ref(Immutable.Stack.empty()); - -let future = ref(Immutable.Stack.empty()); - -let goBack = currentState => - switch (Immutable.Stack.first(past^)) { - | Some(lastState) => - future := Immutable.Stack.addFirst(currentState, future^); - if (Immutable.Stack.isNotEmpty(past^)) { - past := Immutable.Stack.removeFirstOrRaise(past^); - }; - lastState; - | None => currentState - }; - -let goForward = currentState => - switch (Immutable.Stack.first(future^)) { - | Some(nextState) => - past := Immutable.Stack.addFirst(currentState, past^); - if (Immutable.Stack.isNotEmpty(future^)) { - future := Immutable.Stack.removeFirstOrRaise(future^); - }; - nextState; - | None => currentState - }; - -let recordHistory = currentState => { - past := Immutable.Stack.addFirst(currentState, past^); - future := Immutable.Stack.empty(); -}; - -let timeTravel = (store, next, action) => { - let currentState = Reductive.Store.getState(store); - switch (action) { - | TravelBackward => next(ReplaceState(goBack(currentState))) - | TravelForward => next(ReplaceState(goForward(currentState))) - | _ => - next(action); - let newState = Reductive.Store.getState(store); - if (currentState !== newState) { - recordHistory(currentState); - }; - }; -}; - -let thunkedLoggedTimeTravelLogger = (store, next) => - Middleware.thunk(store) @@ - Middleware.logger(store) @@ - timeTravel(store) @@ - next; - -let timeTravelStore = - Reductive.Store.create( - ~reducer=appReducter, - ~preloadedState={counter: 0, notACounter: ""}, - ~enhancer=thunkedLoggedTimeTravelLogger, - (), - ); - -include ReductiveContext.Make({ - type action = ReduxThunk.thunk(AppState.appState); - type state = AppState.appState; -}); diff --git a/examples/immutable/timeTravelStore.res b/examples/immutable/timeTravelStore.res new file mode 100644 index 0000000..0e3cc41 --- /dev/null +++ b/examples/immutable/timeTravelStore.res @@ -0,0 +1,90 @@ +open SimpleStore + +type stringAction = + | A + | B + +let stringReduce = (state, action) => + switch action { + | A => state ++ "a" + | B => state ++ "b" + } + +type ReduxThunk.thunk<_> += + | StringAction(stringAction) + | CounterAction(action) + +type ReduxThunk.thunk<'a> += + | ReplaceState('a) + +let appReducter = (state: AppState.appState, action) => + switch action { + | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} + | CounterAction(action) => {...state, counter: counter(state.counter, action)} + | ReplaceState(replacedState) => replacedState + | _ => state + } + +type ReduxThunk.thunk<_> += + | TravelBackward + | TravelForward + +let past = ref(Immutable.Stack.empty()) + +let future = ref(Immutable.Stack.empty()) + +let goBack = currentState => + switch Immutable.Stack.first(past.contents) { + | Some(lastState) => + future := Immutable.Stack.addFirst(currentState, future.contents) + if Immutable.Stack.isNotEmpty(past.contents) { + past := Immutable.Stack.removeFirstOrRaise(past.contents) + } + lastState + | None => currentState + } + +let goForward = currentState => + switch Immutable.Stack.first(future.contents) { + | Some(nextState) => + past := Immutable.Stack.addFirst(currentState, past.contents) + if Immutable.Stack.isNotEmpty(future.contents) { + future := Immutable.Stack.removeFirstOrRaise(future.contents) + } + nextState + | None => currentState + } + +let recordHistory = currentState => { + past := Immutable.Stack.addFirst(currentState, past.contents) + future := Immutable.Stack.empty() +} + +let timeTravel = (store, next, action) => { + let currentState = Reductive.Store.getState(store) + switch action { + | TravelBackward => next(ReplaceState(goBack(currentState))) + | TravelForward => next(ReplaceState(goForward(currentState))) + | _ => + next(action) + let newState = Reductive.Store.getState(store) + if currentState !== newState { + recordHistory(currentState) + } + } +} + +let thunkedLoggedTimeTravelLogger = (store, next) => + \"@@"(Middleware.thunk(store), \"@@"(Middleware.logger(store), \"@@"(timeTravel(store), next))) + +let timeTravelStore = Reductive.Store.create( + ~reducer=appReducter, + ~preloadedState={counter: 0, notACounter: ""}, + ~enhancer=thunkedLoggedTimeTravelLogger, + (), +) + +include ReductiveContext.Make({ + type action = ReduxThunk.thunk + type state = AppState.appState +}) diff --git a/examples/middleware.re b/examples/middleware.res similarity index 85% rename from examples/middleware.re rename to examples/middleware.res index fef168b..ef8a460 100644 --- a/examples/middleware.re +++ b/examples/middleware.res @@ -6,18 +6,17 @@ * return value can be used by the middleware that called you (optional) */ -/*** +/* ** * logs the action before dispatching and the new state after. */ let logger = (store, next, action) => { - Js.log(action); - let returnValue = next(action); - Js.log(Reductive.Store.getState(store)); + Js.log(action) + let returnValue = next(action) + Js.log(Reductive.Store.getState(store)) returnValue -}; +} - -/*** +/* ** * middleware that listens for a specific action and calls that function. * Allows for async actions. */ @@ -25,4 +24,4 @@ let thunk = (store, next, action) => switch action { | ReduxThunk.Thunk(func) => func(store) | _ => next(action) - }; + } diff --git a/examples/react/dataRenderer.re b/examples/react/dataRenderer.re deleted file mode 100644 index 6a4b4b7..0000000 --- a/examples/react/dataRenderer.re +++ /dev/null @@ -1,42 +0,0 @@ -let stateSelector = state => state; - -[@react.component] -let make = () => { - let state = ThunkedStore.useSelector(stateSelector); - let dispatch = ThunkedStore.useDispatch(); - - let incrementIfOdd = - (store: Reductive.Store.t(ReduxThunk.thunk(ThunkedStore.appState), ThunkedStore.appState)) => - switch (Reductive.Store.getState(store)) { - | {counter} when counter mod 2 === 1 => - Reductive.Store.dispatch(store, ThunkedStore.CounterAction(SimpleStore.Increment)) - | _ => () - }; - let incrementAsync = (store) => - ignore( - Js.Global.setTimeout( - () => Reductive.Store.dispatch(store, ThunkedStore.CounterAction(SimpleStore.Increment)), - 1000 - ) - ); - -
-
(ReasonReact.string("string: " ++ state.notACounter))
-
(ReasonReact.string("counter: " ++ string_of_int(state.counter)))
- - - - - -
; -}; diff --git a/examples/react/dataRenderer.res b/examples/react/dataRenderer.res new file mode 100644 index 0000000..074a678 --- /dev/null +++ b/examples/react/dataRenderer.res @@ -0,0 +1,43 @@ +let stateSelector = state => state + +@react.component +let make = () => { + let state = ThunkedStore.useSelector(stateSelector) + let dispatch = ThunkedStore.useDispatch() + + let incrementIfOdd = ( + store: Reductive.Store.t, ThunkedStore.appState>, + ) => + switch Reductive.Store.getState(store) { + | {counter} if mod(counter, 2) === 1 => + Reductive.Store.dispatch(store, ThunkedStore.CounterAction(SimpleStore.Increment)) + | _ => () + } + let incrementAsync = store => + ignore( + Js.Global.setTimeout( + () => Reductive.Store.dispatch(store, ThunkedStore.CounterAction(SimpleStore.Increment)), + 1000, + ), + ) + +
+
{ReasonReact.string("string: " ++ state.notACounter)}
+
{ReasonReact.string("counter: " ++ string_of_int(state.counter))}
+ + + + + +
+} diff --git a/examples/react/reactEntry.re b/examples/react/reactEntry.re deleted file mode 100644 index 2fa98db..0000000 --- a/examples/react/reactEntry.re +++ /dev/null @@ -1,7 +0,0 @@ -[@bs.config {jsx: 3}]; -ReactDOMRe.renderToElementWithId( - - - , - "index", -); diff --git a/examples/react/reactEntry.res b/examples/react/reactEntry.res new file mode 100644 index 0000000..1db5df2 --- /dev/null +++ b/examples/react/reactEntry.res @@ -0,0 +1,5 @@ +@@bs.config({jsx: 3}) +ReactDOMRe.renderToElementWithId( + , + "index", +) diff --git a/examples/react/reduxThunk.re b/examples/react/reduxThunk.re deleted file mode 100644 index ddfb302..0000000 --- a/examples/react/reduxThunk.re +++ /dev/null @@ -1,4 +0,0 @@ -type thunk('state) = ..; - -type thunk('state) += - | Thunk ((Reductive.Store.t(thunk('state), 'state) => unit)); diff --git a/examples/react/reduxThunk.res b/examples/react/reduxThunk.res new file mode 100644 index 0000000..ee094fc --- /dev/null +++ b/examples/react/reduxThunk.res @@ -0,0 +1,4 @@ +type thunk<'state> = .. + +type thunk<'state> += + | Thunk(Reductive.Store.t, 'state> => unit) diff --git a/examples/react/thunkedStore.re b/examples/react/thunkedStore.re deleted file mode 100644 index bd8efc1..0000000 --- a/examples/react/thunkedStore.re +++ /dev/null @@ -1,43 +0,0 @@ -[@bs.config {jsx: 3}]; -open SimpleStore; - -type stringAction = - | A - | B; - -let stringReduce = (state, action) => - switch action { - | A => state ++ "a" - | B => state ++ "b" - }; - -type ReduxThunk.thunk(_) += - | StringAction (stringAction) - | CounterAction (action); - -type appState = { - counter: int, - notACounter: string -}; - -let appReducer = (state, action) => - switch action { - | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} - | CounterAction(action) => {...state, counter: counter(state.counter, action)} - | _ => state - }; - -let thunkedLogger = (store, next) => Middleware.thunk(store) @@ Middleware.logger(store) @@ next; - -let appStore = - Reductive.Store.create( - ~reducer=appReducer, - ~preloadedState={counter: 0, notACounter: ""}, - ~enhancer=thunkedLogger, - (), - ); - -include ReductiveContext.Make({ - type state = appState; - type action = ReduxThunk.thunk(appState); -}); diff --git a/examples/react/thunkedStore.res b/examples/react/thunkedStore.res new file mode 100644 index 0000000..dc44c32 --- /dev/null +++ b/examples/react/thunkedStore.res @@ -0,0 +1,43 @@ +@@bs.config({jsx: 3}) +open SimpleStore + +type stringAction = + | A + | B + +let stringReduce = (state, action) => + switch action { + | A => state ++ "a" + | B => state ++ "b" + } + +type ReduxThunk.thunk<_> += + | StringAction(stringAction) + | CounterAction(action) + +type appState = { + counter: int, + notACounter: string, +} + +let appReducer = (state, action) => + switch action { + | StringAction(action) => {...state, notACounter: stringReduce(state.notACounter, action)} + | CounterAction(action) => {...state, counter: counter(state.counter, action)} + | _ => state + } + +let thunkedLogger = (store, next) => + \"@@"(Middleware.thunk(store), \"@@"(Middleware.logger(store), next)) + +let appStore = Reductive.Store.create( + ~reducer=appReducer, + ~preloadedState={counter: 0, notACounter: ""}, + ~enhancer=thunkedLogger, + (), +) + +include ReductiveContext.Make({ + type state = appState + type action = ReduxThunk.thunk +}) diff --git a/examples/render-v2/renderEntryV2.re b/examples/render-v2/renderEntryV2.res similarity index 52% rename from examples/render-v2/renderEntryV2.re rename to examples/render-v2/renderEntryV2.res index e9b322c..67bd30b 100644 --- a/examples/render-v2/renderEntryV2.re +++ b/examples/render-v2/renderEntryV2.res @@ -9,39 +9,39 @@ module Counter = { type counterAction = | Increment - | Decrement; + | Decrement let counterReduce = (state, action) => - switch (action) { + switch action { | Increment => state + 1 | Decrement => state - 1 - }; -}; + } +} module Content = { type contentAction = | AppendA - | AppendB; + | AppendB let contentReduce = (state, action) => - switch (action) { + switch action { | AppendA => state ++ "A" | AppendB => state ++ "B" - }; -}; + } +} module App = { type appState = { counter: int, content: string, - }; + } type appAction = | CounterAction(Counter.counterAction) - | StringAction(Content.contentAction); + | StringAction(Content.contentAction) let appReducer = (state, action) => - switch (action) { + switch action { | StringAction(action) => { ...state, content: Content.contentReduce(state.content, action), @@ -50,77 +50,64 @@ module App = { ...state, counter: Counter.counterReduce(state.counter, action), } - }; -}; + } +} module Store = { include ReductiveContext.Make({ - type action = App.appAction; - type state = App.appState; - }); + type action = App.appAction + type state = App.appState + }) - let logger = (store, next) => Middleware.logger(store) @@ next; + let logger = (store, next) => \"@@"(Middleware.logger(store), next) - let store = - Reductive.Store.create( - ~reducer=App.appReducer, - ~preloadedState={counter: 0, content: ""}, - ~enhancer=logger, - (), - ); + let store = Reductive.Store.create( + ~reducer=App.appReducer, + ~preloadedState={counter: 0, content: ""}, + ~enhancer=logger, + (), + ) module Selectors = { - open App; - let state = state => state; - let contentState = state => state.content; - let counterState = state => state.counter; - }; -}; + open App + let state = state => state + let contentState = state => state.content + let counterState = state => state.counter + } +} module ContentComponent = { - [@react.component] + @react.component let make = () => { - let dispatch = Store.useDispatch(); - let state = Store.useSelector(Store.Selectors.contentState); + let dispatch = Store.useDispatch() + let state = Store.useSelector(Store.Selectors.contentState)
{ReasonReact.string("Content: " ++ state)}
- - -
; - }; -}; + + +
+ } +} module CounterComponent = { - [@react.component] + @react.component let make = () => { - let dispatch = Store.useDispatch(); - let state = Store.useSelector(Store.Selectors.counterState); + let dispatch = Store.useDispatch() + let state = Store.useSelector(Store.Selectors.counterState)
{React.string("Counter: " ++ string_of_int(state))}
- - -
; - }; -}; + + +
+ } +} module RenderApp = { - [@react.component] - let make = () => { + @react.component + let make = () =>
- - - - -
; - }; -}; + +
+} -ReactDOMRe.renderToElementWithId(, "index"); +ReactDOMRe.renderToElementWithId(, "index") diff --git a/examples/render/renderEntry.re b/examples/render/renderEntry.res similarity index 55% rename from examples/render/renderEntry.re rename to examples/render/renderEntry.res index 56447e7..bee94bc 100644 --- a/examples/render/renderEntry.re +++ b/examples/render/renderEntry.res @@ -1,4 +1,4 @@ -[@bs.config {jsx: 3}]; +@@bs.config({jsx: 3}) /* * Example using multiple components to represent different slices of state. * Updating the state exposed by one component should not cause the other @@ -8,35 +8,35 @@ */ type counterAction = | Increment - | Decrement; + | Decrement let counterReduce = (state, action) => - switch (action) { + switch action { | Increment => state + 1 | Decrement => state - 1 - }; + } type stringAction = | AppendA - | AppendB; + | AppendB let stringReduce = (state, action) => - switch (action) { + switch action { | AppendA => state ++ "A" | AppendB => state ++ "B" - }; + } -type ReduxThunk.thunk(_) += +type ReduxThunk.thunk<_> += | StringAction(stringAction) - | CounterAction(counterAction); + | CounterAction(counterAction) type appState = { counter: int, content: string, -}; +} let appReducer = (state, action) => - switch (action) { + switch action { | StringAction(action) => { ...state, content: stringReduce(state.content, action), @@ -46,52 +46,47 @@ let appReducer = (state, action) => counter: counterReduce(state.counter, action), } | _ => state - }; + } let thunkedLogger = (store, next) => - Middleware.thunk(store) @@ Middleware.logger(store) @@ next; + \"@@"(Middleware.thunk(store), \"@@"(Middleware.logger(store), next)) -let appStore = - Reductive.Store.create( - ~reducer=appReducer, - ~preloadedState={counter: 0, content: ""}, - ~enhancer=thunkedLogger, - (), - ); +let appStore = Reductive.Store.create( + ~reducer=appReducer, + ~preloadedState={counter: 0, content: ""}, + ~enhancer=thunkedLogger, + (), +) module AppStore = { include ReductiveContext.Make({ - type state = appState; - type action = ReduxThunk.thunk(appState); - }); -}; + type state = appState + type action = ReduxThunk.thunk + }) +} -let contentSelector = state => state.content; +let contentSelector = state => state.content module StringComponent = { - [@react.component] + @react.component let make = () => { - let dispatch = AppStore.useDispatch(); - let state = AppStore.useSelector(contentSelector); + let dispatch = AppStore.useDispatch() + let state = AppStore.useSelector(contentSelector)
{ReasonReact.string("Content: " ++ state)}
- - -
; - }; -}; + + +
+ } +} -let counterSelector = state => state.counter; +let counterSelector = state => state.counter module CounterComponent = { - [@react.component] + @react.component let make = () => { - let dispatch = AppStore.useDispatch(); - let state = AppStore.useSelector(counterSelector); + let dispatch = AppStore.useDispatch() + let state = AppStore.useSelector(counterSelector)
{ReasonReact.string("Counter: " ++ string_of_int(state))}
@@ -101,15 +96,13 @@ module CounterComponent = { -
; - }; -}; +
+ } +} module RenderApp = { - [@react.component] - let make = () => { -
; - }; -}; + @react.component + let make = () =>
+} -ReactDOMRe.renderToElementWithId(, "index"); +ReactDOMRe.renderToElementWithId(, "index") diff --git a/examples/todomvc/todomvcEntry.re b/examples/todomvc/todomvcEntry.re deleted file mode 100644 index 9fa4b37..0000000 --- a/examples/todomvc/todomvcEntry.re +++ /dev/null @@ -1,338 +0,0 @@ -[@bs.config {jsx: 3}]; -/** - * Example of a todo list, loosely based on the redux example, mixed with - * some code from Jared Forsyth's ReasonReact tutorial, and Cheng Lou's - * todomvc ReasonReact example. - * - * Borrowed CSS from https://github.com/tastejs/todomvc-app-css - */ -type visibility = - | ALL - | ACTIVE - | COMPLETED; - -type appAction = - | AddItem(string) - | Show(visibility) - | UpdateItem(int, string) - | ToggleItem(int) - | ToggleAll(bool) - | ClearItem(int) - | ClearCompleted; - -type todo = { - id: int, - text: string, - completed: bool, -}; - -type appState = { - todos: list(todo), - filter: visibility, -}; - -let lastId = ref(0); -let newItem = text => { - lastId := lastId^ + 1; - {id: lastId^, text, completed: false}; -}; - -let appReducer = (state, action) => - switch (action) { - | AddItem(text) => {...state, todos: [newItem(text), ...state.todos]} - | Show(vis) => {...state, filter: vis} - | UpdateItem(id, text) => { - ...state, - todos: - List.map( - todo => todo.id == id ? {...todo, text} : todo, - state.todos, - ), - } - | ToggleItem(id) => { - ...state, - todos: - List.map( - todo => - todo.id == id ? {...todo, completed: !todo.completed} : todo, - state.todos, - ), - } - | ToggleAll(checked) => { - ...state, - todos: List.map(todo => {...todo, completed: checked}, state.todos), - } - | ClearItem(id) => { - ...state, - todos: List.filter(todo => todo.id != id, state.todos), - } - | ClearCompleted => { - ...state, - todos: List.filter(todo => !todo.completed, state.todos), - } - }; - -let logger = (store, next) => Middleware.logger(store) @@ next; - -let appStore = - Reductive.Store.create( - ~reducer=appReducer, - ~preloadedState={todos: [], filter: ALL}, - ~enhancer=logger, - (), - ); - -module AppStore = { - include ReductiveContext.Make({ - type action = appAction; - type state = appState; - }); -}; - -let valueFromEvent = (evt): string => ReactEvent.Form.target(evt)##value; - -module TodoItem = { - type state = { - editText: string, - editing: bool, - }; - type action = - | Edit - | FinishEdit(string) - | CancelEdit - | Change(string); - - let getInitialState = todo => {editText: todo.text, editing: false}; - - [@react.component] - let make = (~todo, ~dispatch) => { - let editFieldRef = React.useRef(Js.Nullable.null); - - let reducer = (state, action) => { - switch (action) { - | Edit => {editing: true, editText: todo.text} - | FinishEdit(editText) => {editing: false, editText} - | Change(text) => state.editing ? {...state, editText: text} : state - | CancelEdit => {editing: false, editText: todo.text} - }; - }; - let (state, todoItemDispatch) = - React.useReducer(reducer, getInitialState(todo)); - - React.useEffect1( - () => { - Js.log("Inside effect "); - let optionRef = editFieldRef->React.Ref.current->Js.Nullable.toOption; - switch (state.editing, optionRef) { - | (true, Some(field)) => - let node = ReactDOMRe.domElementToObj(field); - Js.log(node); - - ignore(node##focus()); - ignore( - node##setSelectionRange(node##value##length, node##value##length), - ); - | _ => () - }; - - None; - }, - [|state.editing|], - ); - - let submit = () => { - switch (String.trim(state.editText)) { - | "" => dispatch(ClearItem(todo.id)) - | nonEmptyValue => - todoItemDispatch(FinishEdit(nonEmptyValue)); - dispatch(UpdateItem(todo.id, nonEmptyValue)); - }; - }; - - let handleKeyDown = key => { - switch (key) { - | 27 => todoItemDispatch(CancelEdit) - | 13 => submit() - | _ => () - }; - }; - - let className = - [todo.completed ? "completed" : "", state.editing ? "editing" : ""] - |> String.concat(" "); - -
  • -
    - dispatch(ToggleItem(todo.id))} - /> - -
    - submit()} - onChange={event => { - let value = valueFromEvent(event); - todoItemDispatch(Change(value)); - }} - onKeyDown={event => handleKeyDown(ReactEvent.Keyboard.which(event))} - /> -
  • ; - }; -}; - -module TodoList = { - [@react.component] - let make = (~todos, ~dispatch) => { - let todoItems = - List.map( - todo => , - todos, - ); - let todoCount = List.length(todos); - let completedCount = - List.length(List.filter(todo => todo.completed, todos)); - let activeTodoCount = todoCount - completedCount; - -
    - dispatch(ToggleAll(activeTodoCount > 0))} - checked={todoCount > 0 && activeTodoCount === 0} - /> - -
      - {ReasonReact.array(Belt.List.toArray(todoItems))} -
    -
    ; - }; -}; - -module TodoInput = { - [@react.component] - let make = (~onSubmit) => { - let (value, setValue) = React.useState(() => ""); - let handleChange = event => { - let value = valueFromEvent(event); - setValue(_ => value); - }; - let handleKeyDown = event => - if (ReactEvent.Keyboard.key(event) == "Enter") { - onSubmit(value); - setValue(_ => ""); - }; - - ; - }; -}; - -module AddTodo = { - [@react.component] - let make = (~dispatch) => { - dispatch(AddItem(text))} />; - }; -}; - -module VisibleTodoList = { - [@react.component] - let make = (~state: appState, ~dispatch) => { - let todos = - switch (state.filter) { - | ALL => state.todos - | ACTIVE => List.filter(t => !t.completed, state.todos) - | COMPLETED => List.filter(t => t.completed, state.todos) - }; - ; - }; -}; - -module FilterLink = { - [@react.component] - let make = (~filter, ~label, ~state: appState, ~dispatch) => { - let className = filter == state.filter ? "selected" : ""; -
  • - dispatch(Show(filter))}> - {ReasonReact.string(label)} - -
  • ; - }; -}; - -module Footer = { - [@react.component] - let make = (~state: appState, ~dispatch) => { - let completedCount = - List.length(List.filter(todo => todo.completed, state.todos)); - let activeTodoCount = List.length(state.todos) - completedCount; - let activeTodoWord = activeTodoCount === 1 ? "item" : "items"; - let clearButton = - completedCount > 0 - ? - : ReasonReact.null; -
    - - - {ReasonReact.string(string_of_int(activeTodoCount))} - - {ReasonReact.string(" " ++ activeTodoWord ++ " left")} - -
      - - - -
    - clearButton -
    ; - }; -}; - -let stateSelector = state => state; - -module App = { - [@react.component] - let make = () => { - let dispatch = AppStore.useDispatch(); - let state = AppStore.useSelector(stateSelector); - -
    -
    -

    {ReasonReact.string("todos")}

    - -
    - -
    -
    ; - }; -}; - -ReactDOMRe.renderToElementWithId( - , - "TodoApp", -); diff --git a/examples/todomvc/todomvcEntry.res b/examples/todomvc/todomvcEntry.res new file mode 100644 index 0000000..5e59654 --- /dev/null +++ b/examples/todomvc/todomvcEntry.res @@ -0,0 +1,294 @@ +@@bs.config({jsx: 3}) +@ocaml.doc(" + * Example of a todo list, loosely based on the redux example, mixed with + * some code from Jared Forsyth's ReasonReact tutorial, and Cheng Lou's + * todomvc ReasonReact example. + * + * Borrowed CSS from https://github.com/tastejs/todomvc-app-css + ") +type visibility = + | ALL + | ACTIVE + | COMPLETED + +type appAction = + | AddItem(string) + | Show(visibility) + | UpdateItem(int, string) + | ToggleItem(int) + | ToggleAll(bool) + | ClearItem(int) + | ClearCompleted + +type todo = { + id: int, + text: string, + completed: bool, +} + +type appState = { + todos: list, + filter: visibility, +} + +let lastId = ref(0) +let newItem = text => { + lastId := lastId.contents + 1 + {id: lastId.contents, text: text, completed: false} +} + +let appReducer = (state, action) => + switch action { + | AddItem(text) => {...state, todos: list{newItem(text), ...state.todos}} + | Show(vis) => {...state, filter: vis} + | UpdateItem(id, text) => { + ...state, + todos: List.map(todo => todo.id == id ? {...todo, text: text} : todo, state.todos), + } + | ToggleItem(id) => { + ...state, + todos: List.map( + todo => todo.id == id ? {...todo, completed: !todo.completed} : todo, + state.todos, + ), + } + | ToggleAll(checked) => { + ...state, + todos: List.map(todo => {...todo, completed: checked}, state.todos), + } + | ClearItem(id) => { + ...state, + todos: List.filter(todo => todo.id != id, state.todos), + } + | ClearCompleted => { + ...state, + todos: List.filter(todo => !todo.completed, state.todos), + } + } + +let logger = (store, next) => \"@@"(Middleware.logger(store), next) + +let appStore = Reductive.Store.create( + ~reducer=appReducer, + ~preloadedState={todos: list{}, filter: ALL}, + ~enhancer=logger, + (), +) + +module AppStore = { + include ReductiveContext.Make({ + type action = appAction + type state = appState + }) +} + +let valueFromEvent = (evt): string => ReactEvent.Form.target(evt)["value"] + +module TodoItem = { + type state = { + editText: string, + editing: bool, + } + type action = + | Edit + | FinishEdit(string) + | CancelEdit + | Change(string) + + let getInitialState = todo => {editText: todo.text, editing: false} + + @react.component + let make = (~todo, ~dispatch) => { + let editFieldRef = React.useRef(Js.Nullable.null) + + let reducer = (state, action) => + switch action { + | Edit => {editing: true, editText: todo.text} + | FinishEdit(editText) => {editing: false, editText: editText} + | Change(text) => state.editing ? {...state, editText: text} : state + | CancelEdit => {editing: false, editText: todo.text} + } + let (state, todoItemDispatch) = React.useReducer(reducer, getInitialState(todo)) + + React.useEffect1(() => { + Js.log("Inside effect ") + let optionRef = editFieldRef->React.Ref.current->Js.Nullable.toOption + switch (state.editing, optionRef) { + | (true, Some(field)) => + let node = ReactDOMRe.domElementToObj(field) + Js.log(node) + + ignore(node["focus"]()) + ignore(node["setSelectionRange"](node["value"]["length"], node["value"]["length"])) + | _ => () + } + + None + }, [state.editing]) + + let submit = () => + switch String.trim(state.editText) { + | "" => dispatch(ClearItem(todo.id)) + | nonEmptyValue => + todoItemDispatch(FinishEdit(nonEmptyValue)) + dispatch(UpdateItem(todo.id, nonEmptyValue)) + } + + let handleKeyDown = key => + switch key { + | 27 => todoItemDispatch(CancelEdit) + | 13 => submit() + | _ => () + } + + let className = + list{todo.completed ? "completed" : "", state.editing ? "editing" : ""} |> String.concat(" ") + +
  • +
    + dispatch(ToggleItem(todo.id))} + /> + +
    + submit()} + onChange={event => { + let value = valueFromEvent(event) + todoItemDispatch(Change(value)) + }} + onKeyDown={event => handleKeyDown(ReactEvent.Keyboard.which(event))} + /> +
  • + } +} + +module TodoList = { + @react.component + let make = (~todos, ~dispatch) => { + let todoItems = List.map(todo => , todos) + let todoCount = List.length(todos) + let completedCount = List.length(List.filter(todo => todo.completed, todos)) + let activeTodoCount = todoCount - completedCount + +
    + dispatch(ToggleAll(activeTodoCount > 0))} + checked={todoCount > 0 && activeTodoCount === 0} + /> + +
      {ReasonReact.array(Belt.List.toArray(todoItems))}
    +
    + } +} + +module TodoInput = { + @react.component + let make = (~onSubmit) => { + let (value, setValue) = React.useState(() => "") + let handleChange = event => { + let value = valueFromEvent(event) + setValue(_ => value) + } + let handleKeyDown = event => + if ReactEvent.Keyboard.key(event) == "Enter" { + onSubmit(value) + setValue(_ => "") + } + + + } +} + +module AddTodo = { + @react.component + let make = (~dispatch) => dispatch(AddItem(text))} /> +} + +module VisibleTodoList = { + @react.component + let make = (~state: appState, ~dispatch) => { + let todos = switch state.filter { + | ALL => state.todos + | ACTIVE => List.filter(t => !t.completed, state.todos) + | COMPLETED => List.filter(t => t.completed, state.todos) + } + + } +} + +module FilterLink = { + @react.component + let make = (~filter, ~label, ~state: appState, ~dispatch) => { + let className = filter == state.filter ? "selected" : "" +
  • dispatch(Show(filter))}> {ReasonReact.string(label)}
  • + } +} + +module Footer = { + @react.component + let make = (~state: appState, ~dispatch) => { + let completedCount = List.length(List.filter(todo => todo.completed, state.todos)) + let activeTodoCount = List.length(state.todos) - completedCount + let activeTodoWord = activeTodoCount === 1 ? "item" : "items" + let clearButton = + completedCount > 0 + ? + : ReasonReact.null +
    + + {ReasonReact.string(string_of_int(activeTodoCount))} + {ReasonReact.string(" " ++ (activeTodoWord ++ " left"))} + +
      + + + +
    + clearButton +
    + } +} + +let stateSelector = state => state + +module App = { + @react.component + let make = () => { + let dispatch = AppStore.useDispatch() + let state = AppStore.useSelector(stateSelector) + +
    +
    +

    {ReasonReact.string("todos")}

    +
    + +
    +
    + } +} + +ReactDOMRe.renderToElementWithId( + , + "TodoApp", +) diff --git a/package.json b/package.json index c2a8ac6..6c96f37 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "Redux in Reason", "main": "index.js", "scripts": { - "build": "bsb -make-world", - "start": "bsb -make-world -w", - "clean": "bsb -clean-world", + "build": "rescript build", + "start": "rescript build -w", + "clean": "rescript clean", "test": "npm run build && jest --silent", "webpack": "webpack -w" }, @@ -30,13 +30,12 @@ }, "devDependencies": { "@glennsl/bs-jest": "^0.4.8", - "bs-platform": "^5.0.6", "bs-react-testing-library": "^0.5.0", - "immutable-re": "https://github.com/facebookincubator/immutable-re.git", "react": "^16.8.6", "react-dom": "^16.8.6", "reason-hooks-testing-library": "^0.2.0", "reason-react": "^0.7.0", + "rescript": "^9.1.4", "webpack": "^4.21.0", "webpack-cli": "^3.1.2" }, diff --git a/src/reductive.re b/src/reductive.re deleted file mode 100644 index a6e33e8..0000000 --- a/src/reductive.re +++ /dev/null @@ -1,164 +0,0 @@ -module Store = { - type t('action, 'state) = { - mutable state: 'state, - mutable reducer: ('state, 'action) => 'state, - mutable listeners: list(unit => unit), - customDispatcher: - option((t('action, 'state), 'action => unit, 'action) => unit), - }; - let create = (~reducer, ~preloadedState, ~enhancer=?, ()) => - switch (preloadedState, enhancer, reducer) { - | (preloadedState, None, reducer) => { - state: preloadedState, - listeners: [], - reducer, - customDispatcher: None, - } - | (preloadedState, Some(enhancer), reducer) => { - state: preloadedState, - listeners: [], - reducer, - customDispatcher: Some(enhancer), - } - }; - let unsubscribe = (store, listener, ()) => - store.listeners = List.filter(l => listener !== l, store.listeners); - let subscribe = (store, listener) => { - store.listeners = [listener, ...store.listeners]; - unsubscribe(store, listener); - }; - let nativeDispatch = (store, action) => { - store.state = store.reducer(store.state, action); - List.iter(listener => listener(), store.listeners); - }; - let dispatch = (store, action) => - switch (store.customDispatcher) { - | Some(customDispatcher) => - customDispatcher(store, nativeDispatch(store), action) - | None => nativeDispatch(store, action) - }; - let getState = store => store.state; - let replaceReducer = (store, reducer) => store.reducer = reducer; -}; - -/* deprecated, use hooks API instead */ -module Lense = { - type state('reductiveState) = { - reductiveState: option('reductiveState), - unsubscribe: option(unit => unit), - }; - type action = - | UpdateState - | AddListener(action => unit); - let createMake = - ( - ~name="Lense", - ~lense: 'state => 'lense, - store: Store.t('action, 'state), - ) => { - let innerComponent = ReasonReact.reducerComponent(name); - let make = - ( - ~component: - ( - ~state: 'lense, - ~dispatch: 'action => unit, - array(ReasonReact.reactElement) - ) => - ReasonReact.component('a, 'b, 'c), - _children: array(ReasonReact.reactElement), - ) - : ReasonReact.component( - state('lense), - ReasonReact.noRetainedProps, - action, - ) => { - ...innerComponent, - initialState: () => { - reductiveState: Some(lense(Store.getState(store))), - unsubscribe: None, - }, - reducer: (action, state) => - switch (action) { - | AddListener(send) => - ReasonReact.Update({ - unsubscribe: - Some(Store.subscribe(store, _ => send(UpdateState))), - reductiveState: Some(lense(Store.getState(store))), - }) - | UpdateState => - lense(Store.getState(store)) - |. Some - |. Belt.Option.eq(state.reductiveState, (a,b) => a === b) - ? ReasonReact.NoUpdate - : ReasonReact.Update({ - ...state, - reductiveState: Some(lense(Store.getState(store))), - }) - }, - didMount: ({send}) => send(AddListener(send)), - willUnmount: ({state}) => - switch (state.unsubscribe) { - | Some(unsubscribe) => unsubscribe() - | None => () - }, - render: ({state}) => - switch (state.reductiveState) { - | None => ReasonReact.null - | Some(state) => - ReasonReact.element( - component(~state, ~dispatch=Store.dispatch(store), [||]), - ) - }, - }; - make; - }; -}; - -/* deprecated, use provider from reductiveContext.re */ -module Provider = { - type state('reductiveState) = Lense.state('reductiveState); - type action = Lense.action; - let createMake = (~name="Provider", store: Store.t('action, 'state)) => - Lense.createMake(~name, ~lense=s => s, store); -}; - -/*** These are all visible apis of Redux that aren't needed in Reason. - * When used, build tools will provide explanation of alternatives. - * (see .rei for those) - */ -let compose = _ => (); - -let combineReducers = _ => (); - -let applyMiddleware = _ => (); - -let bindActionCreators = (actions, dispatch) => - List.map((action, ()) => dispatch(action), actions); - -type store('action, 'state) = Store.t('action, 'state); -type reducer('action, 'state) = ('state, 'action) => 'state; - -type middleware('action, 'state) = - (store('action, 'state), 'action => unit, 'action) => unit; - -type storeCreator('action, 'state) = - ( - ~reducer: reducer('action, 'state), - ~preloadedState: 'state, - ~enhancer: middleware('action, 'state)=?, - unit - ) => - store('action, 'state); - -type storeEnhancer('action, 'state) = - storeCreator('action, 'state) => storeCreator('action, 'state); - -type liftedStoreEnhancer('action, 'state, 'enhancedAction, 'enhancedState) = - ( - ~reducer: reducer('action, 'state), - ~preloadedState: 'enhancedState, - ~enhancer: middleware('enhancedAction, 'enhancedState)=?, - unit - ) => - store('enhancedAction, 'enhancedState); \ No newline at end of file diff --git a/src/reductive.rei b/src/reductive.rei deleted file mode 100644 index a331c35..0000000 --- a/src/reductive.rei +++ /dev/null @@ -1,163 +0,0 @@ -module Store: { - type t('action, 'state); - let create: - ( - ~reducer: ('state, 'action) => 'state, - ~preloadedState: 'state, - ~enhancer: (t('action, 'state), 'action => unit, 'action) => unit=?, - unit - ) => - t('action, 'state); - let unsubscribe: (t('action, 'state), unit => unit, unit) => unit; - let subscribe: (t('action, 'state), unit => unit, unit) => unit; - /* skips all middleware and applies an update directly to the store */ - let nativeDispatch: (t('action, 'state), 'action) => unit; - let dispatch: (t('action, 'state), 'action) => unit; - let getState: t('action, 'state) => 'state; - let replaceReducer: - (t('action, 'state), ('state, 'action) => 'state) => unit; -}; - -module Lense: { - type state('reductiveState); - type action = - | UpdateState - | AddListener(action => unit); - - [@deprecated "Legacy API, prefer the new hooks API with jsx 3"] - let createMake: - ( - ~name: string=?, - ~lense: 'state => 'lense, - Store.t('action, 'state), - ~component: ( - ~state: 'lense, - ~dispatch: 'action => unit, - array(ReasonReact.reactElement) - ) => - ReasonReact.component('a, 'b, 'c), - array(ReasonReact.reactElement) - ) => - ReasonReact.component(state('lense), ReasonReact.noRetainedProps, action); -}; - -module Provider: { - type state('reductiveState) = Lense.state('reductiveState); - type action = Lense.action; - - [@deprecated "Legacy API, prefer the new hooks API with jsx 3"] - let createMake: - ( - ~name: string=?, - Store.t('action, 'state), - ~component: ( - ~state: 'state, - ~dispatch: 'action => unit, - array(ReasonReact.reactElement) - ) => - ReasonReact.component('a, 'b, 'c), - array(ReasonReact.reactElement) - ) => - ReasonReact.component(state('state), ReasonReact.noRetainedProps, action); -}; - -/*** These are all visible apis of Redux that aren't needed in Reason. - * When used, build tools will provide explanation of alternatives. - */ -[@ocaml.deprecated - {| -Use the |> as an infix operator to chain the -result of one function into another: - -`compose(f, g, h)(x)` -in JS goes to -`x |> h |> g |> f` -in Reason. -|} -] -let compose: _ => unit; - -[@ocaml.deprecated - {| -combineReducers uses some introspection to determine -the shape of your state. Instead, consider a declarative pattern like: - -type counterAction = -| Increment -| Decrement; -type stringAction = -| A -| B; -type action = -| StringAction stringAction -| CounterAction counterAction; -type state = {string, counter}; - -let combinedReducer state action => { -| StringAction action => {...state, string: stringReducer state action} -| CounterAction action => {...state, counter: counterReducer state action} -}; - -this pattern gives you full control over the shape of your state. -|} -] -let combineReducers: _ => unit; - -[@ocaml.deprecated - {| -The enhancer attribute in Redux allows you -to provide a custom dispatch method (to perform more -actions before or after the dispatch function). You can simply pass in -a function directly which handles the exact actions you're looking for. - -To chain middlewares you can do something like: - -let thunkedLoggedTimeTravelLogger store next => - Middleware.thunk store @@ - Middleware.logger store @@ - Middleware.timeTravel store @@ - next; -|} -] -let applyMiddleware: _ => unit; - -[@ocaml.deprecated - {| -bindActionCreators is not as useful in Reason, -since action creators are types, not functions. -The code is implemented as: - -let bindActionCreators actions dispatch => -List.map (fun action () => dispatch action) actions; - -Instead - you are free to build the action data type at dispatch time. -|} -] -let bindActionCreators: (list('a), 'a => 'b) => list(unit => 'b); - -type store('action, 'state) = Store.t('action, 'state); -type reducer('action, 'state) = ('state, 'action) => 'state; - -type middleware('action, 'state) = - (store('action, 'state), 'action => unit, 'action) => unit; - -type storeCreator('action, 'state) = - ( - ~reducer: reducer('action, 'state), - ~preloadedState: 'state, - ~enhancer: middleware('action, 'state)=?, - unit - ) => - store('action, 'state); - -type storeEnhancer('action, 'state) = - storeCreator('action, 'state) => storeCreator('action, 'state); - -type liftedStoreEnhancer('action, 'state, 'enhancedAction, 'enhancedState) = - ( - ~reducer: reducer('action, 'state), - ~preloadedState: 'enhancedState, - ~enhancer: middleware('enhancedAction, 'enhancedState)=?, - unit - ) => - store('enhancedAction, 'enhancedState); \ No newline at end of file diff --git a/src/reductive.res b/src/reductive.res new file mode 100644 index 0000000..739d63c --- /dev/null +++ b/src/reductive.res @@ -0,0 +1,138 @@ +module Store = { + type rec t<'action, 'state> = { + mutable state: 'state, + mutable reducer: ('state, 'action) => 'state, + mutable listeners: list unit>, + customDispatcher: option<(t<'action, 'state>, 'action => unit, 'action) => unit>, + } + let create = (~reducer, ~preloadedState, ~enhancer=?, ()) => + switch (preloadedState, enhancer, reducer) { + | (preloadedState, None, reducer) => { + state: preloadedState, + listeners: list{}, + reducer: reducer, + customDispatcher: None, + } + | (preloadedState, Some(enhancer), reducer) => { + state: preloadedState, + listeners: list{}, + reducer: reducer, + customDispatcher: Some(enhancer), + } + } + let unsubscribe = (store, listener, ()) => + store.listeners = List.filter(l => listener !== l, store.listeners) + let subscribe = (store, listener) => { + store.listeners = list{listener, ...store.listeners} + unsubscribe(store, listener) + } + let nativeDispatch = (store, action) => { + store.state = store.reducer(store.state, action) + List.iter(listener => listener(), store.listeners) + } + let dispatch = (store, action) => + switch store.customDispatcher { + | Some(customDispatcher) => customDispatcher(store, nativeDispatch(store), action) + | None => nativeDispatch(store, action) + } + let getState = store => store.state + let replaceReducer = (store, reducer) => store.reducer = reducer +} + +/* deprecated, use hooks API instead */ +module Lense = { + type state<'reductiveState> = { + reductiveState: option<'reductiveState>, + unsubscribe: option unit>, + } + type rec action = + | UpdateState + | AddListener(action => unit) + let createMake = (~name="Lense", ~lense: 'state => 'lense, store: Store.t<'action, 'state>) => { + let innerComponent = ReasonReact.reducerComponent(name) + let make = ( + ~component: ( + ~state: 'lense, + ~dispatch: 'action => unit, + array, + ) => ReasonReact.component<'a, 'b, 'c>, + _children: array, + ): ReasonReact.component, ReasonReact.noRetainedProps, action> => { + ...innerComponent, + initialState: () => { + reductiveState: Some(lense(Store.getState(store))), + unsubscribe: None, + }, + reducer: (action, state) => + switch action { + | AddListener(send) => + ReasonReact.Update({ + unsubscribe: Some(Store.subscribe(store, _ => send(UpdateState))), + reductiveState: Some(lense(Store.getState(store))), + }) + | UpdateState => + lense(Store.getState(store)) + ->Some + ->Belt.Option.eq(state.reductiveState, (a, b) => a === b) + ? ReasonReact.NoUpdate + : ReasonReact.Update({ + ...state, + reductiveState: Some(lense(Store.getState(store))), + }) + }, + didMount: ({send}) => send(AddListener(send)), + willUnmount: ({state}) => + switch state.unsubscribe { + | Some(unsubscribe) => unsubscribe() + | None => () + }, + render: ({state}) => + switch state.reductiveState { + | None => ReasonReact.null + | Some(state) => ReasonReact.element(component(~state, ~dispatch=Store.dispatch(store), [])) + }, + } + make + } +} + +/* deprecated, use provider from reductiveContext.re */ +module Provider = { + type state<'reductiveState> = Lense.state<'reductiveState> + type action = Lense.action + let createMake = (~name="Provider", store: Store.t<'action, 'state>) => + Lense.createMake(~name, ~lense=s => s, store) +} + +/* ** These are all visible apis of Redux that aren't needed in Reason. + * When used, build tools will provide explanation of alternatives. + * (see .rei for those) + */ +let compose = _ => () + +let combineReducers = _ => () + +let applyMiddleware = _ => () + +let bindActionCreators = (actions, dispatch) => List.map((action, ()) => dispatch(action), actions) + +type store<'action, 'state> = Store.t<'action, 'state> +type reducer<'action, 'state> = ('state, 'action) => 'state + +type middleware<'action, 'state> = (store<'action, 'state>, 'action => unit, 'action) => unit + +type storeCreator<'action, 'state> = ( + ~reducer: reducer<'action, 'state>, + ~preloadedState: 'state, + ~enhancer: middleware<'action, 'state>=?, + unit, +) => store<'action, 'state> + +type storeEnhancer<'action, 'state> = storeCreator<'action, 'state> => storeCreator<'action, 'state> + +type liftedStoreEnhancer<'action, 'state, 'enhancedAction, 'enhancedState> = ( + ~reducer: reducer<'action, 'state>, + ~preloadedState: 'enhancedState, + ~enhancer: middleware<'enhancedAction, 'enhancedState>=?, + unit, +) => store<'enhancedAction, 'enhancedState> diff --git a/src/reductive.resi b/src/reductive.resi new file mode 100644 index 0000000..dbdbc79 --- /dev/null +++ b/src/reductive.resi @@ -0,0 +1,140 @@ +module Store: { + type t<'action, 'state> + let create: ( + ~reducer: ('state, 'action) => 'state, + ~preloadedState: 'state, + ~enhancer: (t<'action, 'state>, 'action => unit, 'action) => unit=?, + unit, + ) => t<'action, 'state> + let unsubscribe: (t<'action, 'state>, unit => unit, unit) => unit + let subscribe: (t<'action, 'state>, unit => unit, unit) => unit + /* skips all middleware and applies an update directly to the store */ + let nativeDispatch: (t<'action, 'state>, 'action) => unit + let dispatch: (t<'action, 'state>, 'action) => unit + let getState: t<'action, 'state> => 'state + let replaceReducer: (t<'action, 'state>, ('state, 'action) => 'state) => unit +} + +module Lense: { + type state<'reductiveState> + type rec action = + | UpdateState + | AddListener(action => unit) + + @deprecated("Legacy API, prefer the new hooks API with jsx 3") + let createMake: ( + ~name: string=?, + ~lense: 'state => 'lense, + Store.t<'action, 'state>, + ~component: ( + ~state: 'lense, + ~dispatch: 'action => unit, + array, + ) => ReasonReact.component<'a, 'b, 'c>, + array, + ) => ReasonReact.component, ReasonReact.noRetainedProps, action> +} + +module Provider: { + type state<'reductiveState> = Lense.state<'reductiveState> + type action = Lense.action + + @deprecated("Legacy API, prefer the new hooks API with jsx 3") + let createMake: ( + ~name: string=?, + Store.t<'action, 'state>, + ~component: ( + ~state: 'state, + ~dispatch: 'action => unit, + array, + ) => ReasonReact.component<'a, 'b, 'c>, + array, + ) => ReasonReact.component, ReasonReact.noRetainedProps, action> +} + +/* ** These are all visible apis of Redux that aren't needed in Reason. + * When used, build tools will provide explanation of alternatives. + */ +@ocaml.deprecated(` +Use the |> as an infix operator to chain the +result of one function into another: + +\`compose(f, g, h)(x)\` +in JS goes to +\`x |> h |> g |> f\` +in Reason. +`) +let compose: _ => unit + +@ocaml.deprecated(` +combineReducers uses some introspection to determine +the shape of your state. Instead, consider a declarative pattern like: + +type counterAction = +| Increment +| Decrement; +type stringAction = +| A +| B; +type action = +| StringAction stringAction +| CounterAction counterAction; +type state = {string, counter}; + +let combinedReducer state action => { +| StringAction action => {...state, string: stringReducer state action} +| CounterAction action => {...state, counter: counterReducer state action} +}; + +this pattern gives you full control over the shape of your state. +`) +let combineReducers: _ => unit + +@ocaml.deprecated(` +The enhancer attribute in Redux allows you +to provide a custom dispatch method (to perform more +actions before or after the dispatch function). You can simply pass in +a function directly which handles the exact actions you're looking for. + +To chain middlewares you can do something like: + +let thunkedLoggedTimeTravelLogger store next => + Middleware.thunk store @@ + Middleware.logger store @@ + Middleware.timeTravel store @@ + next; +`) +let applyMiddleware: _ => unit + +@ocaml.deprecated(` +bindActionCreators is not as useful in Reason, +since action creators are types, not functions. +The code is implemented as: + +let bindActionCreators actions dispatch => +List.map (fun action () => dispatch action) actions; + +Instead - you are free to build the action data type at dispatch time. +`) +let bindActionCreators: (list<'a>, 'a => 'b) => list 'b> + +type store<'action, 'state> = Store.t<'action, 'state> +type reducer<'action, 'state> = ('state, 'action) => 'state + +type middleware<'action, 'state> = (store<'action, 'state>, 'action => unit, 'action) => unit + +type storeCreator<'action, 'state> = ( + ~reducer: reducer<'action, 'state>, + ~preloadedState: 'state, + ~enhancer: middleware<'action, 'state>=?, + unit, +) => store<'action, 'state> + +type storeEnhancer<'action, 'state> = storeCreator<'action, 'state> => storeCreator<'action, 'state> + +type liftedStoreEnhancer<'action, 'state, 'enhancedAction, 'enhancedState> = ( + ~reducer: reducer<'action, 'state>, + ~preloadedState: 'enhancedState, + ~enhancer: middleware<'enhancedAction, 'enhancedState>=?, + unit, +) => store<'enhancedAction, 'enhancedState> diff --git a/src/reductiveContext.re b/src/reductiveContext.re deleted file mode 100644 index ff4199e..0000000 --- a/src/reductiveContext.re +++ /dev/null @@ -1,63 +0,0 @@ -module type Config = { - type state; - type action; -}; - -module Make = (Config: Config) => { - open Subscription; - let storeContext = React.createContext(None); - - module ContextProvider = { - let make = React.Context.provider(storeContext); - let makeProps = - ( - ~value: option(Reductive.Store.t(Config.action, Config.state)), - ~children, - (), - ) => { - "value": value, - "children": children, - }; - }; - - module Provider = { - [@react.component] - let make = (~children, ~store) => { - children ; - }; - }; - - let useStore = () => { - let storeFromContext = React.useContext(storeContext); - switch (storeFromContext) { - | None => - failwith( - "Could not find reductive context value; please ensure the component is wrapped in a ", - ) - | Some(store) => store - }; - }; - - let useSelector = selector => { - let store = useStore(); - - let source = - React.useMemo2( - () => - { - subscribe: Reductive.Store.subscribe(store), - getCurrentValue: () => selector(Reductive.Store.getState(store)), - }, - (selector, store), - ); - - let selectedState = useSubscription(source); - - selectedState; - }; - - let useDispatch = () => { - let store = useStore(); - React.useCallback1(Reductive.Store.dispatch(store), [|store|]); - }; -}; diff --git a/src/reductiveContext.rei b/src/reductiveContext.rei deleted file mode 100644 index 7c11f5c..0000000 --- a/src/reductiveContext.rei +++ /dev/null @@ -1,34 +0,0 @@ -module type Config = { - type state; - type action; -}; -module Make: - (Config: Config) => - { - module Provider: { - [@bs.obj] - external makeProps: - ( - ~children: 'children, - ~store: Reductive.Store.t(Config.action, Config.state), - ~key: string=?, - unit - ) => - { - . - "children": 'children, - "store": Reductive.Store.t(Config.action, Config.state), - } = - ""; - let make: - { - . - "children": React.element, - "store": Reductive.Store.t(Config.action, Config.state), - } => - React.element; - }; - let useSelector: (Config.state => 'selectedState) => 'selectedState; - let useDispatch: (unit, Config.action) => unit; - let useStore: unit => Reductive.Store.t(Config.action, Config.state); - }; diff --git a/src/reductiveContext.res b/src/reductiveContext.res new file mode 100644 index 0000000..1399703 --- /dev/null +++ b/src/reductiveContext.res @@ -0,0 +1,57 @@ +module type Config = { + type state + type action +} + +module Make = (Config: Config) => { + open Subscription + let storeContext = React.createContext(None) + + module ContextProvider = { + let make = React.Context.provider(storeContext) + let makeProps = ( + ~value: option>, + ~children, + (), + ) => + { + "value": value, + "children": children, + } + } + + module Provider = { + @react.component + let make = (~children, ~store) => + children + } + + let useStore = () => { + let storeFromContext = React.useContext(storeContext) + switch storeFromContext { + | None => + failwith( + "Could not find reductive context value; please ensure the component is wrapped in a ", + ) + | Some(store) => store + } + } + + let useSelector = selector => { + let store = useStore() + + let source = React.useMemo2(() => { + subscribe: Reductive.Store.subscribe(store), + getCurrentValue: () => selector(Reductive.Store.getState(store)), + }, (selector, store)) + + let selectedState = useSubscription(source) + + selectedState + } + + let useDispatch = () => { + let store = useStore() + React.useCallback1(Reductive.Store.dispatch(store), [store]) + } +} diff --git a/src/reductiveContext.resi b/src/reductiveContext.resi new file mode 100644 index 0000000..e3193a8 --- /dev/null +++ b/src/reductiveContext.resi @@ -0,0 +1,23 @@ +module type Config = { + type state + type action +} +module Make: (Config: Config) => +{ + module Provider: { + @obj + external makeProps: ( + ~children: 'children, + ~store: Reductive.Store.t, + ~key: string=?, + unit, + ) => {"children": 'children, "store": Reductive.Store.t} = "" + let make: { + "children": React.element, + "store": Reductive.Store.t, + } => React.element + } + let useSelector: (Config.state => 'selectedState) => 'selectedState + let useDispatch: (unit, Config.action) => unit + let useStore: unit => Reductive.Store.t +} diff --git a/src/subscription.re b/src/subscription.re deleted file mode 100644 index cffab86..0000000 --- a/src/subscription.re +++ /dev/null @@ -1,13 +0,0 @@ -[@bs.deriving {jsConverter: newType}] -type source('a) = { - subscribe: (unit => unit, unit) => unit, - getCurrentValue: unit => 'a, -}; - -[@bs.module "use-subscription"] -external useSubscriptionJs: abs_source('a) => 'a = "useSubscription"; - -let useSubscription = source => { - let sourceJs = React.useMemo1(() => sourceToJs(source), [|source|]); - useSubscriptionJs(sourceJs); -}; diff --git a/src/subscription.res b/src/subscription.res new file mode 100644 index 0000000..1a7a932 --- /dev/null +++ b/src/subscription.res @@ -0,0 +1,13 @@ +@deriving({jsConverter: newType}) +type source<'a> = { + subscribe: (unit => unit, unit) => unit, + getCurrentValue: unit => 'a, +} + +@module("use-subscription") +external useSubscriptionJs: abs_source<'a> => 'a = "useSubscription" + +let useSubscription = source => { + let sourceJs = React.useMemo1(() => sourceToJs(source), [source]) + useSubscriptionJs(sourceJs) +} diff --git a/yarn.lock b/yarn.lock index eec0af9..27f619a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -958,16 +958,6 @@ bs-dom-testing-library@^0.4.1: dependencies: dom-testing-library "^3.19.0" -bs-platform@^2.2.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/bs-platform/-/bs-platform-2.2.3.tgz#d905ae10a5f3621e6a739041dfa0b58483a2174f" - integrity sha1-2QWuEKXzYh5qc5BB36C1hIOiF08= - -bs-platform@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/bs-platform/-/bs-platform-5.0.6.tgz#88c13041fb020479800de3d82c680bf971091425" - integrity sha512-6Boa2VEcWJp2WJr38L7bp3J929nYha7gDarjxb070jWzgfPJ/WbzjipmSfnu2eqqk1MfjEIpBipbPz6n1NISwA== - bs-react-testing-library@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/bs-react-testing-library/-/bs-react-testing-library-0.5.0.tgz#3906666ffc6dfd22ff25ae01bfa8f5ba65c30db0" @@ -2202,12 +2192,6 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -"immutable-re@https://github.com/facebookincubator/immutable-re.git": - version "0.0.15" - resolved "https://github.com/facebookincubator/immutable-re.git#d55f65f7fc638ac17cfdd8064a0401d4cde5aad2" - dependencies: - bs-platform "^2.2.1" - import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -4175,6 +4159,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +rescript@^9.1.4: + version "9.1.4" + resolved "https://registry.yarnpkg.com/rescript/-/rescript-9.1.4.tgz#1eb126f98d6c16942c0bf0df67c050198e580515" + integrity sha512-aXANK4IqecJzdnDpJUsU6pxMViCR5ogAxzuqS0mOr8TloMnzAjJFu63fjD6LCkWrKAhlMkFFzQvVQYaAaVkFXw== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" From bd0437122ff40b78708b9c3f4caf4d59f20f79fc Mon Sep 17 00:00:00 2001 From: Amirali Esmaeili Date: Thu, 29 Jul 2021 23:21:53 +0430 Subject: [PATCH 2/9] Remove bs config jsx at file level --- __tests__/reductiveContext_test.res | 1 - examples/immutable/immutableEntry.res | 1 - examples/immutable/immutableRenderer.res | 2 -- examples/react/reactEntry.res | 1 - examples/react/thunkedStore.res | 1 - examples/render/renderEntry.res | 1 - examples/todomvc/todomvcEntry.res | 1 - 7 files changed, 8 deletions(-) diff --git a/__tests__/reductiveContext_test.res b/__tests__/reductiveContext_test.res index a863a68..eb6ea4d 100644 --- a/__tests__/reductiveContext_test.res +++ b/__tests__/reductiveContext_test.res @@ -1,4 +1,3 @@ -@@bs.config({jsx: 3}) open Jest open Expect open ReasonHooksTestingLibrary diff --git a/examples/immutable/immutableEntry.res b/examples/immutable/immutableEntry.res index bc28554..b4238a5 100644 --- a/examples/immutable/immutableEntry.res +++ b/examples/immutable/immutableEntry.res @@ -1,4 +1,3 @@ -@@bs.config({jsx: 3}) ReactDOMRe.renderToElementWithId( diff --git a/examples/immutable/immutableRenderer.res b/examples/immutable/immutableRenderer.res index 22f4cda..f7ea5f7 100644 --- a/examples/immutable/immutableRenderer.res +++ b/examples/immutable/immutableRenderer.res @@ -1,5 +1,3 @@ -@@bs.config({jsx: 3}) - let stateSelector = state => state @react.component let make = () => { diff --git a/examples/react/reactEntry.res b/examples/react/reactEntry.res index 1db5df2..e18c3fd 100644 --- a/examples/react/reactEntry.res +++ b/examples/react/reactEntry.res @@ -1,4 +1,3 @@ -@@bs.config({jsx: 3}) ReactDOMRe.renderToElementWithId( , "index", diff --git a/examples/react/thunkedStore.res b/examples/react/thunkedStore.res index dc44c32..eea3f87 100644 --- a/examples/react/thunkedStore.res +++ b/examples/react/thunkedStore.res @@ -1,4 +1,3 @@ -@@bs.config({jsx: 3}) open SimpleStore type stringAction = diff --git a/examples/render/renderEntry.res b/examples/render/renderEntry.res index bee94bc..be34c5b 100644 --- a/examples/render/renderEntry.res +++ b/examples/render/renderEntry.res @@ -1,4 +1,3 @@ -@@bs.config({jsx: 3}) /* * Example using multiple components to represent different slices of state. * Updating the state exposed by one component should not cause the other diff --git a/examples/todomvc/todomvcEntry.res b/examples/todomvc/todomvcEntry.res index 5e59654..f29084e 100644 --- a/examples/todomvc/todomvcEntry.res +++ b/examples/todomvc/todomvcEntry.res @@ -1,4 +1,3 @@ -@@bs.config({jsx: 3}) @ocaml.doc(" * Example of a todo list, loosely based on the redux example, mixed with * some code from Jared Forsyth's ReasonReact tutorial, and Cheng Lou's From 8b36c6bc9b457070ba28f9deb52b90e95beffe70 Mon Sep 17 00:00:00 2001 From: Amirali Esmaeili Date: Thu, 29 Jul 2021 23:52:28 +0430 Subject: [PATCH 3/9] Remove legacy stuff --- src/context.bs.js | 17 ----- src/reductive.bs.js | 173 -------------------------------------------- src/reductive.res | 65 ----------------- src/reductive.resi | 37 ---------- 4 files changed, 292 deletions(-) delete mode 100644 src/context.bs.js delete mode 100644 src/reductive.bs.js diff --git a/src/context.bs.js b/src/context.bs.js deleted file mode 100644 index c8e6ef6..0000000 --- a/src/context.bs.js +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by BUCKLESCRIPT VERSION 5.0.6, PLEASE EDIT WITH CARE -'use strict'; - -var React = require("react"); - -function Make(Config) { - var context = React.createContext(Config[/* defaultValue */0]); - var make = context.Provider; - var Provider = /* module */[/* make */make]; - return /* module */[ - /* context */context, - /* Provider */Provider - ]; -} - -exports.Make = Make; -/* react Not a pure module */ diff --git a/src/reductive.bs.js b/src/reductive.bs.js deleted file mode 100644 index 9553bca..0000000 --- a/src/reductive.bs.js +++ /dev/null @@ -1,173 +0,0 @@ -// Generated by BUCKLESCRIPT VERSION 5.0.6, PLEASE EDIT WITH CARE -'use strict'; - -var List = require("bs-platform/lib/js/list.js"); -var Curry = require("bs-platform/lib/js/curry.js"); -var React = require("react"); -var Caml_option = require("bs-platform/lib/js/caml_option.js"); -var Context$Reductive = require("./context.bs.js"); - -function create(reducer, preloadedState, enhancer, param) { - if (enhancer !== undefined) { - return /* record */[ - /* state */preloadedState, - /* reducer */reducer, - /* listeners : [] */0, - /* customDispatcher */enhancer - ]; - } else { - return /* record */[ - /* state */preloadedState, - /* reducer */reducer, - /* listeners : [] */0, - /* customDispatcher */undefined - ]; - } -} - -function unsubscribe(store, listener, param) { - store[/* listeners */2] = List.filter((function (l) { - return listener !== l; - }))(store[/* listeners */2]); - return /* () */0; -} - -function subscribe(store, listener) { - store[/* listeners */2] = /* :: */[ - listener, - store[/* listeners */2] - ]; - return (function (param) { - return unsubscribe(store, listener, param); - }); -} - -function nativeDispatch(store, action) { - store[/* state */0] = Curry._2(store[/* reducer */1], store[/* state */0], action); - return List.iter((function (listener) { - return Curry._1(listener, /* () */0); - }), store[/* listeners */2]); -} - -function dispatch(store, action) { - var match = store[/* customDispatcher */3]; - if (match !== undefined) { - return Curry._3(match, store, (function (param) { - return nativeDispatch(store, param); - }), action); - } else { - return nativeDispatch(store, action); - } -} - -function getState(store) { - return store[/* state */0]; -} - -function replaceReducer(store, reducer) { - store[/* reducer */1] = reducer; - return /* () */0; -} - -var Store = /* module */[ - /* create */create, - /* unsubscribe */unsubscribe, - /* subscribe */subscribe, - /* nativeDispatch */nativeDispatch, - /* dispatch */dispatch, - /* getState */getState, - /* replaceReducer */replaceReducer -]; - -function compose(param) { - return /* () */0; -} - -function combineReducers(param) { - return /* () */0; -} - -function applyMiddleware(param) { - return /* () */0; -} - -function bindActionCreators(actions, dispatch) { - return List.map((function (action, param) { - return Curry._1(dispatch, action); - }), actions); -} - -function Make(funarg) { - var defaultValue = funarg[/* store */0]; - var include = Context$Reductive.Make(/* module */[/* defaultValue */defaultValue]); - var context = include[0]; - var Provider = include[1]; - var Reductive$Make$Provider = function (Props) { - var children = Props.children; - return React.createElement(Provider[/* make */0], { - value: funarg[/* store */0], - children: children - }); - }; - var Provider$1 = /* module */[/* make */Reductive$Make$Provider]; - var useSelector = function (selector) { - var storeFromContext = React.useContext(context); - var match = React.useReducer((function (s, param) { - return s + 1 | 0; - }), 0); - var forceRerender = match[1]; - var latestSelectedState = React.useRef(undefined); - var latestSelector = React.useRef(selector); - React.useLayoutEffect((function () { - latestSelector.current = selector; - var newSelectedState = Curry._1(selector, funarg[/* store */0][/* state */0]); - latestSelectedState.current = Caml_option.some(newSelectedState); - return undefined; - }), /* array */[selector]); - React.useLayoutEffect((function () { - var checkForUpdates = function (param) { - var newSelectedState = Curry._1(selector, funarg[/* store */0][/* state */0]); - var match = latestSelectedState.current; - var hasStateChanged = match !== undefined ? newSelectedState !== Caml_option.valFromOption(match) : true; - if (hasStateChanged) { - latestSelectedState.current = Caml_option.some(newSelectedState); - return Curry._1(forceRerender, /* () */0); - } else { - return 0; - } - }; - return subscribe(storeFromContext, checkForUpdates); - }), /* array */[storeFromContext]); - var currentStoreState = funarg[/* store */0][/* state */0]; - if (latestSelector.current !== selector) { - return Curry._1(selector, currentStoreState); - } else { - var match$1 = latestSelectedState.current; - if (match$1 !== undefined) { - return Caml_option.valFromOption(match$1); - } else { - return Curry._1(selector, currentStoreState); - } - } - }; - var useDispatch = function (param) { - var partial_arg = React.useContext(context); - return (function (param) { - return dispatch(partial_arg, param); - }); - }; - return [ - [context], - Provider$1, - useSelector, - useDispatch - ]; -} - -exports.Store = Store; -exports.Make = Make; -exports.compose = compose; -exports.combineReducers = combineReducers; -exports.applyMiddleware = applyMiddleware; -exports.bindActionCreators = bindActionCreators; -/* react Not a pure module */ diff --git a/src/reductive.res b/src/reductive.res index 739d63c..7a24050 100644 --- a/src/reductive.res +++ b/src/reductive.res @@ -39,71 +39,6 @@ module Store = { let replaceReducer = (store, reducer) => store.reducer = reducer } -/* deprecated, use hooks API instead */ -module Lense = { - type state<'reductiveState> = { - reductiveState: option<'reductiveState>, - unsubscribe: option unit>, - } - type rec action = - | UpdateState - | AddListener(action => unit) - let createMake = (~name="Lense", ~lense: 'state => 'lense, store: Store.t<'action, 'state>) => { - let innerComponent = ReasonReact.reducerComponent(name) - let make = ( - ~component: ( - ~state: 'lense, - ~dispatch: 'action => unit, - array, - ) => ReasonReact.component<'a, 'b, 'c>, - _children: array, - ): ReasonReact.component, ReasonReact.noRetainedProps, action> => { - ...innerComponent, - initialState: () => { - reductiveState: Some(lense(Store.getState(store))), - unsubscribe: None, - }, - reducer: (action, state) => - switch action { - | AddListener(send) => - ReasonReact.Update({ - unsubscribe: Some(Store.subscribe(store, _ => send(UpdateState))), - reductiveState: Some(lense(Store.getState(store))), - }) - | UpdateState => - lense(Store.getState(store)) - ->Some - ->Belt.Option.eq(state.reductiveState, (a, b) => a === b) - ? ReasonReact.NoUpdate - : ReasonReact.Update({ - ...state, - reductiveState: Some(lense(Store.getState(store))), - }) - }, - didMount: ({send}) => send(AddListener(send)), - willUnmount: ({state}) => - switch state.unsubscribe { - | Some(unsubscribe) => unsubscribe() - | None => () - }, - render: ({state}) => - switch state.reductiveState { - | None => ReasonReact.null - | Some(state) => ReasonReact.element(component(~state, ~dispatch=Store.dispatch(store), [])) - }, - } - make - } -} - -/* deprecated, use provider from reductiveContext.re */ -module Provider = { - type state<'reductiveState> = Lense.state<'reductiveState> - type action = Lense.action - let createMake = (~name="Provider", store: Store.t<'action, 'state>) => - Lense.createMake(~name, ~lense=s => s, store) -} - /* ** These are all visible apis of Redux that aren't needed in Reason. * When used, build tools will provide explanation of alternatives. * (see .rei for those) diff --git a/src/reductive.resi b/src/reductive.resi index dbdbc79..354ec5f 100644 --- a/src/reductive.resi +++ b/src/reductive.resi @@ -15,43 +15,6 @@ module Store: { let replaceReducer: (t<'action, 'state>, ('state, 'action) => 'state) => unit } -module Lense: { - type state<'reductiveState> - type rec action = - | UpdateState - | AddListener(action => unit) - - @deprecated("Legacy API, prefer the new hooks API with jsx 3") - let createMake: ( - ~name: string=?, - ~lense: 'state => 'lense, - Store.t<'action, 'state>, - ~component: ( - ~state: 'lense, - ~dispatch: 'action => unit, - array, - ) => ReasonReact.component<'a, 'b, 'c>, - array, - ) => ReasonReact.component, ReasonReact.noRetainedProps, action> -} - -module Provider: { - type state<'reductiveState> = Lense.state<'reductiveState> - type action = Lense.action - - @deprecated("Legacy API, prefer the new hooks API with jsx 3") - let createMake: ( - ~name: string=?, - Store.t<'action, 'state>, - ~component: ( - ~state: 'state, - ~dispatch: 'action => unit, - array, - ) => ReasonReact.component<'a, 'b, 'c>, - array, - ) => ReasonReact.component, ReasonReact.noRetainedProps, action> -} - /* ** These are all visible apis of Redux that aren't needed in Reason. * When used, build tools will provide explanation of alternatives. */ From 2027096a2c76c995c46378b81b39342b17401e99 Mon Sep 17 00:00:00 2001 From: Amirali Esmaeili Date: Fri, 30 Jul 2021 00:10:23 +0430 Subject: [PATCH 4/9] Fix examples --- examples/basic/basicEntry.res | 4 ++-- examples/immutable/immutableEntry.res | 11 +++++----- examples/react/dataRenderer.res | 14 ++++++------ examples/react/reactEntry.res | 7 +++--- examples/render-v2/renderEntryV2.res | 8 +++---- examples/render/renderEntry.res | 18 ++++++---------- examples/todomvc/todomvcEntry.res | 31 ++++++++++++--------------- 7 files changed, 44 insertions(+), 49 deletions(-) diff --git a/examples/basic/basicEntry.res b/examples/basic/basicEntry.res index de86e69..30694b0 100644 --- a/examples/basic/basicEntry.res +++ b/examples/basic/basicEntry.res @@ -12,7 +12,7 @@ dispatch(Decrement) dispatch(Increment) -Reductive.Store.subscribe(SimpleStore.store, () => +let _: unit => unit = Reductive.Store.subscribe(SimpleStore.store, () => Js.log(Reductive.Store.getState(SimpleStore.store)) ) @@ -35,7 +35,7 @@ dispatch(Increment) dispatch(Increment) -Reductive.Store.subscribe(ComplexStore.store, () => +let _: unit => unit = Reductive.Store.subscribe(ComplexStore.store, () => Js.log(Reductive.Store.getState(ComplexStore.store)) ) diff --git a/examples/immutable/immutableEntry.res b/examples/immutable/immutableEntry.res index b4238a5..83ba12c 100644 --- a/examples/immutable/immutableEntry.res +++ b/examples/immutable/immutableEntry.res @@ -1,6 +1,7 @@ -ReactDOMRe.renderToElementWithId( - - - , - "index", +ReactDOM.querySelector("#index")->Belt.Option.forEach( + ReactDOM.render( + + + , + ), ) diff --git a/examples/react/dataRenderer.res b/examples/react/dataRenderer.res index 074a678..6ba09e0 100644 --- a/examples/react/dataRenderer.res +++ b/examples/react/dataRenderer.res @@ -22,22 +22,22 @@ let make = () => { )
    -
    {ReasonReact.string("string: " ++ state.notACounter)}
    -
    {ReasonReact.string("counter: " ++ string_of_int(state.counter))}
    +
    {React.string("string: " ++ state.notACounter)}
    +
    {React.string("counter: " ++ string_of_int(state.counter))}
    } diff --git a/examples/react/reactEntry.res b/examples/react/reactEntry.res index e18c3fd..86fa6dc 100644 --- a/examples/react/reactEntry.res +++ b/examples/react/reactEntry.res @@ -1,4 +1,5 @@ -ReactDOMRe.renderToElementWithId( - , - "index", +ReactDOM.querySelector("#index")->Belt.Option.forEach( + ReactDOM.render( + , + ), ) diff --git a/examples/render-v2/renderEntryV2.res b/examples/render-v2/renderEntryV2.res index 67bd30b..2c0424f 100644 --- a/examples/render-v2/renderEntryV2.res +++ b/examples/render-v2/renderEntryV2.res @@ -82,9 +82,9 @@ module ContentComponent = { let dispatch = Store.useDispatch() let state = Store.useSelector(Store.Selectors.contentState)
    -
    {ReasonReact.string("Content: " ++ state)}
    - - +
    {React.string("Content: " ++ state)}
    + +
    } } @@ -110,4 +110,4 @@ module RenderApp = {
    } -ReactDOMRe.renderToElementWithId(, "index") +ReactDOM.querySelector("#index")->Belt.Option.forEach(ReactDOM.render()) diff --git a/examples/render/renderEntry.res b/examples/render/renderEntry.res index be34c5b..f239359 100644 --- a/examples/render/renderEntry.res +++ b/examples/render/renderEntry.res @@ -72,9 +72,9 @@ module StringComponent = { let state = AppStore.useSelector(contentSelector)
    -
    {ReasonReact.string("Content: " ++ state)}
    - - +
    {React.string("Content: " ++ state)}
    + +
    } } @@ -88,13 +88,9 @@ module CounterComponent = { let state = AppStore.useSelector(counterSelector)
    -
    {ReasonReact.string("Counter: " ++ string_of_int(state))}
    - - +
    {React.string("Counter: " ++ string_of_int(state))}
    + +
    } } @@ -104,4 +100,4 @@ module RenderApp = { let make = () =>
    } -ReactDOMRe.renderToElementWithId(, "index") +ReactDOM.querySelector("#index")->Belt.Option.forEach(ReactDOM.render()) diff --git a/examples/todomvc/todomvcEntry.res b/examples/todomvc/todomvcEntry.res index f29084e..85b33ca 100644 --- a/examples/todomvc/todomvcEntry.res +++ b/examples/todomvc/todomvcEntry.res @@ -111,10 +111,10 @@ module TodoItem = { React.useEffect1(() => { Js.log("Inside effect ") - let optionRef = editFieldRef->React.Ref.current->Js.Nullable.toOption + let optionRef = editFieldRef.current->Js.Nullable.toOption switch (state.editing, optionRef) { | (true, Some(field)) => - let node = ReactDOMRe.domElementToObj(field) + let node = ReactDOM.domElementToObj(field) Js.log(node) ignore(node["focus"]()) @@ -151,11 +151,11 @@ module TodoItem = { checked=todo.completed onChange={_ => dispatch(ToggleItem(todo.id))} /> - +
    submit()} @@ -185,8 +185,8 @@ module TodoList = { onChange={_ => dispatch(ToggleAll(activeTodoCount > 0))} checked={todoCount > 0 && activeTodoCount === 0} /> - -
      {ReasonReact.array(Belt.List.toArray(todoItems))}
    + +
      {React.array(Belt.List.toArray(todoItems))}
    } } @@ -238,7 +238,7 @@ module FilterLink = { @react.component let make = (~filter, ~label, ~state: appState, ~dispatch) => { let className = filter == state.filter ? "selected" : "" -
  • dispatch(Show(filter))}> {ReasonReact.string(label)}
  • +
  • dispatch(Show(filter))}> {React.string(label)}
  • } } @@ -251,13 +251,13 @@ module Footer = { let clearButton = completedCount > 0 ? - : ReasonReact.null + : React.null