Skip to content

Instantly share code, notes, and snippets.

@Alex-Bakic
Last active November 16, 2019 01:17
Show Gist options
  • Save Alex-Bakic/a47035dcf915735f331f2658496a613d to your computer and use it in GitHub Desktop.
Save Alex-Bakic/a47035dcf915735f331f2658496a613d to your computer and use it in GitHub Desktop.

Extension of issue 6 , to do with reloading and the hidden bug highlighted in the /job common page.

The reloading of recommended jobs and the dashboard is currently solved, though leaving the applied page out has left bugs, and ones similiar to those in the last issue which is where we find a job in liked without the "liked" selected indication. This is partly because we don't re-render, but there is another issue underlying this one. Let's first take a look at the page function within wh.logged-in.personalised-jobs.views:

(defn page [type-of-jobs]
  (into
    [:div.main
     [:div.spread-or-stack
      [:h1 (str (-> type-of-jobs name str/capitalize)) " Jobs"]
      (when (= type-of-jobs :recommended)
         [:div.has-bottom-margin
          [link [:button.button "Improve recommendations"] :improve-recommendations :class "level-item"]])]]
    (let [parts (partition-all 3 (<sub [::subs/jobs]))
          has-applied? (some? (<sub [:user/applied-jobs]))]
      (cond
          (seq parts) (conj (vec (for [part parts]
                                  (into [:div.columns]
                                      (for [job part]
                                          [:div.column.is-4
                                            [job-card job (merge {:user-has-applied? has-applied?
                                                                  :logged-in? true}
                                                              (when (= type-of-jobs :recommended)
                                                                  {:on-close :reload-recommended})
                                                              (when (= type-of-jobs :liked)
                                                                  {:on-close :reload-liked}))]]))))
                        [:div.columns.is-centered.load-more-section
                         [:div.column.is-4.has-text-centered
                            (when (<sub [::subs/show-load-more?])
                              [:button.button {:on-click #(dispatch [::events/load-more type-of-jobs])} "Load more Jobs"])]])
          :else (case type-of-jobs
                  :recommended [[:p "Add some skills and preffered locations to your profile to see  recommendations."]]
                  :liked [[:p "Click on some " [icon "like" :class "like red-fill"] " to save jobs you like."]]
                  :applied [[:p "You haven't applied for any jobs yet... " [link "What are you waiting for?" :jobsboard :class "a--underlined"] "."]]
                  [[:p "No jobs found."]])))))

The "parts" data, that gather information from ::subs/jobs, is never strictly limited to that type of page as graphql will simply tag each job with the approriate "is it an applied or a liked job?" check. But nothing stops those jobs from still being in that group. We notice the implications of this when we use this page function for our liked jobs page, only to see "unliked" jobs slither through and become rendered. Cases where they do slither through are highlighted below, and this is to do with the "/job/job-slug" page and the like icon on that page. Due to the current fixes I administered to the :wh.events/toggle-job-like event, it meant that it required an action as an argument, so that it knows which page to reload, but we don't want to reload the page detailing the job, just because the user clicked like. Nothing new is being re-rendered.

;; within the client/common-pages/src/wh/job/views.cljc file

;; notice the "action" arg is left, and is for all such components at the moment ...
(defn like-icon [class]
  [icon "job-heart"
   :class (util/merge-classes class
                          (when (<sub [::subs/liked?]) (str class "--liked")))
   :on-click #(dispatch [:wh.events/toggle-job-like
                         {:id    (<sub [::subs/id])
                          :company-name (<sub [::subs/company-name])
                          :title (<sub [::subs/title])}])])

Initially I made components supply a :none arg ... which isn't pretty as it means that components will have to be aware of the functionality of ::toggle-job-like. But all we really have to do is realise anything outside of the cases doesn't need to be called, and so (when action) , then we'll dispatch the right reloader.

(reg-event-fx
  ::toggle-job-like-success
  db/default-interceptors
  (fn [{db :db} [id action]]
      (merge {:db (update-in db [:wh.user.db/sub-db :wh.user.db/liked-jobs] util/toggle id)}
          (when action
              {:dispatch (case action
                          :reload-recommended [:personalised-jobs/fetch-jobs-by-type :recommended 1]
                          :reload-dashboard [:wh.logged-in.dashboard.events/fetch-recommended-jobs]
                          :reload-liked [:personalised-jobs/fetch-jobs-by-type :liked 1])}))))

Now that will allow the icon to work correctly, but it doesn't change the structure of the ::subs/jobs subscription in any way, and as long it only tags jobs it means all sorts of bugs are possible, with jobs of different sections being included in the same vector. What we can do is wrap a filtering subscription around ::subs/jobs , as the tagging functionality is important, as it is at the stage that page is instantiated that we will need a marker.

;; unsure what to call these, as :user/liked-jobs is a sub, 
;; but as they're namespaced separated , it isn't an IMMEDIATE issue...

(reg-sub
 ::liked-jobs
 :<- [::jobs]
 (fn [all-jobs]
   (filterv :liked all-jobs)))

(reg-sub
 ::applied-jobs
 :<- [::jobs]
 (fn [all-jobs]
  (filterv :applied all-jobs)))

(reg-sub
  ::recommended-jobs
  :<- [::personalised-jobs]
  (fn [personalised-jobs]
    (::personalised-jobs/jobs personalised-jobs)))

Now to go into page and narrow down the data we need , and to keep all the different sections separated. I began by defining a few helper functions for each unique property of applied, liked and recommended:

;; helper for deciding the value of "action", narrowing down which page should be reloaded by
;; the like and blacklist events in client/src/wh/events.cljs
(defn on-close
  [type-of-jobs]
  (case type-of-jobs
    :recommended {:on-close :reload-recommended}
    :applied {:on-close :reload-applied}
    :liked {:on-close :reload-liked}
    {}))

;; need to specify job db, as the ::subs/jobs is a generalisation too far, as we cannot narrow down the jobs
;; themselves , depending on the type of job is what we work with, and that should be that. Allow the subscription
;; vector to be specified here , allowing page to never "go out of bounds" and avoid such bugs in the future.
(defn specify-subscription
  [type-of-jobs]
  (case type-of-jobs
    :recommended [::subs/recommended-jobs]
    :applied [::subs/applied-jobs]
    :liked [::subs/liked-jobs]))

(defn message
  [type-of-jobs]
  (case type-of-jobs
    :recommended [[:p "Add some skills and preffered locations to your profile to see recommendations."]]
    :liked [[:p "Click on some " [icon "like" :class "like red-fill"] " to save jobs you like."]]
    :applied [[:p "You haven't applied for any jobs yet... " [link "What are you waiting for?" :jobsboard :class "a--underlined"] "."]]
    [[:p "No jobs found."]]))

But we don't need all these case calls , and we can rework this into one , more compact fn. Destructuring goes a little smoother in page if we turn this tuple into a map, as we can use :keys[...] in page to keep the variable name the same as they key.

(defn job-type-data
  [type-of-jobs]
  (case type-of-jobs
    :recommended {:on-close :reload-recommended
                  :sub [::subs/recommended-jobs]
                  :message [[:p "Add some skills and preferred locations to your profile to see recommendations."]]}
    :liked       {:on-close :reload-liked
                  :sub [::subs/liked-jobs]
                  :message [[:p "Click on some " [icon "like" :class "like red-fill"] " to save jobs you like."]]}
                 ;; as we don't want this page to do any re-rendering, we don't need to provide on-close.
    :applied     {:sub [::subs/applied-jobs]
                  :message [[:p "You haven't applied for any jobs yet... " [link "What are you waiting for?" :jobsboard :class "a--underlined"] "."]]}))

And in page we can just destructure the info we get:

(defn page [type-of-jobs]
  (into
    [:div.main
     [:div.spread-or-stack
      [:h1 (str (-> type-of-jobs name str/capitalize)) " Jobs"]
      (when (= type-of-jobs :recommended)
        [:div.has-bottom-margin
         [link [:button.button "Improve recommendations"] :improve-recommendations :class "level-item"]])]]
    (let [{:keys [on-close sub message]} (job-type-data type-of-jobs)
          ;; this allows us to grab the data pertaining to only one type of page.
          parts (partition-all 3 (<sub sub))
          has-applied? (some? (<sub [:user/applied-jobs]))]
      (cond
          (seq parts) (conj (vec (for [part parts]
                                   (into [:div.columns]
                                      (for [job part]
                                         [:div.column.is-4
                                           [job-card job (merge {:user-has-applied? has-applied?
                                                                 :logged-in? true}
                                                                 (when on-close
                                                                    {:on-close on-close}))]]))))
                        [:div.columns.is-centered.load-more-section
                         [:div.column.is-4.has-text-centered
                            (when (<sub [::subs/show-load-more?])
                             [:button.button {:on-click #(dispatch [::events/load-more type-of-jobs])} "Load more Jobs"])]])
          :else (if message
                  message
                  [[:p "No jobs found."]])))))

So to recap, there are two fixes here. The first is to include an else clause for case, so that components that like-jobs and blacklist don't have to re-render anything, like the applied jobs page for example. This also allows the like icon in wh.job.views to not re-render the page displaying the job. But aside from this, we also remodelled the page component to work closer with each implementation, and by passing to the liked jobs page only the liked jobs, we can chalkf off another set of issues for now :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment