Повторно запустить подписку при изменении ввода
Я пытаюсь создать подписку с повторным кадром, которая считывает данные из REST API вместо локальной базы данных и сохраняет эти данные в базе данных. Вызов REST зависит от других значений в базе данных перефреймов (например, API-ключ), и хотя эти данные доступны при навигации по приложению, их нет при перезагрузке. Я следую этому уроку: https://github.com/Day8/re-frame/blob/master/docs/Subscribing-To-External-Data.md#some-code
Моя подписка выглядит так (лишена читабельности):
(rf/reg-sub-raw
:organisation-users
(fn [db [_ api-token organisation]]
(when (not (nil? api-token))]
(ajax/GET
(str "/some-url/to/get/users/for/organisation")
{:params {:api-token api-token}
:handler #(rf/dispatch [:organisation-users-change %])
:format (ajax/json-request-format)
:response-format (ajax/json-response-format {:keywords? true})}))
(ratom/make-reaction
(fn [] (:organisation-users @db)))))
Подписка получает API-ключ и запрашивает пользователей организации с сервера. Возвращает реакцию, которая обновляет подписчика в случае, если organisation-users
изменение в базе данных, которое происходит, как только успешный вызов rest.
В рамках моего компонента я подписываюсь на подписку:
(defn organisation-page
[]
(let [api-token (rf/subscribe [:api-token])
organisation-users (rf/subscribe [:organisation-users @api-token])]
(fn []
[:div {:class "columns"}
))); stripped
Этот код работает, если я перезагружаю страницу и после инициализации (которая инициализирует API-токен) позволяет organisation-page
визуализации. Это не работает, когда organisation-page
Компонент отображается сразу после загрузки, потому что api-токен еще не доступен, и он не выполняет повторный вызов.
Я также попытался включить всю подписку в реакцию, но это приводит к бесконечному циклу, потому что вся реакция выполняется каждый раз, когда organisation-users
изменение в базе данных, которое снова выполняет вызов rest, который снова вызывает реакцию и так далее.
Как бы я решил эту проблему "путем перекадровки"? Одна из идей, которые у меня есть, состоит в том, чтобы выполнить вызов остальных, только если organisation-users
еще не заполнен в базе данных. Это может сработать, но я боюсь, что у меня возникнут проблемы, если пользователи перейдут на страницу позже и увидят старые данные или если вызов не удастся, и пользователи не смогут инициировать новую попытку, снова перейдя на страницу.
1 ответ
Я перенес выборку пользователей в событие.
Чтобы проверить это, я сфальсифицировал ответ, но учтите, что вы можете очень хорошо встраивать вызовы ajax в события с помощью http-fx (см. Закомментированное событие).
Обратите внимание, что вы можете немедленно прервать подписку из-за кэширования / дедупликации подписки.
(ns simple.core
(:require [reagent.core :as r]
[re-frame.core :refer [reg-sub
reg-event-fx
reg-sub-raw
dispatch
subscribe
dispatch-sync]]))
(reg-sub
:api-token
(fn [db _]
(get db :api-token)))
(reg-event-fx
:api-token
(fn [{:keys [:db]} [_ api-token]]
{:db (assoc db :api-token api-token)}))
(reg-event-fx
:good-organisation-users
(fn [{:keys [:db]} [_ response]]
{:db (assoc db :organisation-users response)}))
(def responses {"123" [{:name "Foo"}]
"456" [{:name "Foo"} {:name "Bar"}]})
(reg-event-fx
:fetch-organisation-users
(fn [{:keys [:db]} [_ api-token]]
(.log js/console "fetching users with api-token" api-token)
{:dispatch [:good-organisation-users (get responses api-token)]}))
(defn fetch-users! []
(let [api-token @(subscribe [:api-token])]
(when api-token
(dispatch [:fetch-organisation-users api-token]))))
(reg-sub-raw
:organisation-users
(fn [app-db [_]]
(let [fetcher (r/track! fetch-users!)]
(reagent.ratom/make-reaction
(fn []
(get @app-db :organisation-users))
:on-dispose #(r/dispose! fetcher)))))
(defn organisation-page
[]
(let [organisation-users @(subscribe [:organisation-users])]
[:div
(when organisation-users
[:ul
(for [user organisation-users]
^{:key (:name user)}
[:li (:name user)])])]))
(defn ^:export run
[]
(dispatch [:api-token "123"])
(js/setTimeout #(dispatch [:api-token "456"]) 3000)
(r/render [organisation-page]
js/klipse-container))
(run)
Чтобы показать, что это работает, вы можете посмотреть его в прямом эфире на http://app.klipse.tech/?container&cljs_in.gist=borkdude/f228103b2eaa04b92c5b532485fbd2ef