Fork me on GitHub
#re-frame
<
2020-06-29
>
dabrazhe13:06:00

Hi re-framers. I've setup a re-frame project for AWS amplify. It works but it's kind of black box to me. What's the minimal viable effort i need to make to learn how to plugin the back end API (lambdas on AWS). Ie the crash course in re-frame for back end /infrastructure engineers?

Jakob Durstberger14:06:42

Hey, I am playing around with re-frame + AWS amplify myself. Are you using the auth part of amplify? And what do you mean with “learn how to plug the backend api”?

pwojnowski14:06:25

It walks you thru setting up Cognito (to auth users), Api Gateway (REST api to your lambda) and lambda.

pwojnowski14:06:55

Then you can use modules from aws-amplify, which you need.

pwojnowski14:06:00

In package.json you would need sth like:

"dependencies": {
    "@aws-amplify/api": "^1.2.4",
    "@aws-amplify/auth": "^1.5.0",
    "@aws-amplify/core": "^1.2.4",

pwojnowski14:06:44

Then in somewhere in the code (maybe your-app.core ns) you could add sth like:

(defn ^:dev/after-load start []
  (routes/app-routes)
  (services/configure-amplify)
  (views/init!))

(defn ^:export main []
  (re-frame/dispatch-sync [::events/initialize-db])
  (start))

pwojnowski14:06:06

Where configure-amplify does sth like:

(ns theapp.services
  (:require
   ["@aws-amplify/auth" :default Auth]
   ["@aws-amplify/api" :default API]
   [re-frame.core :as re-frame]))

;; (set! (.. Amplify -Logger -LOG_LEVEL) "DEBUG")

(defn configure-amplify []
  (print "Configuring AWS Amplify.")
  (let [js-config (clj->js (config/aws-config))]
    (.configure Auth js-config)
    (.configure API js-config)))

pwojnowski14:06:25

Then just call AWS api methods like in JS. Just need to handle promises somehow - I used Promesa library.

dabrazhe15:06:38

@U0DTG4DNC I guess I need to understand how to render the results in the page. Say I've got an endpoint that would return a clj hashmap after I parse all that json. How to i call this endpoint from re-frame and how do I display results? Some sort of hiccup library? I know very little of javascript and don't expect people to explain in slack, I'd like to know what shall I read first.

Jakob Durstberger15:06:02

Sorry just to clarify. Are you not doing all of this with clojurescript? Or do you intend to run javascript in your lambda?

dabrazhe15:06:41

The lambdas are a separate CLJ code base and actually the tech behind them is transparent to the front end. Think of them as a common Rest/graphql endpoitns

Jakob Durstberger15:06:46

Ok so is your question just about how to consume the API from your re-frame frontend?

Jakob Durstberger15:06:12

I don’t know if this is any help to you. I am currently figuring a lot of this stuff out myself. I don’t properly handle the response from my API yet, but that’s how I make the call to the backend. https://gitlab.com/JDurstberger/nozuma/-/blob/master/website/src/cljs/website/dashboard/events.cljs Please be aware that I haven’t separated out any of the concerns into separate files so it’s a bit of a mess right now.

dabrazhe16:06:35

I am looking at it; you are using ajax, doesn't reframe have some idiomatic way to make async requests with callbacks or promises?

Jakob Durstberger17:06:55

I looked into using the reframe-http-fx effect handler, but I couldn’t figure out how to combine it with amplify returning the user session in a promise. https://github.com/Day8/re-frame-http-fx As I said, still figuring a lot of that out myself 😄

dabrazhe19:06:33

This -fx looks promising. Actually the user session is returned by congito and I did not have to do much about it. I used this guide to set ip up https://github.com/omnyway-labs/amplify_cljs_example

chucklehead19:06:03

it may not matter for what you're doing, but just fyi, that example uses the legacy Amplify react UI components (aws-amplify-react instead of @aws-amplify/ui-react)

thanks 3
lostineverland00:07:46

I’m in the same boat as you guys. Amplify + re-frame + graphQL + cognito.

lostineverland00:07:32

I went down a rabbit hole of go blocks for the async calls to appsync. If you guys have a channel i would love to join 🙂

Jakob Durstberger07:07:50

I don’t think there is a channel for that yet #amplify-whyyyy 😄

dabrazhe09:07:55

@U0DTG4DNC @jakob.durstberger Do you know how to create aws-exports.js automatically ? I redeployed the stack in another account and the whole file is off, naturally.

pwojnowski09:07:12

I've got whole configuration as cljs map:

(defn aws-config []
  (let [env (resolve-env)]
    (print "Current env:" env)
    {
     :API {:endpoints [{:name api-name :endpoint (format-endpoint-url env)}]}
     :Auth { ... }

Jakob Durstberger09:07:35

I do the same as pwojnowski

pwojnowski09:07:34

Then in the code I just convert it to js:

(defn configure-amplify []
  (print "Configuring AWS Amplify.")
  (let [js-config (clj->js (config/aws-config))]
    (.configure Auth js-config)
    (.configure API js-config)))

dabrazhe11:07:31

@U0DTG4DNC what's the full function, how do you export it to the aws-exports.js file? I guess the file is needed for the amplify cli to deploy properly?

pwojnowski11:07:34

There's no need to export it to the aws-exports.js file. You just pass js-config to the configure functions as above. They configure Amplify modules. AFAIR in Amplify docs there's that you can use either aws-externs.js file or configure them using these functions.

lostineverland16:07:16

amplify pull will recreate the aws-exports.js file.

lostineverland16:07:56

In my project I’m also making use of AppSync’s GraphQL service. Amplify creates the boilerplate code for all CRUD which I can then make use of in re-frame. The actual graphQL calls are tucked within a reg-fx call. I’m curious if this sounds appropriate to you?

dabrazhe20:07:46

@U3ARMQ8Q3 amplify pull helped, thanks!

👍 3
dabrazhe15:06:13

@jakob.durstberger I am using Cognito. I can login and display the welcome page. What i mean I to be able call and asynchronously get the call back and display the result on the page. the API will retrieve data eg from S3 and I want to display them the various results from the several calls as they arrive.

mdallastella16:06:49

Hi everyone, I am stuck with a "problem". I'm using a react component, material-table, which takes a data prop. This prop can either be a vector of maps or a function that must return a js/Promise. How can I deal with it from a re-frame point of view?

p-himik16:06:47

Just pass a vector of maps, don't use the promise interface.

p-himik16:06:41

All the fetching and remote-related stuff will be done in re-frame events/effects.

p-himik16:06:39

But that works only if you can avoid using built-in pagination controls OR if you can hook them up to custom functions that would dispatch the relevant events. If neither is the case, you can still use the promise interface, but you will have to dispatch events that accept the resolve function as a parameter. Not ideal, just at the whole promise interface, but it will work.

mdallastella16:06:23

Thanks, that's one way to deal with it

mdallastella17:07:16

It worked. It's not fancy, but it's a workaround.

mdallastella16:06:37

Yes, but I found nothing that helps with my case... 😢

Jakob Durstberger16:06:53

I saw that p-himik responded on your other post. Did that help you?

mdallastella16:06:56

I'm going to try asap and report back

kelveden18:06:58

I think that I'm missing something obvious here but I'll ask anyway... I have an effectual handler that makes an HTTP request and I want to include a bearer token with the request. That token is available from a window.getToken javascript function - but that function returns a promise. So my handler looks something like this:

(rf/reg-event-fx
  :search
  [app-state-spec-interceptor]
  (fn [_ _]
    (-> (. js/window getToken)
        (.then (fn [token]
                 {:http-xhrio {:method          :get
                               :uri             "/some/url"
                               :headers         {"Authorization" (str "Bearer " token)}
                               :format          (ajax/json-request-format)
                               :response-format (ajax/json-response-format {:keywords? true})
                               :on-success      [:search-ready]
                               :on-failure      [:search-failed]
                               :timeout         5000}})))))
But this doesn't work as the function is returning a promise rather than a map. Does anyone have a suggestion as to how I should be doing this with re-frame?

whoops23:06:25

Honestly, I kind of wish co-fx could be async specifically for situations like this. But they can't, so what you have to so instead is fine a second event. Going all in on the re-frame way it'd look something like:

(rf/reg-fx
 ::get-token
 [to-dispatch]
 (-> (.getToken js/window)
     (.then #(rf/dispatch [::got-token %]))))

(rf/reg-event-fx
 ::got-token
 (fn [_ [_ token]]
   {:http-xhrio {:method          :get
                 :uri             "/some/url"
                 :headers         {"Authorization" (str "Bearer " token)}
                 :format          (ajax/json-request-format)
                 :response-format (ajax/json-response-format {:keywords? true})
                 :on-success      [:search-ready]
                 :on-failure      [:search-failed]
                 :timeout         5000}}))


(rf/reg-event-fx
  :search
  [app-state-spec-interceptor]
  (fn [_ _]
    {::get-token ::got-token}))

kelveden23:06:31

Thanks! I'll give that a go. I've not read about that method of chaining events together: I'll have to dig into the docs again.

Jakob Durstberger07:06:44

Interesting, I have the exact same issue and just used the AJAX lib. It doesn’t feel right but the above mentioned method is also weird to me, as I’ll need that token for a lot of calls 😕

kelveden15:07:27

In the end I actually wrapped up the defining of the two "chained" handlers into a separate function:

(rf/reg-fx
  :get-token
  (fn [{:keys [dispatch args]}]
    (-> (.getToken js/window)
        (.then #(let [dispatch-args (vec (concat [dispatch %] args))]
                  (rf/dispatch dispatch-args))))))

(defn reg-token-event-fx
  ([id interceptors handler]
   (let [with-token-id (keyword (str (name id) "-with-token"))]
     (rf/reg-event-fx with-token-id handler)
     (rf/reg-event-fx
       id
       interceptors
       (fn [_ [_ & args]]
         (cond-> {:get-token {:dispatch with-token-id}}
                 (not (empty? args)) (assoc-in [:get-token :args] args))))))
  ([id handler]
   (reg-token-event-fx id nil handler)))
then a handler definition looks something like this:
(reg-token-event-fx
  :search
  [app-state-spec-interceptor]
  (fn [{{:keys [search-text]} :db} [_ token]]
    {:http-xhrio {:uri     (str "/search?q=" search-text)
                  :headers {"Authorization" (str "Bearer " token)}
                  ...}}))

kelveden15:07:42

It works great!