Fork me on GitHub
#reagent
<
2021-07-08
>
zendevil.eth20:07:56

Iā€™m using the react testing library, and I have the following:

(deftest test-home
  (render (r/reactify-component [:div [:p "Something"]]))
  (.click fireEvent (.getByText screen "Something"))
  (is (= "Something" (.getByText screen "Something")))
  )
But this is giving me the following error: Unable to find an element with the text: Something. This could be because the text is broken up by multiple elements. how to fix this error?

ChillPillzKillzBillz21:07:08

If I have nested reagent components, how do I access the state of a child component? e.g. Child component [ ChildComp State] TopLevel component [Toplevel State] [child component <params>] from TopLevel component can I access childcomp.state? if Yes, how?

p-himik21:07:35

You're controlling the child component from the top level component - it already has all the state, because it sets it.

ChillPillzKillzBillz21:07:13

what if the child component has its own state using the with-let ratom definition? ... and I don't necessarily want to use track on the parent state?

p-himik21:07:49

Then you're out of luck. Architecturally, being able to access that internal state willy-nilly is not good.

ChillPillzKillzBillz21:07:51

I should clarify... what if an event on the parent changes the state of the child... ? So maybe I am more after triggering an event on the child when an event on the parent triggers this... I am fighting against my OOP mind while trying to implement components in functional clojure šŸ˜„

p-himik21:07:47

No idea what you mean. Can you describe the problem that you're trying to solve?

ChillPillzKillzBillz22:07:07

Ok... using our previous smiley example... top level component svg. This component has a state which has a key :smilies which is a list of coords for where the smiley faces will be. I was implementing this such that when you click on the empty svg, the click event adds the coords of the smiley to the :smilies list in the svg component. Additionally if I click on the smiley face itself, I can change the smile to sadface. But because the parent already has the click event the onClick event handler on smiley face never gets triggered. So now I am thinking what if I can keep track of the extents of each of the smiley faces, and when there is a click on an existing smiley face area, instead of spawning a new smiley face, trigger the onClick handler of the smiley face child....

ChillPillzKillzBillz22:07:35

sorry for the messy explanation... šŸ˜„ ask away if you need further clarifications...

p-himik22:07:31

I think I understand. Can you give an example SVG with two faces?

ChillPillzKillzBillz22:07:16

<svg baseProfile="full" width="1000" xmlns="http://www.w3.org/2000/svg" id="svgcontainer" version="1.1" viewBox="0 0 800 700" height="1000"><rect x="0" y="0" width="800" height="700" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: white;"></rect><g id="smiley_x88y83.5999984741211"><circle cx="88" cy="83.5999984741211" r="50" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: yellow;"></circle><circle cx="109.21640735502122" cy="62.383591119099876" r="5" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: black;"></circle><circle cx="66.78359264497878" cy="62.383591119099876" r="5" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: black;"></circle><path d="M 73 98.5999984741211 a30,30 0 0, 0 30,0" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: yellow;"></path></g><g id="smiley_x405.6000061035156y258.79998779296875"><circle cx="405.6000061035156" cy="258.79998779296875" r="50" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: yellow;"></circle><circle cx="426.81641345853683" cy="237.58358043794755" r="5" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: black;"></circle><circle cx="384.3835987484944" cy="237.58358043794755" r="5" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: black;"></circle><path d="M 390.6000061035156 273.79998779296875 a30,30 0 0, 0 30,0" style="stroke: rgb(0, 0, 0); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill-opacity: 100; fill: yellow;"></path></g></svg>

ChillPillzKillzBillz22:07:00

I am also thinking about adding some more states to the smiley face... so instead of just sad/happy if I click on the eyes twice, the smiley changes to dead face... etc... and this is where I become completely discombobulated with reality.... maybe it is too late for me tonight!!

p-himik22:07:13

So I've attached two :on-click handlers - one to :svg and one to one of the faces, its :g. When I click on the SVG, the first handler is called. When I click on the face, both handlers are called - first the one for the face, then the one for the SVG. It's all expected. Is it not what you see on your end?

p-himik22:07:51

If you don't want the SVG to respond to the face click event, just call (.-stopPropagation evt) , assuming the signature of the face click event handler is [^js evt].

p-himik22:07:36

This is the simple code that I used to double check that:

(def circle-style {:stroke          "rgb(0, 0, 0)"
                   :stroke-width    2
                   :stroke-linecap  :round
                   :storke-linejoin :round
                   :fill-opacity    100}

(defn faces []
  [:svg {:on-click (fn [_]
                     (js/console.log "SVG CLICK"))}
   [:g {:on-click (fn [^js evt]
                    (.stopPropagation evt)
                    (js/console.log "FACE CLICK"))}
    [:circle {:cx    88
              :cy    83
              :r     50
              :style (assoc circle-style :fill :yellow)}]
    [:circle {:cx    109
              :cy    62
              :r     5
              :style (assoc circle-style :fill :black)}]
    [:circle {:cx    67
              :cy    62
              :r     5
              :style (assoc circle-style :fill :black)}]
    [:path {:d     "M 73 98.5999984741211 a30,30 0 0, 0 30,0"
            :style (assoc circle-style :fill :yellow)}]]]

ChillPillzKillzBillz06:07:41

wow thanks again man!! On my side without the .stopPropagation (which I didn't even know about... šŸ˜„) the handler for the face doesn't trigger.... I don't know why. only the top level svg handler triggers... but your solution solves the problem. Now I can handle the events at the component levels themselves without having to handle them at the parent level!! Thanks again!!

ChillPillzKillzBillz06:07:06

Since I am practically a toddler in the clojure world, do you reckon I should move my pedantic queries to the beginners thread?

ChillPillzKillzBillz07:07:45

ok another problem... say I want the state of sad/happy change based on the click event on smiley face, but also with click event on the sad/happy component. The state of sad & happy where does it reside? - in the smiley face component or the sad/happy component? I say this because in my example, we have 3 components, SVG, SmileyFace & the smile/scowl or sadhappy components...

ChillPillzKillzBillz07:07:14

This is again one of the gaps in my understanding... how does a child component alter the state of a parent?

p-himik09:07:44

> Since I am practically a toddler in the clojure world, do you reckon I should move my pedantic queries to the beginners thread? I'd say depends on the type of the question. If it's about Clojure[Script] only - #beginners sounds like a proper place. If it's about something more specific like Reagent - #reagent or other specific channel would be more appropriate. > The state of sad & happy where does it reside? Where you put it. If you need to control 2 things in the same way, their state should probably be in the same place, so at the common parent component in this case. > how does a child component alter the state of a parent? By itself, it does not. It should not care about the fact that it has a parent. Along with the current value, just pass an :on-click handler from the parent component, so that the parent component decides what happens in each case. So each component will have its own :on-click.

ChillPillzKillzBillz10:07:23

as a design principle, do you expect child component to ever alter the state of the parent? or architecturally the data flow can be only one way... ? from parent to chilldren

p-himik10:07:36

It should be one-way, yes. Children should not know about their parents, with a potential exception for some really rare convoluted cases. They can affect parents, but only without knowing that - by calling whatever function the parent has provided them.

ChillPillzKillzBillz10:07:21

I was thinking of defining a channel in the smileyface component which I pass as argument for sadhappy component. Sadhappy component then defines a onClick handler which just puts the new value of the sadhappy state on the channel. In the Smileyface component, there is a go block which picks this up and updates the ratom in parent... Is this something expected reagent architecture wise?

ChillPillzKillzBillz10:07:33

ok I know this was a silly example... but maybe I should lead with a usecase... so now we not only want to be able to click all over the svg and generate smiley faces... and click on smiley faces and change them to sad/happy.... but also want to save the configuration for the user such that the user can reload the smiley faces and their respective sad/happy faces later on... Assuming this is the use case, we need some way to query the states of all the parent/child components and save it into a datastructure... (JSON, etc...) But you still need to be able to query the state of all the children, after the fact... that is where the information needs flow as follows: Once the parent triggers the child to handover the state, then the child hands over the state value to the parent, such that the parent can create the JSON configuration to save... How is this kind of interaction done in production environments?

p-himik10:07:33

If you mean core.async channels, then just don't. As a beginner, you don't need its own complexity on top of what you already have. With that being said, you simply don't need it at all - passing :on-click or any other :on-%some event% handlers is the way to go. Pretty much everyone uses it. Not only in the Reagent world, but also in the React one. Of course, things can become different once you start using re-frame or any other library that similarly moves the state from within the components to a global storage. But let's not go there right now. > but also want to save the configuration for the user such that the user can reload the smiley faces and their respective sad/happy faces later on Ah, well... That's actually a perfect use-case for re-frame. It moves all your state in a single location. It's just a regular CLJS map, so you can save and load it however you want. > we need some way to query the states of all the parent/child components and save it into a datastructure No, just no. It's a world of pain where the outcome is almost guaranteed to be miserable. Just use re-frame.

p-himik10:07:30

Re-frame has its own... peculiarities, of course. E.g. it's not trivial to figure out how to use it in a setting where you reuse multiple components and don't know the exact amount of components. There are solutions to that, of course, but what you prefer is up to you to figure out. There's this ongoing issue that has some good discussions: https://github.com/Day8/re-frame/issues/137 I strongly prefer the approach where each component just receives its ID from its parent, and uses dispatch and subscribe with that ID - without any additional abstractions.

ChillPillzKillzBillz10:07:46

architecturally this idea of components which are made up of components is appealing to me. I've no idea about re-frame, but I'll have a look. thanks for all your help!!

šŸ‘ 4