This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-04-26
Channels
- # announcements (10)
- # aws (5)
- # babashka (27)
- # beginners (175)
- # boot (1)
- # braveandtrue (2)
- # calva (11)
- # cider (13)
- # clj-kondo (91)
- # cljs-dev (54)
- # cljsrn (20)
- # clojure (164)
- # clojure-gamedev (3)
- # clojure-uk (43)
- # clojurescript (185)
- # core-async (6)
- # core-typed (1)
- # cursive (1)
- # docker (2)
- # emacs (2)
- # figwheel-main (78)
- # fulcro (69)
- # off-topic (20)
- # pathom (30)
- # planck (3)
- # re-frame (6)
- # reagent (70)
- # reitit (26)
- # ring (1)
- # shadow-cljs (120)
- # tools-deps (6)
- # vim (9)
I've finally merged the functional component support to Reagent master: https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md#features-and-changes alpha release will follow "soon"
I've written some docs and examples about the new features: https://github.com/reagent-project/reagent/blob/master/doc/ReagentCompiler.md https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md#function-components https://github.com/reagent-project/reagent/blob/master/examples/functional-components-and-hooks/src/example/core.cljs And I still have one idea about making it easy to use functional components even with default options: https://github.com/reagent-project/reagent/issues/494
does it mean that when using {:functional-components? true}
you can't use ratoms anymore?
I'd propose :fn
for functional components, those who fancy fancy symbols can use ligatures
No, functional components work nearly the same as class components.
by no you mean you can still use them? So what kind of breakings can one expect from enabling the option?
Ratoms work. Hard to say, r/current-component
returns a mocked object as there is no real Component instance, so if you use that to access component this
and do something with that, that will probably break.
https://github.com/reagent-project/reagent/blob/master/doc/ReagentCompiler.md#functional-components-implementation I added more notes about the features and differences
r/render doesn't exist here https://github.com/reagent-project/reagent/blob/master/doc/ReagentCompiler.md#reagent-compiler ?
Fixed
Ah missed that one
for react-native I didn't touch the current code (r/reactify-component root)
and used
(def functional-compiler (r/create-compiler {:functional-components? true}))
(r/set-default-compiler! functional-compiler)
Did that work?
(defn top-safe-area-view
[props & children]
[:> SafeAreaConsumer {}
(fn [insets]
(r/as-element
(into [view (assoc-in (js->clj props) [:style :padding-top] (.-top insets))]
children)))])
(defn bottom-safe-area-view
[props & children]
[:> SafeAreaConsumer {}
(fn [insets]
(r/as-element
(into [view (assoc-in (js->clj props) [:style :padding-bottom] (.-bottom insets))]
children)))])
(defn keyboard-avoiding-view
[props & children]
[:> Header-height-context-consumer {}
(fn [header-height]
(r/as-element
(into [keyboard-avoiding-view-class
(cond-> (js->clj props)
platform/ios? (assoc :behavior :padding
:keyboardVerticalOffset header-height))]
children)))])
Phew nice. I've quickly tested this with some work projects and I've seen some warnings at some point, but didn't test the latest version yet.
previously I had to use a hack with a ratom where I rendered the hook in a dummy function and reset! the atom in it to pass to the view
Did you see this context example I added a month ago: https://github.com/reagent-project/reagent/blob/master/examples/react-context/src/example/core.cljs
The last variant, with create-element
doesn't convert properties
yes but previously when using as-element the props where not passed down correctly the namespaced keywords lose their namespaces and the sets become vectors
this was my workaround to preserve args 😄
(defn top-safe-area-view
[props & children]
(let [top-inset (r/atom nil)]
(fn [props & children]
[:<>
[:> SafeAreaConsumer {}
(fn [insets]
(reset! top-inset (.-top insets))
nil)]
(into [view (assoc-in (js->clj props) [:style :padding-top] @top-inset)]
children)])))
@U061V0GG2 correct me if I'm wrong but now props are passed without this issue right?
I don't think anything should affect how props are passed in this case.
Why do you need js->clj
call?
this is the code that caused an issue:
(defn- keyboard-avoiding-view-element [args]
(let [height (useHeaderHeight)
{:keys [props children]} (js->clj args :keywordize-keys true)]
(r/as-element
(into [keyboard-avoiding-view* (cond-> (js->clj props)
platform/ios? (assoc :behavior :padding
:keyboardVerticalOffset height))]
children))))
(defn keyboard-avoiding-view
"custom component, to be used before vanilla RN KeyboardAvoidingView is not fixed on ios devices with a notch"
[]
(let [this (r/current-component)]
[:> keyboard-avoiding-view-element {:props (r/props this)
:children (r/children this)}]))
If use use :>
to call keyboard-avoiding-view-element
props are converted to JS objects, which is inconvenient here as it is Cljs function. You could use create-element
directly to preserve Cljs values, then the other components would probably be simpler.
(per issue 494, I'll probably add another shortcut which would work like create-element
)
I didn't write this one so I'm not sure why it was using r/props instead of passing args
but now I can just do this with reagent 1
(defn top-safe-area-view
[props & children]
(into [view (assoc-in props
[:style :padding-top]
(.-top (useSafeArea)))]
children))
(defn bottom-safe-area-view
[props & children]
(into [view (assoc-in props
[:style :padding-bottom]
(.-bottom (useSafeArea)))]
children))
(defn keyboard-avoiding-view
[props & children]
(into [keyboard-avoiding-view-class
(cond-> props
platform/ios? (assoc :behavior :padding
:keyboardVerticalOffset (useHeaderHeight)))]
children))
If use use :> to call keyboard-avoiding-view-element props are converted to JS objects, which is inconvenient
yes I think that was the issue, as the conversion is lossy for namespace keywords and sets, and as a result any view that was using this component ended up with subtle bugs because of that
In what case? If you use r/create-class
that will obviously always create a class component
but the following works
(defn top-safe-area-view
[props & children]
(let [^js insets (useSafeArea)]
(fn [props & children]
(into [view (assoc-in props
[:style :padding-top]
(.-top insets))]
children))))
(defn bottom-safe-area-view
[props & children]
(let [^js insets (useSafeArea)]
(fn [props & children]
(into [view (assoc-in props
[:style :padding-bottom]
(.-bottom insets))]
children))))
(defn keyboard-avoiding-view
[props & children]
(let [header-height (useHeaderHeight)]
(fn [props & children]
(into [keyboard-avoiding-view-class
(cond-> props
platform/ios? (assoc :behavior :padding
:keyboardVerticalOffset header-height))]
children))))
yes view is (def view (r/adapt-react-class (.-View ^js rn)))
so I need to call the hooks outside
thank you so much for this work being able to use hooks so easily with the current context of the js ecosystem using hooks everywhere now is a game changer
adapt-react-class
name can be a bit confusing, the React component could be a function and the call won't change it to a class. But not sure if View is class or Fn here.
const View: React.AbstractComponent<
ViewProps,
React.ElementRef<typeof ViewNativeComponent>,
> = React.forwardRef((props: ViewProps, forwardedRef) => {
return (
<TextAncestor.Provider value={false}>
<ViewNativeComponent {...props} ref={forwardedRef} />
</TextAncestor.Provider>
);
});
I'm wondering if that could be because (r/set-default-compiler! functional-compiler) is called too late
If you call it before reagent.dom/render
call, should be fine
you use (.registerComponent ^js react/app-registry "clash" #(r/reactify-component root))
yep seems like it's the issue, I have a macro to def screens like this
([name component]
`(def ~name
(do (reagent.core/set-default-compiler! (reagent.core/create-compiler {:functional-components? true}))
(reagent.core/create-class
{:reagent-render
(fn []
@app.navigation/cnt
;;NOTE this can be useful to debug re-rendering
~(when log-react-lifecycles
`(taoensso.timbre/debug ~(str "rerender screen: " *ns* "/" name)))
~component)}))))
Well, before the reacitfy-component
call in this case
I think that because my defscreen macro is doing a def create-class the create-class is exectuted before the reactify-component, and before set-default-compiler is ran because those are in the core namespace, and it is evaluated last
as for why I'm doing that, the reason is that react-navigation is dynamically setting up the routes, and I just wanted to define the screens only once, so that when remounting a route there is no need to recreate the whole screen class again
I suppose it might happen whenever you have a lib that takes a component as argument, and you decide to do a def (reagent/create-class ...
Instead of (only) calling set-default-compiler!
you can also provide the compiler option to whatever is converting Hiccup-syntax to React elements.
Maybe as-element ... compiler
inside create-class render
(defmacro defscreen
[name component]
`(def ~name
(reagent.core/create-class
{:reagent-render
(fn []
@app.navigation/cnt
;;NOTE this can be useful to debug re-rendering
~(when log-react-lifecycles
`(taoensso.timbre/debug ~(str "rerender screen: " *ns* "/" name)))
~component)})))
this is the macro, it works well now that I set compiler options in the cljs namespace of the macro
not sure where i'd put as-element here, would it make sense to have the option to set it in create-class as well?
Ah well, in fact create-class takes compiler as optional second parameter.
https://github.com/reagent-project/reagent/blob/master/doc/ReagentCompiler.md#functional-components-implementation I added more notes about the features and differences
is there any perfomance impact (positive or negative) to usingthe new functional-components?
compiiler options?
Not sure - I did some very simple benchmarks but didn't find much impact.
Not sure - I did some very simple benchmarks but didn't find much impact.