reagent/cursor
Managing deeply nested data can be tricky, especially in reactive applications. Luckily, Reagent’s reagent/cursor function provides a streamlined way to access and update nested state, helping you avoid tedious destructuring and boilerplate code.
In this post, we’ll explore reagent/cursor and see how it simplifies working with nested data structures in Reagent.
What is reagent/cursor?
In Reagent, cursor creates a focused view of a nested part of an atom. It lets you “zoom in” on a specific path in your data structure, giving you a mini-atom that holds only the nested data. This mini-atom is fully reactive, so changes to it automatically update the parent atom—and vice versa.
Here’s an example of how cursor can be used:
(ns my-app.core
(:require [reagent.core :as r]))
(def app-state (r/atom {:user {:name "Alice" :age 30} :settings {:theme "dark"}}))
(def user-cursor (r/cursor app-state [:user]))
(defn user-component []
(let [{:keys [name age]} @user-cursor]
[:div
[:p "User: " name]
[:p "Age: " age]
[:button {:on-click #(swap! user-cursor update :age inc)} "Increase Age"]]))
In this example:
- Parent Atom: app-state is the top-level atom, containing nested data under the :user key.
- Cursor Creation: We create a user-cursor by pointing to the :user path in app-state. Now user-cursor is a mini-atom that specifically tracks the :user map.
- Component Binding: In user-component, we dereference user-cursor to access :name and :age without worrying about the surrounding structure of app-state.
- Updating Nested State: The button triggers an update to :age via user-cursor, and Reagent takes care of updating app-state and re-rendering the component.
Why Use reagent/cursor?
Reagent’s cursor function provides a few key benefits:
- Clarity: cursor allows you to write components that focus only on the relevant part of your data structure, without having to reference the entire nested path.
- Reactivity: The mini-atom created by cursor keeps its relationship with the parent atom, meaning updates are automatically propagated in both directions.
- Simplified Updates: With cursor, you can update deeply nested parts of your state with swap! or reset! directly, without having to manage the entire atom.
When to Use reagent/cursor
Cursors are especially useful in the following scenarios:
- Component-Specific State: When a component only needs access to part of the app state, a cursor keeps it isolated from unrelated data.
- Large and Nested Data Structures: For apps with complex state trees, cursor can simplify code and make it more readable by avoiding deeply nested updates.
- Modular Components: If you’re building reusable components, a cursor can help isolate and focus on the state that matters, improving modularity.
Pitfalls to Watch For
While reagent/cursor is a handy tool, here are a few considerations:
- Overuse of Cursors: Avoid creating too many cursors for frequently changing data, as each cursor introduces a new subscription to the parent atom.
- Performance in Large State Trees: In very large state trees, excessive cursor usage can potentially lead to performance issues. Use with awareness of your state structure’s complexity.