status-im / status-mobile

a free (libre) open source, mobile OS for Ethereum
https://status.app
Mozilla Public License 2.0
3.85k stars 983 forks source link

[EPIC] Move away from Reagent #18800

Open flexsurfer opened 4 months ago

flexsurfer commented 4 months ago

Reagent compiles hiccup

Hiccup is a library that was created for Clojure which was mostly used on HTTP servers for generating HTML templates and it was very convenient for that. But do we need it in our RN application, do we use all its benefits, the answer is no. So basically we don’t need hiccup, and we could just use function calls like

(defn greeting [name] 
  (text (str "Hello " name " Welcome!")))

(defn app []
   (greeting "Taylor"))

currently, we just paying the computation price just because Reagent uses Hiccup

Reagent manages state with reactive atoms and forceUpdate React components

With the advent of React hooks for the state, this is no longer a killer feature, so it can be easily replaced by React hooks. But here there is more of a struggle between approaches, Clojure programmers will say that the use of atoms is more idiomatic, on the other hand, JS React developers and accordance with the React development standards

Reagent allows to pass props as arguments instead of one prop object how it’s in React

It’s a downside. It’s confusing, because React always passes props in the first argument as an object, and it might be confusing for devs when they work with Reagent. It might be restricted by guidelines, but it’s hard to follow

Reagent converts kebab to camel case and cljs to js objects

This is controversial, for some devs, it's more convenient, but for others not and it's better to use "native" js props in camelCase for them

Reagent uses deprecated classes instead of functional components

When Reagent was created, there were only class components, they were stateful and had different properties for the component lifecycle. Then later React replaced them by light functional stateless components and introduced hooks for them. Hooks can’t be used in class components, that’s why they can’t be used in Reagent functions.

An article with more technical detail can be found here Reagent under the hood

Also here the first successful experiment in UI development without Reagent. But there has been a lot of discussion and some disagreement, so we must develop a program of steps to phase out the Reagent gradually

First, we need to replace reactive atoms with react hooks for the state, but hooks can be used only in functional components.

so the steps will be as follows

this might take some time, because it cannot be automated, also at that time we should have an agreement in the team that reactive atoms are not used anymore in the new code

https://github.com/status-im/status-mobile/issues/18803

at this point reagent will be used only as reactive atom in re-frame subscriptions,

then later we could decide if we want go next and replace hiccup with functions calls, and replace cljs data for props with js data

flexsurfer commented 4 months ago

Example of reagent atom replacement

before

(defn- view-internal
  [_]
  (let [pressed?     (reagent/atom false)
        on-press-in  #(reset! pressed? true)
        on-press-out #(reset! pressed? nil)]
    (fn
      [{:keys [on-press on-long-press disabled? theme container-style]}]
      [rn/pressable
       {:accessibility-label :log-out-button
        :on-press            on-press
        :on-press-in         on-press-in
        :on-press-out        on-press-out
        :on-long-press       on-long-press
        :disabled            disabled?
        :style               (merge (style/main {:pressed?  @pressed?
                                                 :theme     theme
                                                 :disabled? disabled?})
                                    container-style)}
       [icon/icon :i/log-out {:color (if pressed? colors/white-opa-40 colors/white-opa-70)}]
       [text/text {:weight :medium :size :paragraph-1}
        (i18n/label :t/logout)]])))

after

(defn- view-internal
  [{:keys [on-press on-long-press disabled? theme container-style]}]
  (let [[pressed? set-pressed] (use-state false)
        on-press-in  (use-callback #(set-pressed true))
        on-press-out (use-callback #(set-pressed nil))]
    [rn/pressable
       {:accessibility-label :log-out-button
        :on-press            on-press
        :on-press-in         on-press-in
        :on-press-out        on-press-out
        :on-long-press       on-long-press
        :disabled            disabled?
        :style               (merge (style/main {:pressed?  @pressed?
                                                 :theme     theme
                                                 :disabled? disabled?})
                                    container-style)}
       [icon/icon :i/log-out {:color (if pressed? colors/white-opa-40 colors/white-opa-70)}]
       [text/text {:weight :medium :size :paragraph-1}
        (i18n/label :t/logout)]])))

actually, this is a good example, because it has a mistake, (if pressed? colors/white-opa-40 colors/white-opa-70) here atom is used and not a value @pressed?