Introduction to State Management
Reframe is a flux-like library for uni-directional data flow. In this chapter we'll study the six-step Reframe loop.
Introduction to state management#
State management in React is a controversial issue. There are multiple competing models like MobX, React Context, and Flux.
In this chapter, we'll walk through the concepts and terminologies of two ways of managing the app state: Reframe and reactive atoms. The focus right now is to form a mental model, but in later chapters we'll realize the models.
We have already seen r/atom
in action. Ratoms are a simple yet powerful way to store state. We can have one or more ratoms, and components that listen to them. But as your app grows, keeping track of these atoms get hard. Performance will suffer as we have little control over the re-render cycle. If anything in the ratom changes, all listener components will be re-rendered.
Reframe#
Reframe is a Redux-like framework with a uni-directional data loop. It allows you to structure application state and enables optimal rendering. First released in 2015, it is now one of the most starred Clojure projects.
The authors of Reframe explain it as a 6-step process. Let's walk through the steps with an example.
Imagine you have a form with two input components - email and password - and you want to re-render the form each time a user types something. The skeleton of the component might look something like this:
(defn login-form []
[:input#email {:value "" :on-change #()}]
[:input#password {:type "password" :value "" :on-change #()}])
(This component won't work because the :value
is fixed and :on-change
is not implemented - just focus on the outline for now.)
Step 1 - Dispatching events#
The first step of the Reframe loop is to dispatch or emit an event
. This event could be:
a keystroke in an input field
a click of a button
a message on a WebSocket
mouse motion
etc
In the example above, the user typing in the input
field is the event we wish to capture.
To set the loop in motion, we use the reframe.core/dispatch
method:
(rf/dispatch [:event-id argument1 argument2 ])
dispatch
accepts a vector where the first element is the id of the event we want to dispatch. Data that we wish to pass along can be passed in the vector.
In more concrete terms, in the login-form
component, we wish to save the state in Reframe. We can modify the :on-change
handlers to dispatch
change events:
(defn login-form []
[:input#email {:value ""
:on-change (fn [e]
(rf/dispatch
[:handle-email-change (.. e -target -value)]))}]
[:input#password {:type "password" :value ""
:on-change #(rf/dispatch
[:handle-password-change (.. % -target -value)])}])
We used inline functions to dispatch
events each time the input changed. This is not the ideal approach but is easy to understand.
Both (fn [])
and #()
have the same behavior - both call dispatch
and set the Reframe loop in motion.
Step 2 - Event handlers#
For a dispatch
to be useful, we need to handle the dispatched event. This can be done using reframe.core/reg-event-fx
method (AKA register event effect method). reg-event-fx
takes two arguments: the event id and a pure handler function. Event ids are keywords and generally namespaced. :handle-email-change
and :handle-password-change
are examples of events that change the state of the app.
(rf/reg-event-fx
:handle-email-change ; event-id
(fn [_ _] ; handler function
))This handler function is called with two arguments each time the event is dispatched.
The first argument is the co-effects map - you can think of this as the current state of the universe. The second argument is the vector we passed to dispatch
:
(rf/reg-event-fx
:handle-email-change
(fn [cofx event]
))This is a good time to disclose that Reframe keeps the entire application state in one large map. This is generally referred to as app-db
. Internally, this map is a ratom.
This page is a preview of Tinycanva: Clojure for React Developers