This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-01-17
Channels
- # aleph (57)
- # announcements (2)
- # babashka (11)
- # beginners (55)
- # calva (18)
- # cider (37)
- # clj-kondo (24)
- # clojure (18)
- # clojure-europe (80)
- # clojure-nl (2)
- # clojure-norway (22)
- # clojure-uk (7)
- # clojured (1)
- # clojurescript (24)
- # data-science (9)
- # datomic (45)
- # events (1)
- # gratitude (4)
- # humbleui (30)
- # hyperfiddle (7)
- # introduce-yourself (2)
- # malli (3)
- # missionary (12)
- # music (1)
- # off-topic (33)
- # re-frame (3)
- # reagent (11)
- # ring (24)
- # shadow-cljs (13)
- # sql (3)
- # tools-deps (4)
Hello Clojurians! I have a problem that I've been grinding at for two days now and I still don't understand at all. I have created a webpage in ClojureScript with Reagent, and it has, as its landing page, a grid that displays projects that I retrieve from a backend. The call to the backend works fine and this page renders fine after I navigate to "/" from the navigation bar. However, after correctly rendering my content consistently that way, my main grid disappears upon either recompiling or refreshing the page. I know that this has something to do with the atom that it stores the backend's return in. Oddly enough, when I try to add some debug statements to my call to the backend, I find that it is correctly populated in all situations, compiling, refreshing, navigating. But, only upon navigating does it render my grid, and it does so consistently. What could I try? Here is my code (I have provided a few comments to show what happens):
(ns app.pages.main
(:require [reagent.core :as r]
[ajax.core :refer [GET]]
[app.components.common :refer [handler error-handler]]
[app.components.common :refer [nav-link]]
[app.state :as state]))
(defn transform-project [project]
{:name (get project "projects/name")
:pageid (get project "projects/pageid")
:tags (get project "projects/tags")
:image (get project "frontimage")})
(def project-list (r/atom []))
(defn fetch-and-update-projects []
(GET ""
{:handler (fn [response]
(reset! project-list (map transform-project response))
;; Debug code to check whether the projects are really there (they always are):
(let [projects @project-list]
(doall
(for [project projects]
(js/console.log "Project " project))))) ;; This always prints all the projects
:error-handler error-handler}))
(defn main-grid []
(fetch-and-update-projects)
[:div {:class "flex flex-wrap justify-center max-w-md mx-auto"}
(let [projects @project-list]
(if (and projects (empty? projects))
[:div {:class "flex justify-center items-center h-full"}
[:h2 "No projects found"]] ; We get here each time after refreshing or compiling the page
;; This works -consistently- upon navigating to "/" via the navbar
(doall
(for [project projects]
^{:key (:name project)}
[:div {:class "w-full md:w-1/3 p-4"}
(nav-link (str "/" (:pageid project))
[:div {:class "rounded-lg shadow relative"}
[:img {:class "max-w-full h-auto shadow-lg rounded-lg" :src (:image project)}]
[:div {:class "absolute inset-0 flex items-center justify-center"}
[:h3 {:class "text-xl font-bold text-center text-black shadow-white"} (:name project)]]])]))))])
(def project-list (r/atom []))
should use defonce
instead of def
so that the state is preserved after a hot reload. Of course, that means that if you need to reset the state, you'll have to do so manually via either reloading the page or resetting the atom from the REPL or some other action.
And while it doesn't affect anything, that second doall
is not needed - you can have that for
in there, Reagent will realize the sequence for you.
I have changed the def
to defonce
and that seems to solve the problem of the grid disappearing upon re-compiling, but the error of the entire thing getting lost upon refresh or simply going to localhost:3000 remains. Any ideas?
I've removed the doall
That fetch-and-update is in a strange place. It's bad luck to do that sort of thing in a render function in Reagent
The GET is async, so upon page load the atom will be empty when main-grid resumes after fetch-and-update. But since fetch-and-update was undertaken within the render function, the question of whether the atom changed (upon completion of the GET) might be murky
Could you recommend something to do? Perhaps another atom that keeps the loading state?
Good point. The ratom will be changed and it will lead to a re-rendering. However, that will also trigger a second fetching right away.
An alternative to an event triggering fetching (that can sometimes be cumbersome) would be using a form-2 or form-3 component, or r/with-let
where the fetching is initiated in the binding block (you can use _
as a bogus binding name).
entire thing getting lost upon refresh"Refresh" as in the Refresh button in the browser? Doing that will reset everything, but should also trigger another fetching. If the component is rendered empty and fetching is not triggered, I'd have to see a reproduction case.
Yes, my grid disappears when I click refresh in the browser.
It does indeed trigger another fetching as when I do that the debug statements in the fetch-and-update does indeed fetch the projects correctly
In these cases it gets stuck in the "No projects found"
I had tried adding a refetch
function in place there but to no avail
Done. Does not seem to affect anything
It's the main page. The routing map:
(ns app.routingconfig
(:require [app.pages.main :refer [main-grid]]
[app.pages.about :refer [about-page]]
[app.pages.contact :refer [contact-page]]
[app.pages.projectpage :refer [project-page]]))
(defn route-component-map [path]
(cond
(= path "/") main-grid
(= path "/about") about-page
(= path "/contact") contact-page
:else (project-page {:project-id (last (clojure.string/split path #"/"))})
))
That is called here:
(ns app.routing
(:require [app.state :as state]
[app.routingconfig :refer [route-component-map]]))
(defn navigate [path]
(let [new-content (route-component-map path)]
(reset! state/current-content (new-content))
(.pushState js/window.history nil "" path)))
And the layout renders it like so:
(ns app.layout
(:require [app.components.logo :refer [logo]]
[app.state :as state]
[app.components.navbar :refer [navbar]]
[app.routing :as routing])) ;; Make sure routing is required
(defn layout []
[:div
[logo]
[navbar]
@state/current-content]) ;; Dereference current-content here
Ultimately built with core.cljs:
(ns app.core
(:require [reagent.core :as r]
[reagent.dom :as rdom]
[app.state :as state]
[app.routing :as routing]
[app.layout :as layout]))
(defn init-routing []
(add-watch state/current-route :route-change
(fn [_ _ _ new-route]
(routing/navigate new-route))))
(defn ^:export main []
(init-routing)
;; Re-establish the current route based on the browser's URL
(let [current-path (.-pathname js/window.location)]
(reset! state/current-route current-path)
;; Set the current content based on the current route
(routing/navigate current-path))
;; Render the app
(rdom/render [layout/layout] (.getElementById js/document "app"))
;; Handle browser navigation events
(.addEventListener js/window "popstate"
(fn [_] (routing/navigate (.-pathname js/window.location)))))
You're welcome to tell me if any of this seems dumb. It's my first clojure(script) project and frankly also my first websiteDo not use Reagent components with ()
. Always use them in Hiccup vectors, []
. All the way up to the call to render
.
Do you mean this part:
(defn navigate [path]
(let [new-content (route-component-map path)]
(reset! state/current-content (new-content))
(.pushState js/window.history nil "" path)))
?Wow, that actually fixed the problem
Thank you kindly sir. Could you explain or point me to where I can read the explanation?
Sure: https://github.com/reagent-project/reagent/blob/master/doc/UsingSquareBracketsInsteadOfParens.md
Thanks!