I am trying to create a search form to display at the top of every page. I have it created, but I see errors from React like this:
Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
Here is the Search form I created:
(defsc Search [this props]
{:query ['*]
:ident (fn [] [:component/id ::Search])
:initial-state {}}
(div
(input {:value ""})
(button {:onClick (fn [& args]
(tap> {:from :search/button :args args}))} "Search")))
(def ui-search (comp/factory Search))
I would like to have the Search form able to query against multiple objects in the database. When I click the search button from that form, I get an object that i can't seem to figure out how to get the input field value from...
#object [SyntheticBaseEvent [object Object]]http://book.fulcrologic.com • Events are react synthetic events. See event/target-value. You should know something about React if you’re using it. Fulcro has nothing to do with the React stuff…it’s just React. • There are tons of examples in Fulcro (todomvc), fulcro-template, and the fulcro community docs.
Components do not exist in isolation. What you were given from Eric is simple and correct. If it is not working, it is because you have misunderstood a core tenent of the operation of Fulcro: that queries AND initial state compose to root. Components like what you have shown do not exist in isolation. Fulcro treats the UI as a F(state-map) , where F = Render(Query(state-maps)) and Render is calling your Root with the complete ui tree. If you’re not getting props, it means that you didn’t compose the application correctly. If you post your complete application from Root (ideally with just Root and this Form composed together), then anyone will easily point out your error.
I cut down my Root component to just the Search form and still see the same issue, here is the Root component:
(defsc Root [this {::app/keys [active-remotes]
:ui/keys [ready? search landing-page]
:keys [session]}]
{:query [::app/active-remotes (scf/statechart-session-ident uir/session-id)
:ui/ready?
{:ui/search (comp/get-query Search)}
{:ui/landing-page (comp/get-query LandingPage)}
{:session (comp/get-query Session)}]
:initial-state {:ui/ready? false
:ui/search {}
:ui/landing-page {}
:session {}}}
(div (ui-search {}))You pass an empty map to UI search
If I use (div (ui-search {:ui/search search})) there then the form shows [object Object] in the input field, and typing into the field does nothing.
If I use (div (ui-earch {:ui/search ""})) there then the search field works, and [:component/id Search] is populated with :ui/search, but when I click the search form button the props are the empty string, instead of the string from the input field, which is populating into the client database.
This is driving me crazy because it's not acting like all my other forms, and the only difference is this form is not tied to any entities in the database.
The currently broken code is here: https://gitlab.com/michaelwhitford/gailish/-/blob/main/src/main/us/whitford/gailish/ui/root.cljc?ref_type=heads
you are passing props and using initial-state without understanding what it is
(div (ui-search search))
in the initial-state :ui/search should be (comp/get-initial-state Search) otherwise the initial-state of Search is useless and will never be used
initial-state is “magical” when used without a fn, so what you have in initial-state is ok…but you did misunderstand the point of composition
That fixed the search form, using what Tony supplied. I tried to use the Login form from the fulcro-rad-demo as the example to build from, but that is not rendered with a factory that I can see.
probably because it uses a router with a dynamic query>?
I tested this in a very basic scaffold, and it works perfectly. Have you tried reloading the inspector tool? Right click and select "Reload frame" in the context menu.
(ns app.client
(:require
[com.fulcrologic.fulcro.application :as app]
[com.fulcrologic.fulcro.components :as comp :refer [defsc]]
[com.fulcrologic.fulcro.dom :as dom]
[com.fulcrologic.fulcro.dom.events :as evt]
[com.fulcrologic.fulcro.mutations :as m]
[fulcro.inspect.tool :as it]
[taoensso.timbre :as log]))
(defonce SPA (app/fulcro-app))
(defsc Search [this {:ui/keys [search] :as props}]
{:query [:ui/search]
:ident (fn [] [:component/id ::Search])
:initial-state {:ui/search ""}}
(dom/div :.ui.form
(dom/div :.ui.action.icon.input
(dom/label "Search terms ")
(dom/input {:type "text"
:onChange (fn [evt]
(m/set-string! this :ui/search :event evt))
:value search
:placeholder "Search..."})
(dom/button :.circular.search.link.icon
{:onClick (fn [evt]
(tap> {:from :search/button :this this :evt (evt/target-value evt) :props props
:component (comp/component-name this) :app (comp/any->app this) :search search}))}
"Search"))))
(def ui-search (comp/factory Search))
(defsc Root [this {:ui/keys [ready? search]}]
{:query [:ui/ready?
{:ui/search (comp/get-query Search)}]
:initial-state {:ui/ready? false
:ui/search {}}}
(dom/div (ui-search search)))
(defn ^:export init
"Shadow-cljs sets this up to be our entry-point function. See shadow-cljs.edn `:init-fn` in the modules of the main build."
[]
(log/info "Application starting.")
(app/set-root! SPA Root {:initialize-state? true})
(it/add-fulcro-inspect! SPA)
(app/mount! SPA Root "app" {:initialize-state? false})
(js/console.log "Loaded"))
(defn ^:export refresh
"During development, shadow-cljs will call this on every hot reload of source. See shadow-cljs.edn"
[]
;; re-mounting will cause forced UI refresh, update internals, etc.
(app/mount! SPA Root "app")
;; As of Fulcro 3.3.0, this addition will help with stale queries when using dynamic routing:
;(comp/refresh-dynamic-queries! SPA)
(js/console.log "Hot reload"))Oh wait, I think I understand the issue @michael819. You are not seeing the search value in the props when you tap the button. Change set-string! to set-string!! .
Thank you, Tony gave me the hint on what I was doing wrong. I was not passing the react component to the ui factory for the component. It was connected up in the fulcro client database correctly. It was not reflecting in the button press because those props I had passed to the ui factory at render time were not the react component, but just a plain old map.
I still see the react warning about changing a controlled input to uncontrolled, whether I use set-string! or set-string!!.
Ah, yes. (ui-search search) not (ui-search {}) is correct.
I have a bit of time. Happy share screen to help with react warning.
(defsc Search [this {:ui/keys [search] {:query [:ui/search] :ident (fn [] [:component/id Search]) :initial-state {:ui/search ""}} (div (input {:value search :on-change (fn [evt] (m/set-string! this :ui/search :event evt}) (button {:onClick (fn [& args] (tap> {:from search}))} "Search"))) (def ui-search (comp/factory Search))
Here is the latest I have, when I run this the form shows up on the page correctly, and the tap happens, but the props are empty when I click the button, even though the input has a long string. The fulcro client database shows the correct query for :ui/search, and the [:component/id Search] is created and populated with :ui/search populated from the input in the form. The click of the button from the search form has no props. I do not understand why there are no props on the component when the button is clicked.
(defsc Search [this {:ui/keys [search] :as props}]
{:query [:ui/search]
:ident (fn [] [:component/id ::Search])
:initial-state {:ui/search ""}}
(div :.ui.form
(div :.ui.action.icon.input
#_(label "Search terms")
(input {:type "text"
:onChange (fn [evt]
(set-string! this :ui/search :event evt))
:value (or search "")
:placeholder "Search..."})
(dom/i :.circular.search.link.icon
{:onClick (fn [evt]
(tap> {:from :search/button :this this :evt (events/target-value evt) :props props
:component (comp/component-name this) :app (comp/any->app this) :search search})
#_(toast! "Hello!")
#_(comp/transact! this [`(swapi/search {:search (:ui/search props)})]))}))))Did you try https://blog.jakubholy.net/2020/troubleshooting-fulcro/ ? especially DB explorer? I usually just inject (def search search) somewhere - before first div, and play with repl. we can try to fix it in a zoom session.
there's no point doing (or search "") you already give an initial state to search
if you get an error without the or that would be because you are not passing the props to your Search component properly