Search code examples
javascriptreactjsclojurescriptreact-spring

Javascript to Clojurescript interop with react-spring


I am trying to implement react-spring (https://www.react-spring.io/docs/hooks/basics) into my Clojurescript proct and I am struggling to translate this to clojurescript

import {useSpring, animated} from 'react-spring'
function App() {
  const props = useSpring({opacity: 1, 
                           from: {opacity: 0}})
  return <animated.div style={props}>I will fade in</animated.div>
}

So far this is what I have done: I have required the following:

(:require 
[useSpring]
[animated])

and in a let block I have something like so:

(defn example-app []
   (let [props (useSpring (->js {:opacity 1
                                 :from  {opacity: 0}}))]
    (def props props)
    [:animated.div {:style props} "I will fade in"]
))

The variable props returns this :

#js{:opacity #object[AnimatedValue [object Object]]}

Than this is how I render the animated object

(react-dom/render
(hx/f [example-app])
(goog.dom/getElement "example-app"))

This is the error that I get

#object[Error Invariant Violation: Objects are not valid as a React child (found: object with keys {ns, name, fqn, _hash, cljs$lang$protocol_mask$partition0$, cljs$lang$protocol_mask$partition1$}). If you meant to render a collection of children, use an array instead.
in core$example_app]

What am I doing wrong? What am I missing?


Solution

  • I did manage to get a basic example to work, hopefully it will help you get started:

    • I cloned my example repo: https://github.com/dfuenzalida/cljs-react-intl/ (I created this for a test on react-intl, will work for you too)
    • From the checked out repo, I installed react-spring with yarn install react-spring
    • I edited the file shadow-cljs.edn to add the dependency on lilactown/hx:
    {:source-paths ["src"]
     :dependencies [[lilactown/hx "0.5.3"]]
     :dev-http {8080 "target/"}
     :builds {:app {:output-dir "target/"
                    :asset-path "."
                    :target :browser
                    :modules {:main {:init-fn app.main/main!}}
                    :devtools {:after-load app.main/reload!}}}}
    

    I replaced the contents of src/app/main.cljs with the react-spring example, adapted a bit:

    (ns app.main
      (:require [hx.react :as hx :refer [defnc]]
                ["react-dom" :as react-dom]
                ["react-spring" :as spring]))
    
    (defnc AppComponent [{:keys [title]}]
      (let [props (spring/useSpring (clj->js {:opacity 1 :from {:opacity 0}}))]
        [:<>
         [spring/animated.div {:style props} title]]))
    
    ;; App initialization
    
    (defn mount-root []
      (react-dom/render
       (hx/f [AppComponent {:title "I will fade in"}])
       (js/document.getElementById "app")))
    
    (defn main! []
      (mount-root)
      (println "[core]: loading"))
    
    (defn reload! []
      (mount-root)
      (println "[core] reloaded"))
    

    Finally, ran yarn install to download any missing deps and then yarn run html and yarn shadow-cljs watch app to start the compiler in watch mode. The console will show something like:

    $ yarn shadow-cljs watch app
    yarn run v1.17.3
    $ /tmp/cljs-react-intl/node_modules/.bin/shadow-cljs watch app
    shadow-cljs - config: /tmp/cljs-react-intl/shadow-cljs.edn  cli version: 2.8.74  node: v10.16.0
    shadow-cljs - updating dependencies
    ...
    shadow-cljs - dependencies updated
    shadow-cljs - HTTP server available at http://localhost:8080
    shadow-cljs - server version: 2.8.74 running at http://localhost:9630
    shadow-cljs - nREPL server started on port 43685
    shadow-cljs - watching build :app
    [:app] Configuring build.
    [:app] Compiling ...
    [:app] Build completed. (156 files, 11 compiled, 0 warnings, 9.21s)
    

    Once you load your app at http://localhost:8080, you should see something like:

    screenshot of code and browser

    The animation actually works, but I couldn't figure how to make the other examples to work (eg. the scroll example or the counter).

    I hope this helps!