Search code examples
data-structuresfunctional-programmingclojurescriptreagent

Clojure collection atom with selected item


What is the best way in Clojure (actually ClojureScript) to accomplish the following:

  • A mutable collection of elements xs, each of type T (T is a map, if we want to be specific)
  • A "selected element" among them, x, which alternates between various xs or a "none selected" state. The number of selected items is either 1 or zero.
  • xs and x will have event listeners listening to them: some listen for any change in xs; some listen to any change in x. (That is, they listen for both an update in the state of the selected item, as well as a switching of which item is selected.) (I'm actually using the Reagent wrapper for React, so this affects when components will update.)
  • Methods to edit x (don't need to edit non-selected xs, though need to be able to add new xs)
  • Given an element in xs, a method to select it. (Including the case of select "none")

Possibilities I've thought of so far:

  1. Make xs an atom which is a vector of Ts, and make sure every T element knows its own index. Replace x with x_idx which stores the index of the selected item (or nil). Selecting an element just means getting its index and changing x_idx to that index, both of which are constant-time ops. Downsides to this are that it makes the structure a little less elegant and simple: I'm always passing around the index of the selected item, and every operation I want to do has to work with the index instead of the item itself, as it would like to. Since my T objects are small, it would be more pleasing to just have a "selected object" variable which is of type T.
  2. Make xs an atom vector and x an atom which stores a T. Now x is a T, which is nice, but when I want to update info about x, I have to make two calls to reset! or swap!: one for x and one for the element in xs which x represents. Inelegant for obvious reasons. And I have to do these in quick succession or else there will be an inconsistency in the data: in between the two calls, the event listeners listening to xs will see the selected item in one state, and the ones listening to x will see it in another state.
  3. Give T elements a field to tell if they're selected or not, and get rid of x. This would be right if multiple items could be selected at once, but since only one item can be selected, it just makes me do a linear search every time I want the selected item, which sucks.

The point of this question is not to solve some particular issue (any of the possibilities above work fine for the scope of the small project I'm working on), but to learn about Clojure data structures. This seems like a common enough situation that there would be some structure around it. Responses along the lines of "you should be trying to answer a different question entirely..." are welcome.


Solution

  • You (I) want to look into cursors and reactions, two of the more advanced features of Reagent atoms. They can be used to have a collection and a selected element inside it which is automatically updated. See here for example.

    Suppose you have a r/atom holding vector of objects and you want to have a selected object which can change, and which is directly editable. One way to achieve this is to keep an atom storing the index of the selected item and then

    (defn my-get-set
      ([_k] (@vector-of-items @idx-of-selected-item))
      ([_k v] (reagent.core/rswap! my-state-vector assoc @idx-of-selected-item v)))
    
    (def selected-item (reagent.core/cursor my-get-set []))
    

    Edit: Changed reagent.core/cursor my-get-set nil to reagent.core/cursor my-get-set []. The former leads to problems.