Apps using Redux maintain their whole state in an object tree inside a single store. To manipulate
such states one has to emit actions
which are plain JavaScript objects containg the type
of
the given action and optionally other properties, like payload
etc.
To register a store we use createStore
and give it a reducer
and an optional state
as arguments.
In the example below register a new store by using two arguments counter
(the reducer)
and 1
(the initial state)
Hint: PureScript doesn't have functions that take more than one argument! I'm using JavaScript terms here because Redux is written in JavaScript.
store <- (createStore counter 1)
A reducer
is a function that takes a state
and an action
and returns a new state
. This is
how our reducer looks like. It's written as a lambda
function (a callback
in JS) which is always
indicated with an \
(it resembles the small greek lambda letter λ).
counter :: Int -> Action -> Int
counter = \v t -> case t.type of
"INCREMENT" -> v + 1
"DECREMENT" -> v - 1
_ -> v
Redux' Actions are just plain POJOs.
{ "type" : "INCREMENT", payload: "TEST INCR" }
The definition is located in src/Control/Monad/Eff/Redux/Redux.purs
together with our <a href=""http://www.purescript.org/learn/ffi/ target="_blank">FFI imports.
type Action a = {
"type" :: String
| a
}
[...]
foreign import createStore :: forall a b. (a -> Action b -> a) -> a -> ReduxEff Store
foreign import subscribe :: forall e. (Eff e Unit) -> Store -> ReduxEff Unit
foreign import dispatch :: forall a. Action a -> Store -> ReduxEff (Action a)
foreign import getState :: forall a. Store -> ReduxEff a
foreign import replaceReducer :: Reducer -> Store -> ReduxEff Unit
We also want to be informed about any state changes. Therefore we define yet another callback called numericListener
.
numericListener :: forall e. Store -> Eff (reduxM :: ReduxM, console :: CONSOLE | e) Unit
numericListener = \store -> do
currentState <- (getState store)
log ("STATE: " <> (unsafeCoerce currentState))
We register it by using Redux' subscribe
function.
(subscribe (numericListener store) store)
Hint: The argument store
is used to curry the numericListener
callback.
And finally we register two event handlers to react to button clicks. Here we're using RactiveJS and its proxy events.
This is not mandatory as you can use any other UI-Library or Framework instead.
on "increment-clicked" (onIncrementClicked ract) ract
on "decrement-clicked" (onDecrementClicked ract) ract
Our event handler functions have the same mechanics. Only their action
types are different.
First, we get our store reference from the RactiveJS component via get "store"
. Then we use Redux'
function dispatch
to, well, dispatch a new action of type INCREMENT
. Of course, we give it the
store
instance too.
Hint: PureScript natively supports JavaScript's objects.
That's why we can simply write { "type" : "INCREMENT" } without any extra calls or conversions. Just use the good old POJOs. 😄
onIncrementClicked = \r e -> do
store <- (get "store" r)
log "DISPATCH: INCREMENT"
action <- (dispatch { "type" : "INCREMENT", payload: "TEST INCR" } store)
pure unit
This is how the demo app works.
Basically, it's a combination of a predictable container (Redux) with real pure functions and hostility towards any side-effects (PureScript).