Introduction to React State Management

Reframe is a flux-like library for uni-directional data flow. In this chapter we'll study the six-step Reframe loop.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

Table of Contents

This lesson preview is part of the Tinycanva: Clojure for React Developers course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Tinycanva: Clojure for React Developers, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Tinycanva: Clojure for React Developers

State management in React is a controversial issue. There are multiple competing models like mobx, context and flux. In this chapter we will walk through two ways of managing app state, reframe and reactive atoms. Both of them include concepts and terminologies. The focus right now is to form a mental model. In later chapters we will realize these models. We have already seen our atom in action. Ratams are simple yet a powerful way to store state. We can have one or more ratams and components that listen to them. But as your app grows keeping track of these atoms get hurt. The performance too will suffer as we will have little control over the render cycle. If anything in the ratam changes all listener components will be re-rendered. Reframe is a Redux-like framework with a unidirectional data loop. It allows you to structure application state and side effects and enables optimal rendering. It is one of the most starred closure projects on github and was first released in 2015. The author of free frame explained it as a six step process. Let's walk through the steps with an example. Imagine you have a form with two components, email and password. You want to re-render the form each time a user types something. The skeleton of the component might look something like this. This component won't work obviously because the value is fixed and non-change is not implemented. Just focus on the outline for now. The first step of reframe loop is to dispatch or emit an event. This event could be a keystroke in an input field or a click of a button, a message on a web socket or anything else. In our example the user typing in the input field is the event we wish to capture. We use the reframe.core/dispatch method. Dispatch accepts a vector where the first element is the id of the event we want to dispatch and data that we wish to pass along can be passed as subsequent arguments of the vector. In more country terms, in the login form component, we wish to save the state in reframe so we can modify the onchange handlers to dispatch change events. We use inline functions to dispatch events each time the input changed. This is not the ideal way to go about it, just easy to understand. Both FN and hash parenthesis have the same behavior. Both call dispatch and set the reframe loop in motion. Handle email change and handle password change keywords are events that change the state of the app. Event IDs are keywords and are generally namespaced. For a dispatch to be useful, we need to handle the dispatch event. This can be done using the reframe.core/regeventfx method which is short for a register event effect. Register event effect takes two arguments, the event id and a pure 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 the co-effect map as the current state of the universe. The second argument is the vector we passed into dispatch. This is a good time to disclose that reframe keeps the entire application state in one large map. This is generally referred to app TB. Internally this map is a reactive atom. Event handlers are pure functions that return a map of effects to run. These effects change state or perform other side effects in a sequential way. Reframe maintains a queue of effects returned by all event handlers. All effects are guaranteed to run in order they were queued. In our example, we want the state of the app to change and reflect the latest value of the input. That is, we want our app DB to change to a new DB. We destructure the event to pull out the email and return the map with key DB and value new DB which we will define soon. All event handlers are required to return a map. The keys of the map are effects we want to run and the values are the argument to that effect. Keyword DB is a built-in effect that changes the value of global app TB. We can define custom effects for interaction with APIs, sockets, local storage, etc. The DB effect takes a map and resets the app DB to this new map. The entire app state is in one map. We need to fetch that, associate the new value of email and set it as new DB. This is where co-effects come into play. Co-effects is a map of all effects and holds the current value of app DB under the key DB. To compute the new DB, we can do this. Here we associated a new value to email in the current app DB from co-effects map and then returned an effect handler with key DB and the new value of the database. This code incues an instruction to change the value of email key in the app DB to the value dispatched in the event. It doesn't actually change the app DB. The built-in DB effect handler does that. Pure event handlers put effects on a queue. These are then applied in order and can be traversed for debugging. Effect handlers on the other hand can be impure. The side effect has been applied and the state of the universe has changed but the DOM hasn't changed yet. The last piece of the puzzle is to subscribe to the database and render changes. Reframe.core/registersub method helps us define subscriptions. Sub scriptions help the coupled view layer from the app DB. With subscriptions, we pluck the state that we care about and make our component re-render only when the relevant state changes. Now we can tie the subscription to the view via the reframe.core/subscribe method. Here we subscribe to the subscription ID we just defined in the previous step and had to de-ref it. The return value of the subscribe function is an atom-like object that we need to de-ref to get the actual value. Step 5 is the re-computation of the view for the new state and step 6 is flushing the view down to DOM. Step 5 and 6 are handled automatically by reframe. There are three major moving parts in reframe application. Events, effects and subscriptions. Where each of these goals is an opinion. You can put them together in a single namespace and require that namespace in the bootstrap process. But this might bite you as a rap cruising complexity. A convention for small apps is to have three namespaces. App events, apps, subs and app effects. In this separation by concerns method, all events go to the events namespace, all subscriptions go to the subs namespace and all effects go to the effect namespace. For example, imagine an application with two domain entities, authors and posts . Our app.events namespace will have events to fetch all authors, fetch an author by ID, fetch the dot post and search a post. App.effects namespace will have an HTTP API to interact with CMS. An app.subs namespace will have subscriptions for author profile, post list and post detail. With namespace keywords, we can easily code hundreds of events or subscriptions in a single namespace. Another way is to think of actions in terms of entities of a domain. In the blog application from the last slides, we have two domains, posts and authors. We can create a namespace for each entity like app domain authors and app domain posts. And all events, subscriptions and effects related to authors will go to the authors namespace. This presents a challenge. The event or effect or subscription wouldn't be registered unless the namespace is required explicitly. Both approaches have their merits. The first one leads to less namespaces to manage. The second one makes it easier to manage and maintain apps with the large number of entities. For our application, we are going to use separation by domain. Again, there is no hard and fast role as to the folder structure. My suggestion is to get the hang of the system and then play around until you find a good fit. Closgescript data structures are not as efficient as raw JavaScript data structures. This performance difference is most evident during actions that require real-time feedback. For example, if you choose to store local form state in app DB, you might experience input lag as an app grows. To bypass this issue and excessive boilerplate code, we are going to use RATOM for UI only state and reframe for shared state and side effects. Again, this is just in opinion. There is nothing stopping you from moving everything to reframe. For smaller apps, you might not even need reframe. In this chapter, we learned about reframe concepts and different ways of structuring a reframe application. Reframe is a powerful library with the robust API. The parts we covered are the absolute basics. Don't worry if everything doesn't fit together just yet. As we build our application step by step, the concepts will make more sense.