Fork me on GitHub
#reagent
<
2018-02-10
>
kurt-o-sys14:02:11

I'm using reagent and want to integrate apollo. I'm pretty close, but I have some issue 'translating' js to cljs, or rather react to reagent compents (I think). The code to be translated would be:

import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

function UserList({ data } }) {
  return (<div>test</div>);
}

export default graphql(gql`
  query {allUsers {id name email}}
`)(UserList);
(from https://github.com/apollographql/react-apollo, simplified)

kurt-o-sys14:02:33

I get pretty close, but the last call doesn't work so far:

(defn user-mgmt-page []
  (let [graphql    (wp/import "react-apollo" "graphql")
        gql        (wp/import "graphql-tag")
        ql-query   (gql "query {allUsers {id name email}}")
        graphql-fn (graphql ql-query)
        comp       user-list]
    (graphql-fn (fn [data]
                  [:div "test"]))))
In the last line, I tried passing r/create-class and r/reactify to the graphql-fn.

kurt-o-sys14:02:51

I know the query works fine - I can call it using an apollo client like this: (.then (client.query #js {:query ql-query}) js/console.log)

mikerod14:02:47

@kurt-o-sys have you tried r/as-element

kurt-o-sys14:02:00

nope, let me try.

mikerod14:02:41

I’m only on phone so not sure on the lib completely. But you can also look into adapting r/adapt-react-component

kurt-o-sys14:02:17

oh, that works the other way around, doesn't it? 😛

mikerod14:02:40

I don’t think I understand the usage enough. If you need to take a react component from a lib to use in reagent you can wrap it with adapt react component

kurt-o-sys14:02:42

r/as-element same result

kurt-o-sys14:02:07

yeah... ok, let me search a little more.

mikerod14:02:10

Yeah as element is backwards. That’s to make reagent pass things to the Js side

mikerod14:02:42

@kurt-o-sys are both imports on a React component? If so adapt both of them and then they can be used similar to a normal Reagent component fn

kurt-o-sys14:02:36

oh, so instead of trying to pass a react component to the graphql-fn, turning them into reagent components? That would mean that graphql-fn accepts a reagent function - doesn't sound plausible to me (since that's a js function expecting well... a js function, I thought.

kurt-o-sys14:02:27

What actually happens is that the UserList function (component?) is updated whenever a component modifies data from the apollo API (whenever the query changes).

kurt-o-sys14:02:54

That's the aim. There seems to be a 'react' way of doing it, but I can't make it work in reagent so far.

kurt-o-sys14:02:35

So, the upper function UserList is a dumb component, the lower export default is used to well... connect the dumb component to the db state

mikerod14:02:23

You adapt react components to fit into your typical reagent model. Square braces etc.

mikerod14:02:53

If you have to call a react fn that expects a react component you do the r/as-element

mikerod14:02:53

So I think you have a few things going on. Some of your interop is react components (that can be a fn or a class In JS)

mikerod14:02:41

You may also be using just a plain JS fn. for the query. For that, you may just need to do appropriate clj js interop. Like clj->js

mikerod14:02:35

@kurt-o-sys

(defn user-mgmt-page []
  (let [graphql    (r/adapt-react-component (wp/import "react-apollo" "graphql"))
        gql       (r/adapt-react-component  (wp/import "graphql-tag"))
        ql-query   (gql "query {allUsers {id name email}}")
      ]

[graphql ql-query (r/as-element (fn [data]
                  [:div "test"])))))

mikerod14:02:01

This is Maybe in the right direction. Likely not complete

kurt-o-sys14:02:20

yeah, right... although actually graphql ql-query returns a function

kurt-o-sys14:02:37

export default graphql(gql`
  query {allUsers {id name email}}
`)(UserList);

kurt-o-sys14:02:53

so, first, I need to do:

graphql(gql`
  query {allUsers {id name email}}
`)

kurt-o-sys14:02:11

and use that function and pass a react component/function

mikerod14:02:15

Ok. Then call it with as-element around your hiccup form

kurt-o-sys14:02:31

ok... makes sense.

mikerod14:02:37

Sorry. About to take off on a plane. I tried. 😛

justinlee18:02:56

@kurt-o-sys i’ve just gone through this interop dance (with a different library). let me know if you are still having trouble.

kurt-o-sys18:02:23

still having trouble 🙂 - I mean, most interop is fine, I'm only struggling with this one.

justinlee18:02:37

the graphql call returns a react-style component, right?

kurt-o-sys18:02:47

right, I guess so

kurt-o-sys18:02:14

this is the example they give:

import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

function TodoApp({ data: { todos, refetch } }) {
  return (
    <div>
      <button onClick={() => refetch()}>Refresh</button>
      <ul>{todos && todos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>
    </div>
  );
}

export default graphql(gql`
  query TodoAppQuery {
    todos {
      id
      text
    }
  }
`)(TodoApp);

kurt-o-sys18:02:50

A 'normal' react function/component being TodoApp and using HoC to embed it in another component.

kurt-o-sys18:02:05

which is build by graphql(gql...`)

kurt-o-sys18:02:29

Basically, there are 3 places where I have to make the interop work properly: 1. where I mount the app:

import { ApolloProvider } from 'react-apollo';

ReactDOM.render(
  <ApolloProvider client={client}>
    <MyRootComponent />
  </ApolloProvider>,
  document.getElementById('root'),
);
becomes:
;; -------------------------
;; Initialize app
(defn mount-root []
  (let [LinkHTTP         (-> (wp/import "apollo-link-http")
                             (.createHttpLink #js {:uri ""}))
        InMemoryCache    (wp/import "apollo-cache-inmemory" "InMemoryCache")
        ApolloClient     (wp/import "apollo-client" "ApolloClient")
        client           (ApolloClient.
                          (clj->js {:link  LinkHTTP
                                    :cache (InMemoryCache.)}))
        ApolloProvider (wp/import "react-apollo" "ApolloProvider")]
    (reagent/render [(reagent/adapt-react-class ApolloProvider) {:client client}
                     [main-page]]
                    (.getElementById js/document "app"))))

kurt-o-sys18:02:36

(if I got it right)

justinlee18:02:39

so somewhere in your code you’re going to need something like [(adapt-react-class react-component) props children]. i’m just trying to sort out where

justinlee18:02:59

oh you have that

kurt-o-sys18:02:01

yeah, wait, here comes the next part 🙂

kurt-o-sys18:02:24

this part (replacing TodoApp with UserList):

import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

function UserList({ data }) {
  return (
    <div>
      data
    </div>
  );
}

export default graphql(gql`
  query {  ... }
`)(UserList);

kurt-o-sys18:02:18

becomes something like:

(defn user-list [{data :data}]
  [:div data])

(defn user-mgmt-page []
  (let [graphql          (wp/import "react-apollo" "graphql")
        gql              (wp/import "graphql-tag")
        query            "query {allUsers {id name email}}"
        enclose-with-gql (graphql (gql (clj->js [query])) (clj->js {}))
        comp             (-> user-list
                             r/reactify-component
                             enclose-with-gql
                             r/adapt-react-class)]
    comp
    ))

kurt-o-sys18:02:21

I thought...

kurt-o-sys18:02:26

but it doesn't work properly 😛

justinlee18:02:33

let’s see. so i just did this with react-dnd which uses these hoc functional injectors.

justinlee18:02:52

this is what i ended up with:

(defn draggable-notebook-link
  [notebook-id notebook-name]
  (let [source-decorator (react-dnd/DragSource
                          "notebook"
                          source-spec
                          source-collect)
        target-decorator (react-dnd/DropTarget
                           #js ["notebook" "doc"]
                           target-spec
                           target-collect)]
    (reagent/create-element
      (target-decorator
        (source-decorator (reagent/reactify-component editable-notebook-link)))
      #js {:notebook-id notebook-id
           :notebook-name notebook-name})))

justinlee18:02:15

DragSource and DropTarget work just like the graphql function

justinlee18:02:48

if you replace r/adapt-react-class with r/create-element, does that work?

kurt-o-sys18:02:58

let me check...

justinlee18:02:47

so i think with the r/adapt-react-class you would need to return [comp]

justinlee18:02:55

or just create the element directly

kurt-o-sys18:02:15

oh, right... that might make sense!

kurt-o-sys18:02:06

Uncaught TypeError: Cannot convert a Symbol value to a string with the r/create-element.

kurt-o-sys18:02:21

will try returning [comp]

justinlee18:02:35

oh god yes i got that too. let me read your code again more carefully

kurt-o-sys18:02:09

lol, be back in a minute 😛

justinlee19:02:04

question: in the javascript code, graphql took 1 argument but here you are passing it 2. is that right?

kurt-o-sys19:02:17

oh, euh, no?

kurt-o-sys19:02:31

oh yeah, I do.

kurt-o-sys19:02:45

doesn't matter, it's an optional map with parameters.

kurt-o-sys19:02:50

I'll remove the second one.

kurt-o-sys19:02:05

enclose-with-gql (graphql (gql (clj->js [query])))

justinlee19:02:34

hm. i tried changing my code to

[(reagent/adapt-react-class
      (target-decorator
        (source-decorator (reagent/reactify-component editable-notebook-link))))]
    {:notebook-id notebook-id
     :notebook-name notebook-name}))
to see if i understand what’s going on. but that doesn’t work.

kurt-o-sys19:02:38

when I log things:

comp -> {"id":null,"class":null}
comp is pretty empty.

justinlee19:02:05

oh wait of course not

kurt-o-sys19:02:01

ooh, it works! 😛

kurt-o-sys19:02:37

(defn user-list [{data :data}]
  [:div data])
was the problem (the last problem) I had 😛

kurt-o-sys19:02:05

data is an object, not something that can be printed.

kurt-o-sys19:02:17

(defn user-list [{data :data}]
  (js/console.log (str "data -> " data))
  [:div (js/JSON.stringify data)])

kurt-o-sys19:02:30

shows me the data in my browser 😛

kurt-o-sys19:02:36

without errors!

kurt-o-sys19:02:41

nice, thx, cool!!

justinlee19:02:06

great! just for the record, this:

[(reagent/adapt-react-class
      (target-decorator
        (source-decorator (reagent/reactify-component editable-notebook-link))))
     {:notebook-id notebook-id
      :notebook-name notebook-name}]))
and this
(reagent/create-element
      (target-decorator
        (source-decorator (reagent/reactify-component editable-notebook-link)))
      #js {:notebook-id notebook-id
           :notebook-name notebook-name})))
ARE equivalent, (although there is some camel/kebab shenanigans in the argument passing that confused me)

kurt-o-sys19:02:29

oh, nice, thx!

kurt-o-sys19:02:42

so the create-element should work as well? Will try. Great.

kurt-o-sys19:02:54

someone should write a blog how to do this...

justinlee19:02:19

i often think i should write a blog about something but i don’t have a blog so i don’t do it 🙂

justinlee19:02:20

the one thing i’m not sure was a good idea is that reagent functions used to take a props and children argument instead of taking an arbitrary list. interop would be considerably less confusing if they did it the old way

kurt-o-sys19:02:01

I don't have a blog either.

justinlee19:02:03

i might just toss up a gist

kurt-o-sys19:02:05

should start one 🙂

justinlee19:02:29

i think the angle i’d like to take is “reagent for experienced javascript/react programmers”

kurt-o-sys19:02:58

maybe, or not. I'm not an experienced js or react programmer.

kurt-o-sys19:02:36

I just happen to like cljs and the idea behind react, and reagent is the normal way to go. So I need to translate all that js/react stuff all the time.

kurt-o-sys19:02:07

It's something that turns many people down, that interop.

justinlee19:02:14

that’s a good point. i won’t call it that then because understanding the inner workings of reagent is just plain useful for doing interop

justinlee19:02:22

oh by the way, what is that wp/import call you keep making?

kurt-o-sys19:02:56

oh, that's just a wrapper to import webpack stuff.

kurt-o-sys19:02:12

(defn import
  ([module]
   (aget js/window.deps module))
  ([module object]
   (aget js/window.deps module object)))

kurt-o-sys19:02:24

(`import` may not be the best name, though)

kurt-o-sys19:02:35

it's just what js import does.

justinlee19:02:08

ah got it. btw check out cljs-oops for doing that instead of using aget

justinlee19:02:47

it’s a small but useful wrapper around the gcl getters, which is supposedly what we’re supposed to be using instead of aget

kurt-o-sys19:02:54

oh, ok, thx!

justinlee19:02:01

i’m also moving over to shadow-cljs right now because i do a lot of interop. it’s pretty slick if you have an afternoon to mess around with it

kurt-o-sys19:02:58

yeah... heard about it. For this project, I can't change anymore. I will for other ones. Need to check it first.