aarkerio
aarkerio

Reputation: 2364

ClojureScript. Reset atom in Reagent when re-render occurs

I'm displaying a set of questions for a quiz test and I'm assigning a number to each question just to number them when they are shown in the browser:

(defn questions-list
 []
  (let [counter (atom 0)]
    (fn []
      (into [:section]
           (for [question @(re-frame/subscribe [:questions])]
              [display-question (assoc question :counter (swap! counter inc))])))))

The problem is that when someone edits a question in the browser (and the dispatch is called and the "app-db" map is updated) the component is re-rendered but the atom "counter" logically starts from the last number not from zero. So I need to reset the atom but I don't know where. I tried with a let inside the anonymous function but that didn't work.

Upvotes: 2

Views: 259

Answers (2)

jackdbd
jackdbd

Reputation: 5061

If you need counter to be just an index for a question, you could instead use something like this:

(defn questions-list
 []
  (let [questions @(re-frame/subscribe [:questions])
        n (count questions)]
    (fn []
      [:section
        [:ul
          (map-indexed (fn [idx question] ^{:key idx} [:li question]) questions)]])))

Note: here I used [:li question] because I assumed that question is some kind of text.

Also, you could avoid computing the count for questions in this component and do it with a layer 3 subscription:

(ns your-app.subs
  (:require
   [re-frame.core :as rf]))

;; other subscriptions...

(rf/reg-sub
 :questions-count
 (fn [_ _]
   [(rf/subscribe [:questions])])
 (fn [[questions] _]
   (count questions)))

Then in the let binding of your component you would need to replace n (count questions) with n @(re-frame/subscribe [:questions-count]).

Upvotes: 1

Walton Hoops
Walton Hoops

Reputation: 864

In this case I'd just remove the state entirely. I haven't tested this code, but your thinking imperatively here. The functional version of what your trying to do is something along the lines of: Poor but stateless:

(let [numbers (range 0 (count questions))
      indexed (map #(assoc (nth questions %) :index %) questions)]
  [:section
   (for [question indexed]
     [display-question question])])

but this is ugly, and nth is inefficient. So lets try one better. Turns out map can take more than one collection as it's argument.

(let [numbers (range 0 (count questions))
      indexed (map (fn [idx question] (assoc question :index idx)) questions)]
  [:section
   (for [question indexed]
     [display-question question])])

But even better, turns out there is a built in function for exactly this. What I'd actually write:

[:section
 (doall
  (map-indexed
   (fn [idx question]
     [display-question (assoc question :index idx)])
   questions))]

Note: None of this code has actually been run, so you might have to tweak it a bit before it works. I'd recommend looking up all of the functions in ClojureDocs to make sure you understand what they do.

Upvotes: 1

Related Questions