From 776c47491c14dd67775b208c8ec6a31df470ef1b Mon Sep 17 00:00:00 2001 From: Dan Holmsand Date: Mon, 12 Oct 2015 17:18:37 +0200 Subject: [PATCH] Make sure lifecycle callbacks work when called as native component --- src/reagent/impl/component.cljs | 56 +++++++++------ test/reagenttest/testreagent.cljs | 111 +++++++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 22 deletions(-) diff --git a/src/reagent/impl/component.cljs b/src/reagent/impl/component.cljs index 7ac8c5b..eb0bf3e 100644 --- a/src/reagent/impl/component.cljs +++ b/src/reagent/impl/component.cljs @@ -39,13 +39,29 @@ (props-argv c (.' c :props))) (defn get-props [c] - (-> (get-argv c) extract-props)) + (let [p (.' c :props)] + (if-some [v (.' p :argv)] + (extract-props v) + (shallow-obj-to-map p)))) (defn get-children [c] - (-> (get-argv c) extract-children)) + (let [p (.' c :props)] + (if-some [v (.' p :argv)] + (extract-children v) + (->> (.' p :children) + (.' util/react Children.toArray) + (into []))))) -(defn reagent-component? [c] - (-> (get-argv c) nil? not)) +(defn ^boolean reagent-class? [c] + (and (fn? c) + (some? (some-> c .-prototype (.' :reagentRender))))) + +(defn ^boolean react-class? [c] + (and (fn? c) + (some? (some-> c .-prototype (.' :render))))) + +(defn ^boolean reagent-component? [c] + (some? (.' c :reagentRender))) (defn cached-react-class [c] (.' c :cljsReactClass)) @@ -69,9 +85,6 @@ ;;; Rendering -(defn ^boolean reagent-class? [c] - (and (fn? c) - (some? (some-> c .-prototype (.' :reagentRender))))) (defn wrap-render [c] (let [f (.' c :reagentRender) @@ -146,8 +159,8 @@ (this-as c (reset! (state-atom c) (.call f c c)))) :componentWillReceiveProps - (fn componentWillReceiveProps [props] - (this-as c (.call f c c (get-argv c)))) + (fn componentWillReceiveProps [nextprops] + (this-as c (.call f c c (props-argv c nextprops)))) :shouldComponentUpdate (fn shouldComponentUpdate [nextprops nextstate] @@ -257,9 +270,9 @@ (defn create-class [body] {:pre [(map? body)]} - (let [spec (cljsify body) - res (.' util/react createClass spec)] - (cache-react-class res res))) + (->> body + cljsify + (.' util/react createClass))) (defn component-path [c] (let [elem (some-> (or (some-> c (.' :_reactInternalInstance)) @@ -285,20 +298,21 @@ "")) "")) - (defn fn-to-class [f] (assert (ifn? f) (str "Expected a function, not " (pr-str f))) - (warn-unless (not (and (fn? f) - (some? (some-> f .-prototype (.' :render))))) + (warn-unless (not (and (react-class? f) + (not (reagent-class? f)))) "Using native React classes directly in Hiccup forms " "is not supported. Use create-element or " "adapt-react-class instead: " (let [n (util/fun-name f)] (if (empty? n) f n)) (comp-name)) - (let [spec (meta f) - withrender (assoc spec :reagent-render f) - res (create-class withrender)] - (cache-react-class f res))) + (if (reagent-class? f) + (cache-react-class f f) + (let [spec (meta f) + withrender (assoc spec :reagent-render f) + res (create-class withrender)] + (cache-react-class f res)))) (defn as-class [tag] (if-some [cached-class (cached-react-class tag)] @@ -306,4 +320,6 @@ (fn-to-class tag))) (defn reactify-component [comp] - (as-class comp)) + (if (react-class? comp) + comp + (as-class comp))) diff --git a/test/reagenttest/testreagent.cljs b/test/reagenttest/testreagent.cljs index 43e0908..5aa05bc 100644 --- a/test/reagenttest/testreagent.cljs +++ b/test/reagenttest/testreagent.cljs @@ -728,6 +728,8 @@ comp (atom c1) c2 (fn [] (apply vector @comp @arg)) + cnative (fn [] + (into [:> @comp] @arg)) check (fn [] (is (= (:initial-state @res) {:at 1 :args [@t]})) @@ -741,7 +743,7 @@ (reset! arg ["a" "c"]) (r/flush) (is (= (:will-receive @res) - {:at 5 :args [@t [@comp "a" "b"]]})) + {:at 5 :args [@t [@comp "a" "c"]]})) (is (= (:should-update @res) {:at 6 :args [@t [@comp "a" "b"] [@comp "a" "c"]]})) (is (= (:will-update @res) @@ -762,6 +764,111 @@ (is (= (:will-unmount @res) {:at 10 :args [@t]}))))) + +(deftest lifecycle-native + (let [n1 (atom 0) + t (atom 0) + res (atom {}) + oldprops (atom nil) + newprops (atom nil) + add-args (fn [key args] + (swap! res assoc key + {:at (swap! n1 inc) + :args (vec args)})) + render (fn [& args] + (this-as + c + (when @newprops + (is (= @newprops) (first args)) + (is (= @newprops) (r/props c))) + (is (= c (r/current-component))) + (is (= (first args) (r/props c))) + (add-args :render + {:children (r/children c)}) + [:div (first args)])) + ls {:get-initial-state + (fn [& args] + (reset! t (first args)) + (reset! oldprops (-> args first r/props)) + (add-args :initial-state args) + {:foo "bar"}) + :component-will-mount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-mount args)) + :component-did-mount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :did-mount args)) + :should-component-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :should-update args) true) + :component-will-receive-props + (fn [& args] + (reset! newprops (-> args second second)) + (this-as c + (is (= c (first args))) + (add-args :will-receive (into [(dissoc (r/props c) :children)] + (:children (r/props c)))))) + :component-will-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-update args)) + :component-did-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :did-update args)) + :component-will-unmount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-unmount args))} + c1 (r/create-class + (assoc ls :reagent-render render)) + defarg [{:foo "bar"} "a" "b"] + arg (r/atom defarg) + comp (atom c1) + cnative (fn [] + (into [:> @comp] @arg)) + check (fn [] + (is (= (:initial-state @res) + {:at 1 :args [@t]})) + (is (= (:will-mount @res) + {:at 2 :args [@t]})) + (is (= (:render @res) + {:at 3 :args [[:children ["a" "b"]]]})) + (is (= (:did-mount @res) + {:at 4 :args [@t]})) + + (reset! arg [{:f "oo"} "a" "c"]) + (r/flush) + + (is (= (:will-receive @res) + {:at 5 :args [{:foo "bar"} "a" "b"]})) + (let [a (:should-update @res) + {at :at + [this oldv newv] :args} a] + (is (= at 6)) + (is (= (count (:args a)) 3)) + (is (= (js->clj oldv) (js->clj [@t @oldprops]))) + (is (= newv [@t @newprops]))) + (let [a (:will-update @res) + {at :at + [this newv] :args} a] + (is (= at 7)) + (is (= newv [@t @newprops]))) + (is (= (:render @res) + {:at 8 :args [[:children ["a" "c"]]]})) + (let [a (:did-update @res) + {at :at + [this oldv] :args} a] + (is (= at 9)) + (is (= oldv [@t @oldprops]))))] + (when isClient + (with-mounted-component [cnative] check) + (is (= (:will-unmount @res) + {:at 10 :args [@t]}))))) + (defn foo [] [:div]) @@ -817,7 +924,7 @@ (is (= e {:error (lstr "Error rendering component (" stack1 ")")}))) - (let [e (debug/track-warnings #(r/reactify-component nat))] + (let [e (debug/track-warnings #(r/as-element [nat]))] (is (re-find #"Using native React classes directly" (-> e :warn first))))