Error: No queries exists for component path? - om-next

I'm trying out om.next, trying to extend the example given in Components, Identity & Normalization.
The code in the example linked above contains of two lists that are backed by data in two different paths in the state. The example shows how the normalization of the data makes it possible to easily update normalized entities occurring in different views. In the code below I have removed the second list in the example below, since the behaviour shows up anyway.
Working example
The example below works and shows:
List A
John, points: 4 +
Mary, points: 0 +
Bob, points: 0 +
where the + is a button that increases the point for the person.
(ns ui.core
(:require [om.next :as om :refer-macros [defui]]
[om.dom :as dom]
[goog.dom :as gdom]))
(def init-data
{:list/one [{:name "John" :points 0}
{:name "Mary" :points 0}
{:name "Bob" :points 0}]})
;; -----------------------------------------------------------------------------
;; Parsing
(defmulti read om/dispatch)
(defn get-people [state key]
(let [st #state]
(into [] (map #(get-in st %)) (get st key))))
(defmethod read :list/one
[{:keys [state] :as env} key params]
{:value (get-people state key)})
(defmulti mutate om/dispatch)
(defmethod mutate 'points/increment
[{:keys [state]} _ {:keys [name]}]
{:action
(fn []
(swap! state update-in
[:person/by-name name :points]
inc))})
;; -----------------------------------------------------------------------------
;; Components
(defui Person
static om/Ident
(ident [this {:keys [name]}] [:person/by-name name])
static om/IQuery
(query [this] '[:name :points :age])
Object
(render [this]
(let [{:keys [points name] :as props} (om/props this)]
(dom/li nil
(dom/label nil (str name ", points: " points))
(dom/button
#js {:onClick
(fn [e]
(om/transact! this
`[(points/increment ~props)]))}
"+")))))
(def person (om/factory Person {:keyfn :name}))
(defui ListView
Object
(render [this]
(let [list (om/props this)]
(apply dom/ul nil
(map person list)))))
(def list-view (om/factory ListView {:key-fn ffirst}))
(defui RootView
static om/IQuery
(query [this]
(let [subquery (om/get-query Person)]
`[{:list/one ~subquery}]))
Object
(render [this]
(let [{:keys [list/one]} (om/props this)]
(apply dom/div nil
[
(dom/h2 nil "List A")
(list-view one)
]))))
(def rootview (om/factory RootView))
;; wrapping the Root in another root (this fails)
(defui AnotherRoot
static om/IQuery
(query [this] `[~#(om/get-query RootView)])
Object
(render
[this]
(dom/div nil
(rootview (om/props this)))))
(def reconciler
(om/reconciler
{:state init-data
:parser (om/parser {:read read :mutate mutate}) }))
(om/add-root! reconciler RootView (gdom/getElement "app"))
Problem: Using AnotherRoot as root component
However, when I change RootView in the last row to AnotherRoot, like:
(om/add-root! reconciler AnotherRoot (gdom/getElement "app"))
the ui still renders, but when pressing a button, the following error occurs:
Error: No queries exist for component path
(ui.core/AnotherRoot ui.core/RootView ui.core/Person)
The error comes from (next.cljc:1916)
I don't understand the error. The query from AnotherRoot returns the same query as RootView (however, you'll get a warning when returning the query straight away - which makes sense), but the reconciler seems to not be able to figure out how to re-render the component when the app-state changes.
The relevant dependencies should be:
[[org.clojure/clojure "1.9.0-alpha14"]
[org.clojure/clojurescript "1.9.473"]
[figwheel-sidecar "0.5.9"]
[org.omcljs/om "1.0.0-alpha47"]]
The question
What is the general way to create nested components in om.next?

Related

Swift Accidental Infinite ForEach Loop

I'm trying to use a foreach loop to show multiple Elements in an [[String]] with the help of an incremented Index in a list. But when I want to generate the list the loop repeats infinity regardless of the specified loop-count.
This is my function that gets called in the loop, to return the wanted Array of Strings:
func getPlaces (PlaceNumber: Int) -> [String]{
if allPlaces.count - 1 >= PlaceNumber{
return allPlaces[PlaceNumber]
} else{
return ["Not found",
"Not found",
"https://www.wikipedia.com",
"Not found",
"Not found"]
}
}
The Variable 'allPlaces' is an [[String]] and has 25 String-Arrays in it (0-24):
let allPlaces: [[String]] =
[["no sight",
"No information",
"https://www.wikipedia.com",
"No information",
"No information"],
["Tower of London",
"The Tower of London, officially Her Majesty's Royal Palace and Fortress of the Tower of London, is a historic castle on the north bank of the River Thames in central London. It lies within the London Borough of Tower Hamlets, which is separated from the eastern edge of the square mile of the City of London by the open space known as Tower Hill. It was founded towards the end of 1066 as part of the Norman Conquest. The White Tower, which gives the entire castle its name, was built by William the Conqueror in 1078 and was a resented symbol of oppression, inflicted upon London by the new ruling elite. The castle was also used as a prison from 1100 until 1952, although that was not its primary purpose. A grand palace early in its history, it served as a royal residence.",
"https://en.wikipedia.org/wiki/London_Bridge",
"1066",
"4"],
usw...
This is my view and the function to increment (I use the index to access the different Elements in the [[String]]). I thought that the loop should trigger just as often as the 'places.allPlaces'- Array count is. But it triggers infinitely. Even when I use '0...24' instead of "places.allPlaces'.
struct ListOfPlacesView: View {
#StateObject var location = Location()
#StateObject var places = Places()
#State var index = 0
var body: some View {
NavigationView{
List{
ForEach (places.allPlaces, id: \.self) { _ in
Text(incrementIndex())
}
}
}
.navigationTitle("All Places")
}
func incrementIndex() -> String{
index += 1
print(index)
return (places.getPlaces(PlaceNumber: index)[0])
}
}
But when I start this, the 'print(index)' counts to infinity in the console and the view doesn't even load. When I delete the 'index += 1' the loop prints the first Element of the Array 25 times, like it should.
I don't want to include the first Element of the [[String]] in the list, that's why im starting at an index of 0 and increment first.
Do you have an idea why this occurs and how to stop the app from doing that? Or know a better way to increment?
Sorry if this is described badly, ask if you can't figure out something.
Your incrementIndex() function updates the variable #State var index. This will cause the view to re-render and in turn call the incrementIndex() again. This causes the infinite loop.
Currently, you are not using the function parameter ForEach supplies, but instead discard it by naming it _. Instead of an index, I'd suggest using the value ForEach already supplies:
ForEach(places.allPlaces, id: \.self) { place in
Text(place[0])
}

Sort an object array [Venue] by its property using a sorted array [Geofire Keys] as reference?

I have built the following "helper" function, which takes as parameters:
'unsortedArray': The array of Venue objects required to be sorted by its .venueID string property
'sortingGeoArray': the Geofire string keys to be used as reference to order the unsorted array above.
and it returns a sorted array of type [Venue] via an escaping handler.
I have tried to implement this nice and simple solution suggested on the following thread:
'Sorting a Swift array by ordering from another array'
func sortVenuesArraybyGeofireKeys(unsortedArray: [Venue], sortingGeoArray: [String] , handler: #escaping (_ sortedArray: [Venue]) -> ()){
let ordering = Dictionary(uniqueKeysWithValues: sortingGeoArray.enumerated().map { ($1, $0) })
let sorted: [Venue] = unsortedArray.sorted{ ordering[$0.venueID]! < ordering[$1.venueID]! }
handler(sorted)
}
I have tried the sorting code above in multiple places through out my code by I always get a "Unexpectedly found nil while unwrapping an Optional value" at the following line (works when i test it an playground):
let sorted: [Venue] = unsortedArray.sorted{ ordering[$0.venueID]! < ordering[$1.venueID]!
On my debug window below, I have a feeling that the .map function into the let 'ordering' is not working and therefore finding nil on the next line
any help would be appreciated.
UPDATE: thanks to the support below, it appears that my Geofire query below in particular the 'append' to venueGeoKeys [string] array is not appending the key values, hence why found nil when I execute the function to sort.
let query = self.GEOFIRE_VENUES.query(at: location, withRadius: 1000)
query.observe(.keyEntered) { (key: String!, location: CLLocation!) in
self.venueGeoKeys.append(key)
}

how to get multiple records from db and put into array or map

I want to get multiple records from db and put into array or map.
this is my sample array with user ids
{"array":[133,136,137] }
this is my code
def array(conn, %{"array" => array}) do
userlist = %{}
Enum.each(array, fn(x) ->
Map.put(userlist, x, Repo.get(ApiDb.User, x))
end)
json conn, userlist
end
but this method return empty array
below is the console output
I think this approach is more optimized than #Dogbert's one (correct me if I'm wrong) because we ask directly Ecto to format each row in tuple then we convert the list of tuples into a map using the built-in Enum.into/2. For that, you'll want to import Ecto.Query in your current module and :
query = from user in ApiDb.User,
where: user.id in ^user_ids,
select: {user.id, user}
Repo.all(query) |> Enum.into(%{})
which yields
%{id1 => user1, id2 => user2...}
For the Poison encoding problem, I didn't encounter any problem regarding the conversion of number keys, as they get converted into strings automatically by Poison.
Hope this also helps :)
You cannot modify the value of a variable outside Enum.each from within Enum.each. For this specific case, I'd use a for to iterate through the list, fetch the user, and put it in a map with the id as key:
def array(conn, %{"array" => array}) do
users = for x <- array, into: %{}, do: {"#{x}", Repo.get(ApiDb.User, x)}
json conn, users
end
I'd suggest using id IN _ query here so that all the records are fetched in a single query:
def array(conn, %{"array" => array}) do
users = from(u in ApiDb.User, where: u.id in ^array) |> Repo.all
map = for user <- users, into: %{}, do: {"#{user.id}", user}
json conn, map
end

Reagent doesn't re-render on first transition of ratom

I've been working on a linked text-box and list-component, similar to a combobox or typeahead component. I want to be able type free text into the text-box, unless an item from the list is clicked, in which case overwrite the text-box with the selected item's text.
These two components are contained in a single, parent div, which owns the ratom that maintains the selection state. The ratom is initally nil, but is updated when a list-item is selected. I'd like the text-box's value to be updated only when the ratom's contents are not nil, and premit free entry when the ratom's value is nil.
Unfortunately, this works only for the second distinct click of the list-component, and thereafter. The text-box does not update on the first click, even though console inspection shows that it's value attribute is updated. So, it looks like a question of triggering rendering.
Here's the code (uses dommy)...
(defn text-box [selection]
[:input.jdropbtn
(into
{:type :text
:placeholder "Dropdown"
:on-change #(.log js/console
(let [v (-> % .-target .-value)
opts (-> % .-target .-nextSibling dom/children array-seq)]
;(doseq [t opts] (dom/toggle! t (clojure.string/includes? (dom/text t) v)))
))}
(when #selection
{:value (:value #selection)} ) ; This fails on first selection!
; {:value (:value #selection)} ; This prevents free typing!
)
]
)
(defn list-component [selection data-key items]
[:div.jdropdown-content
(for [i items]
(let [{:keys [id value] :as item} i]
^{:key id} [:a {data-key id :on-click #(reset! selection {:id id :value value}) } value])
)
]
)
;;;;;;;;;;;;;; parent
(defn my-droplist [data-key items]
(let [selection (r/atom nil) ]
(fn []
[:div.jdropdown {:id "jack"}
[text-box selection]
[list-component selection data-key items]
])
))
; invoked as:
[my-droplist :data-appid
[{:id 11 :value "Hey there!"}
{:id 22 :value "Hi there!"}
{:id 33 :value "Ho there!"}]]
Styling is essentially the same as the dropdown menu css example from w3schools (http://www.w3schools.com/css/css_dropdowns.asp).
TIA.
Ok, it's all about control; Reagent doesn't like sharing. If I always set the textbox's value from the ratom, and update the ratom on-change, it works. Also, the ratom is initialized to empty string instead of nil.
Thing is, simply dereferencing the ratom every time (as I was originally doing) did not help. Seems I have to actually write the ratom's value to the textbox's value each time.

Find method is returning wrong object from array?

I'm working on a Ruby project for a text based adventure game and have encountered an issue when trying to use the 'find' method in Ruby.
I've got a location class that all locations are instantiated from. Each location has a set of two coordinates, ([x, y] = [1, 1]) and the player also has their own set of coordinates to store where they are actually located.
The class method & initialise method
def self.findLocation(coord, attributeToPrint)
puts coord.inspect
puts ##finder.find { coord }.inspect
end
def initialize(newTitle, newDescription, newCoord)
#title = newTitle
#description = newDescription
#coordinate = newCoord
##finder << self
end
What I'm trying to do is store all of the locations in an array and have a class method for printing out the title and description of a location by using the find method to select the location with the matching coordinates to the player. The method I currently have passes the player's coordinate in coord parameter and uses the find method to check the array (which has all location objects within it) for the coordinate.
I've found many questions relating to this method but have had no success with any of the solutions found on these questions and no luck with any solution of my own. If I try and use a comparison statement such as #coordinate == coord the method will simply return nil and my current version returns an object, but only the object which is first in the array and does not return the location with the matching #coordinate attribute.
I would greatly appreciate any help with this issue as it is the main roadblock to making some progress on the text adventure game and allowing some interactivity. I am sure that I am using this method incorrectly and don't understand how it functions but the enumerator documentation hasn't helped me very much after looking at it and there is possibly a much better way of implementing this over a class method.
Reproducing the Issue
Location class (necessary parts)
class Location
##finder = Array.new
def self.findLocation(coord, attributeToPrint)
puts coord.inspect
puts ##finder.find { coord }.inspect
end
#Initialise locations here
def initialize(newTitle, newDescription, newCoord)
#title = newTitle
#description = newDescription
#coordinate = newCoord
##finder << self
end
Player class (necessary parts)
class Player
def initialize(playerHealth, playerLocation, playerInventory)
#health = playerHealth
#location = playerLocation
#inventory = playerInventory
end
Main script (necessary parts)
require_relative '../lib/player'
require_relative '../lib/location'
start = Location.new('Test 1', 'This is test 1.', [0, 0])
start2 = Location.new('Test 2', 'This is test 2.', [1,1])
start3= Location.new('Test 3', 'This is test 3.', [2, 2])
player = Player.new(100, [1,1], ['sword'])
#Input loop
loop do
Location.findLocation(player.getLocation, 'example')
end
You have to specify how find will match the stored records against the provided value. Specifically, you need to compare coord to the record's coord. To access the record's coord you need a getter method.
class Location
def self.findLocation(coord, attributeToPrint)
puts coord.inspect
puts ##finder.find { |location| location.coord == coord }.inspect
end
def coord
#coord
end
end
The way that find works is that it executes the block for every instance in the array, returning the first array element where the result is 'truthy' (i.e. not nil and not false). When you do { coord } the block returns the coord value immediately. coord is not nil and not false, so the first record is selected. When you did #coord == coord the #coord is undefined at the class level (it's nil) and so for all records the comparison was false so no record was selected, hence your nil result.
To print a specific attribute (say, title) you can also access the attribute with the getter method. and then send that method to the object.
class Location
def self.findLocation(coord, attributeToPrint)
puts coord.inspect
found_location = ##finder.find { |location| location.coord == coord }
puts found_location.send(attributeToPrint) if found_location
end
def coord
#coord
end
def title
#title
end
end
So now you can do...
Location.findLocation([1,1], 'title')
However...
It's much more flexible to have findLocation only be responsible for returning the object and then output the attribute outside the method... single responsibility principle...
class Location
def self.findLocation(coord)
##finder.find { |location| location.coord == coord }
end
def coord
#coord
end
def title
#title
end
end
So now you can do...
player_location = Location.findLocation([1,1])
puts player_location.title if player_location

Resources