From b0133e97cf38762249c6ec18802577ebb737958e Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Fri, 23 Feb 2024 18:30:46 +0100 Subject: [PATCH] UI components coding guidelines (#18926) * UI components coding guidelines --- doc/README.md | 2 + doc/react_tree.png | Bin 0 -> 42225 bytes doc/ui-guidelines.md | 254 ++++++++++++++++++ src/legacy/status_im/bottom_sheet/sheets.cljs | 8 +- .../components/colors/color_picker/view.cljs | 2 +- .../drawers/drawer_buttons/view.cljs | 6 +- .../record_audio/record_audio/view.cljs | 30 +-- src/react_native/core.cljs | 55 ++-- .../common/bottom_sheet_screen/view.cljs | 2 +- src/status_im/common/lightbox/utils.cljs | 2 +- .../common/lightbox/zoomable_image/view.cljs | 4 +- src/status_im/common/scan_qr_code/view.cljs | 2 +- .../chat/messenger/composer/effects.cljs | 2 +- .../messages/content/audio/view.cljs | 2 +- .../onboarding/common/overlay/view.cljs | 2 +- .../contexts/onboarding/identifiers/view.cljs | 5 +- .../contexts/profile/profiles/view.cljs | 12 +- .../contexts/shell/jump_to/view.cljs | 5 +- .../contexts/syncing/scan_sync_code/view.cljs | 5 +- .../contexts/wallet/collectible/view.cljs | 5 +- .../wallet/send/input_amount/view.cljs | 2 +- 21 files changed, 339 insertions(+), 68 deletions(-) create mode 100644 doc/react_tree.png create mode 100644 doc/ui-guidelines.md diff --git a/doc/README.md b/doc/README.md index 637ce86026..301629677b 100644 --- a/doc/README.md +++ b/doc/README.md @@ -10,6 +10,8 @@ [Coding guidelines](new-guidelines.md) +[UI components coding guidelines](ui-guidelines.md) + [Release Checklist](release-checklist.md) [Release Guide](release-guide.md) diff --git a/doc/react_tree.png b/doc/react_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ade680dd49f1ae61daa0362fb1bf0587060b04 GIT binary patch literal 42225 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 literal 0 HcmV?d00001 diff --git a/doc/ui-guidelines.md b/doc/ui-guidelines.md new file mode 100644 index 0000000000..55aaf4f861 --- /dev/null +++ b/doc/ui-guidelines.md @@ -0,0 +1,254 @@ +# UI components coding guidelines + +> [!IMPORTANT] +> React apps are made out of components. A component is a piece of the UI (user interface) that has its own logic and appearance. A component can be as small as a button, or as large as an entire screen. +> React components are JavaScript functions that return markup +> This document will provide best practices on how to write efficient React components in ClojureScript + + +At the time of creating the Status app, the Reagent library was a solid choice. Back then, hooks didn't exist, and there weren't any libraries providing effective global state management. After Reagent's emergence, another library called Re-frame built upon Reagent. Together, they offered powerful tools for developing React applications with ClojureScript. However, as React evolved, significant changes occurred. Class components, utilized in Reagent, became deprecated. Instead, functional components and hooks emerged for state management. In Status 2.0, we began incorporating more functional components and hooks, resulting in a blend of both approaches. To simplify matters and reduce confusion, we opted to transition to functional components and hooks for local state management. + +BEFORE: +```clojure +(defn- view-internal + [_ _] + (let [pressed? (reagent/atom false)] + (fn + [{:keys [theme on-press on-long-press icon]}] + [rn/pressable + {:style (style/main @pressed? theme) + :on-press on-press + :on-press-in #(reset! pressed? true) + :on-press-out #(reset! pressed? nil) + :on-long-press on-long-press} + [quo.icons/icon icon]]))) + +(def view (theme/with-theme view-internal)) +``` + +NOW: +```clojure +(defn view + [{:keys [on-press on-long-press icon]}] + (let [[pressed? set-pressed] (rn/use-state false) + theme (theme/use-theme-value) + on-press-in (rn/use-callback #(set-pressed true)) + on-press-out (rn/use-callback #(set-pressed nil))] + [rn/pressable + {:style (style/main pressed? theme) + :on-press on-press + :on-press-in on-press-in + :on-press-out on-press-out + :on-long-press on-long-press} + [quo.icons/icon icon]])) +``` + + +- We no longer need to create an anonymous function for rendering. This removes unnecessary confusion and the need for specific knowledge on how it works and why it was needed. +- `rn/use-state` is used instead of `reagent/atom` +- State values no longer need to be dereferenced; they are accessible as regular symbols. This eliminates a common bug where the "@" symbol was inadvertently omitted. +- `theme/with-theme` wrapper is not needed anymore, `(theme/use-theme-value)` hook can be used directly in the components +- `:f>` not needed anymore, all components are functional by default +- `rn/use-callback` hook should be used for anon callback functions + +> [!IMPORTANT] +> DO NOT USE anon functions directly in the props + +BAD +```clojure +(defn view + [] + (let [[pressed? set-pressed] (rn/use-state false)] + [rn/pressable + {:style (style/main pressed?) + :on-press-in #(set-pressed true) + :on-press-out #(set-pressed nil)}])) +``` + +GOOD: +```clojure +(defn view + [] + (let [[pressed? set-pressed] (rn/use-state false) + on-press-in (rn/use-callback #(set-pressed true)) + on-press-out (rn/use-callback #(set-pressed nil))] + [rn/pressable + {:style (style/main pressed?) + :on-press-in on-press-in + :on-press-out on-press-out}])) +``` + +## Global state and subscriptions + +For global state management, we utilize Re-frame subscriptions. They can be likened to React state. To obtain the state, `(rf/sub [])` is employed, and to modify it, `(rf/dispatch [])` is utilized. However, they update components in a similar manner to React states. + +```clojure +(defn view + [{:keys [selected-tab]}] + (let [collectible-list (rf/sub [:wallet/all-collectibles]) + on-collectible-press (rn/use-callback + (fn [{:keys [id]}] + (rf/dispatch [:wallet/get-collectible-details id])))] + [rn/view {:style style/container} + (case selected-tab + :assets [assets/view] + :collectibles [collectibles/view {:collectibles collectible-list + :on-collectible-press on-collectible-press}]) + [activity/view]])) +``` + +## Regular atoms + +In certain instances, components utilized regular atoms; however, they should now be used with `rn/use-ref-atom` + +BEFORE: +```clojure +(defn view + [] + (let [focused? (atom false)] + (fn [] + (let [on-clear #(reset! status (if @focused? :active :default)) + on-focus #(reset! focused? true) + on-blur #(reset! focused? false)])))) +``` + +NOW: +```clojure +(defn view + [] + (let [focused? (rn/use-ref-atom false) + on-clear (rn/use-callback #(set-status (if @focused? :active :default))) + on-focus (rn/use-callback #(reset! focused? true)) + on-blur (rn/use-callback #(reset! focused? false))])) +``` + +## Effects + +LIFECYCLE: + +```clojure +(defn view + [{:keys []}] + (let [opacity (reanimated/use-shared-value 0)] + (rn/use-mount #(reanimated/animate opacity 1)) + [rn/view + {:style (style/opacity opacity)}])) +``` + +```clojure +(defn view + [{:keys []}] + (let [] + (rn/use-unmount #(rn/dispatch [:unmounted])) + [rn/view])) +``` + +> [!IMPORTANT] +> Effects should NOT be utilized as a response to state changes for modifying logic. If you're unsure how to achieve this without using effects, please consult the team in the general chat. There may be instances where using effects is appropriate, so we can explore a solution together and enhance our guidelines. + +BAD: +```clojure +(defn f-zoom-button + [{:keys [selected? current-zoom]}] + (let [size (reanimated/use-shared-value (if selected? 37 25))] + (rn/use-effect #(reanimated/animate size (if selected? 37 25)) [current-zoom]) + [rn/touchable-opacity + {:style (style/zoom-button-container size)}])) +``` + +BAD: + +```clojure +(defn view + [collectible-list (rf/sub [:wallet/all-collectibles])] + (let [] + (rn/use-effect #(rn/dispatch [:all-collectibles-changed]) [collectible-list]) + [rn/view])) +``` + +Instead `:all-collectibles-changed` should be used in the handler which changes `collectible-list` state + + + +## Performance tips + +To begin with, we need to understand that there are two distinct stages for a component: creation and update. React creates a render tree, a UI tree, composed of the rendered components. + +![react_tree.png](react_tree.png) + +### Component creation + +For component creation, the most critical factor is the number of elements involved, so we should strive to minimize them. For instance, it's advisable to avoid using unnecessary wrappers or containers. + +BAD: + +```clojure +(defn view + [] + (let [] + [rn/view {:style {:padding-top 20}} + [quo/button]])) +``` + +GOOD: +```clojure +(defn view + [] + (let [] + [quo/button {:container-style {:padding-top 20}}])) +``` + +### Component updates + +For component updates, it's crucial to recognize that React will invoke the function where state is utilized. Therefore, if you utilize state in the root component, React will execute the root function and re-render the entire root component along with all its children (unless optimizations like memoization are implemented). + +BAD: + +```clojure +(defn component + [{:keys [label]}] + (let [] + [rn/text label])) + +(defn component2 + [{:keys [label2]}] + (let [] + [rn/text label2])) + +(defn screen + [] + (let [screen-params (rf/sub [:screen-params])] + [component screen-params] + [component1] + [component2 screen-params] + [component3] + [rn/view {:padding-top 20} + [quo/button]])) +``` + +Here, we have lost control over the `screen-params` map. It can contain any data, and if any field within this map changes, the entire screen function will be executed, resulting in the re-rendering of both `component` and `component2`. + +GOOD: +```clojure +(defn component + [] + (let [label (rf/sub [:screen-params-label])] + [rn/text label])) + +(defn component2 + [] + (let [label2 (rf/sub [:screen-params-label2])] + [rn/text label2])) + +(defn screen + [] + (let [] + [component] + [component1] + [component2] + [component3] + [rn/view {:padding-top 20} + [quo/button]])) +``` + +So, now the screen component function will never be invoked, and `component` and `component2` will be re-rendered only when `label` or `label2` have changed. diff --git a/src/legacy/status_im/bottom_sheet/sheets.cljs b/src/legacy/status_im/bottom_sheet/sheets.cljs index 1f531fb525..537d4117b0 100644 --- a/src/legacy/status_im/bottom_sheet/sheets.cljs +++ b/src/legacy/status_im/bottom_sheet/sheets.cljs @@ -28,10 +28,10 @@ [:f> (fn [] - (rn/use-effect (fn [] - (rn/hw-back-add-listener dismiss-bottom-sheet-callback) - (fn [] - (rn/hw-back-remove-listener dismiss-bottom-sheet-callback)))) + (rn/use-mount (fn [] + (rn/hw-back-add-listener dismiss-bottom-sheet-callback) + (fn [] + (rn/hw-back-remove-listener dismiss-bottom-sheet-callback)))) [theme/provider {:theme (or page-theme (theme/get-theme))} [bottom-sheet/bottom-sheet opts (when content diff --git a/src/quo/components/colors/color_picker/view.cljs b/src/quo/components/colors/color_picker/view.cljs index 14579fc044..84c9445f03 100644 --- a/src/quo/components/colors/color_picker/view.cljs +++ b/src/quo/components/colors/color_picker/view.cljs @@ -27,7 +27,7 @@ (let [selected (reagent/atom default-selected) {window-width :width} (rn/get-window) ref (atom nil)] - (rn/use-effect + (rn/use-mount (fn [] (js/setTimeout (fn [] diff --git a/src/quo/components/drawers/drawer_buttons/view.cljs b/src/quo/components/drawers/drawer_buttons/view.cljs index f5fc930117..c496bf21df 100644 --- a/src/quo/components/drawers/drawer_buttons/view.cljs +++ b/src/quo/components/drawers/drawer_buttons/view.cljs @@ -177,9 +177,9 @@ 1 animations-duration :easing4))] - (rn/use-effect (fn [] - (when on-init - (on-init reset-top-animation)))) + (rn/use-mount (fn [] + (when on-init + (on-init reset-top-animation)))) [reanimated/view {:style (style/outer-container height border-radius container-style)} [blur/view {:blur-type :dark diff --git a/src/quo/components/record_audio/record_audio/view.cljs b/src/quo/components/record_audio/record_audio/view.cljs index e7cff3b571..53371b6242 100644 --- a/src/quo/components/record_audio/record_audio/view.cljs +++ b/src/quo/components/record_audio/record_audio/view.cljs @@ -15,7 +15,7 @@ [quo.foundations.colors :as colors] [quo.theme :as quo.theme] [react-native.audio-toolkit :as audio] - [react-native.core :as rn :refer [use-effect]] + [react-native.core :as rn] [react-native.platform :as platform] [reagent.core :as reagent] [taoensso.timbre :as log] @@ -528,20 +528,20 @@ (reset! reached-max-duration? false)) (reset! touch-timestamp nil))] (fn [] - (use-effect (fn [] - (when on-check-audio-permissions - (on-check-audio-permissions)) - (when on-init - (on-init reset-recorder)) - (when audio-file - (let [filename (last (string/split audio-file "/"))] - (reload-player filename))) - (reset! app-state-listener - (.addEventListener rn/app-state - "change" - #(when (= % "background") - (reset! playing-audio? false)))) - #(.remove @app-state-listener))) + (rn/use-mount (fn [] + (when on-check-audio-permissions + (on-check-audio-permissions)) + (when on-init + (on-init reset-recorder)) + (when audio-file + (let [filename (last (string/split audio-file "/"))] + (reload-player filename))) + (reset! app-state-listener + (.addEventListener rn/app-state + "change" + #(when (= % "background") + (reset! playing-audio? false)))) + #(.remove @app-state-listener))) [rn/view {:style style/bar-container :pointer-events :box-none} diff --git a/src/react_native/core.cljs b/src/react_native/core.cljs index 6b316189ad..4f62cf6658 100644 --- a/src/react_native/core.cljs +++ b/src/react_native/core.cljs @@ -2,7 +2,6 @@ (:require ["react" :as react] ["react-native" :as react-native] - [cljs-bean.core :as bean] [oops.core :as oops] [react-native.flat-list :as flat-list] [react-native.platform :as platform] @@ -135,26 +134,50 @@ (def use-context react/useContext) +(defn use-ref-atom + [value] + (let [ref (use-ref (atom value))] + (.-current ^js ref))) + +(defn get-js-deps + [deps] + (if deps + (let [prev-state (use-ref-atom {:value false :deps nil}) + prev-deps (:deps @prev-state) + prev-value (:value @prev-state)] + (if (and (not (nil? prev-deps)) (not= (count deps) (count prev-deps))) + (throw (js/Error. "Hooks can't have a different number of dependencies across re-renders")) + (if (not= deps prev-deps) + (let [new-value (not prev-value)] + (reset! prev-state {:value new-value + :deps deps}) + #js [new-value]) + #js [prev-value]))) + js/undefined)) + (defn use-effect - ([effect-fn] - (use-effect effect-fn [])) - ([effect-fn deps] + {:deprecated + "use-mount or use-unmount should be used, more here https://github.com/status-im/status-mobile/blob/develop/doc/ui-guidelines.md#effects"} + ([handler] + (use-effect handler nil)) + ([handler deps] (react/useEffect - #(let [ret (effect-fn)] - (if (fn? ret) ret js/undefined)) - (bean/->js deps)))) + #(let [ret (handler)] (if (fn? ret) ret js/undefined)) + (get-js-deps deps)))) -(def use-callback react/useCallback) - -(defn use-effect-once - [effect-fn] - (use-effect effect-fn)) +(defn use-mount + [handler] + (use-effect handler [])) (defn use-unmount - [f] - (let [fn-ref (use-ref f)] - (oops/oset! fn-ref "current" f) - (use-effect-once (fn [] (fn [] (oops/ocall! fn-ref "current")))))) + [handler] + (use-mount (fn [] handler))) + +(defn use-callback + ([handler] + (use-callback handler [])) + ([handler deps] + (react/useCallback handler (get-js-deps deps)))) (def layout-animation (.-LayoutAnimation ^js react-native)) (def configure-next (.-configureNext ^js layout-animation)) diff --git a/src/status_im/common/bottom_sheet_screen/view.cljs b/src/status_im/common/bottom_sheet_screen/view.cljs index e9783b456e..40a65565cf 100644 --- a/src/status_im/common/bottom_sheet_screen/view.cljs +++ b/src/status_im/common/bottom_sheet_screen/view.cljs @@ -64,7 +64,7 @@ (reanimated/animate opacity 1 300) (set-animating-false 300) (reset! scroll-enabled? true))] - (rn/use-effect + (rn/use-mount (fn [] (rn/hw-back-add-listener close) (reanimated/animate translate-y 0 300) diff --git a/src/status_im/common/lightbox/utils.cljs b/src/status_im/common/lightbox/utils.cljs index 2f6e71b711..b1d34ab575 100644 --- a/src/status_im/common/lightbox/utils.cljs +++ b/src/status_im/common/lightbox/utils.cljs @@ -27,7 +27,7 @@ (defn effect [{:keys [flat-list-ref scroll-index-lock? timers]} {:keys [opacity layout border]} index] - (rn/use-effect + (rn/use-mount (fn [] (reagent/next-tick (fn [] (when @flat-list-ref diff --git a/src/status_im/common/lightbox/zoomable_image/view.cljs b/src/status_im/common/lightbox/zoomable_image/view.cljs index a82e4f015c..7f777114c7 100644 --- a/src/status_im/common/lightbox/zoomable_image/view.cljs +++ b/src/status_im/common/lightbox/zoomable_image/view.cljs @@ -259,8 +259,8 @@ dimensions animations state))] - (rn/use-effect (fn [] - (js/setTimeout (fn [] (reset! set-full-height? true)) 500))) + (rn/use-mount (fn [] + (js/setTimeout (fn [] (reset! set-full-height? true)) 500))) (when platform/ios? (utils/handle-orientation-change curr-orientation focused? dimensions animations state) (utils/handle-exit-lightbox-signal exit-lightbox-signal diff --git a/src/status_im/common/scan_qr_code/view.cljs b/src/status_im/common/scan_qr_code/view.cljs index 5ee2734c23..80a72e80c9 100644 --- a/src/status_im/common/scan_qr_code/view.cljs +++ b/src/status_im/common/scan_qr_code/view.cljs @@ -202,7 +202,7 @@ (boolean (not-empty @qr-view-finder))) camera-ready-to-scan? (and show-camera? (not @qr-code-succeed?))] - (rn/use-effect + (rn/use-mount (fn [] (rn/hw-back-add-listener navigate-back-handler) (set-listener-torch-off-on-app-inactive torch?) diff --git a/src/status_im/contexts/chat/messenger/composer/effects.cljs b/src/status_im/contexts/chat/messenger/composer/effects.cljs index 02e0ff2401..cd112f5527 100644 --- a/src/status_im/contexts/chat/messenger/composer/effects.cljs +++ b/src/status_im/contexts/chat/messenger/composer/effects.cljs @@ -222,7 +222,7 @@ (defn did-mount [{:keys [selectable-input-ref input-ref selection-manager]}] - (rn/use-effect + (rn/use-mount (fn [] (when platform/android? (let [selectable-text-input-handle (rn/find-node-handle @selectable-input-ref) diff --git a/src/status_im/contexts/chat/messenger/messages/content/audio/view.cljs b/src/status_im/contexts/chat/messenger/messages/content/audio/view.cljs index c829a929d4..d31dfc92dd 100644 --- a/src/status_im/contexts/chat/messenger/messages/content/audio/view.cljs +++ b/src/status_im/contexts/chat/messenger/messages/content/audio/view.cljs @@ -159,7 +159,7 @@ paused? (= (audio/get-state player) audio/PAUSED) app-state (rf/sub [:app-state]) mediaserver-port (rf/sub [:mediaserver/port])] - (rn/use-effect (fn [] #(destroy-player player-key))) + (rn/use-mount (fn [] #(destroy-player player-key))) (rn/use-effect (fn [] (when (or diff --git a/src/status_im/contexts/onboarding/common/overlay/view.cljs b/src/status_im/contexts/onboarding/common/overlay/view.cljs index 3ae44ead9a..61e0cdfd80 100644 --- a/src/status_im/contexts/onboarding/common/overlay/view.cljs +++ b/src/status_im/contexts/onboarding/common/overlay/view.cljs @@ -55,7 +55,7 @@ (let [opacity (reanimated/use-shared-value (if (zero? @blur-amount) 0 1)) blur-show-fn #(blur-show opacity blur-amount) blur-dismiss-fn #(blur-dismiss opacity blur-amount)] - (rn/use-effect + (rn/use-mount (fn [] (reset! blur-show-fn-atom blur-show-fn) (reset! blur-dismiss-fn-atom blur-dismiss-fn) diff --git a/src/status_im/contexts/onboarding/identifiers/view.cljs b/src/status_im/contexts/onboarding/identifiers/view.cljs index 9c7da07689..e2834cbc49 100644 --- a/src/status_im/contexts/onboarding/identifiers/view.cljs +++ b/src/status_im/contexts/onboarding/identifiers/view.cljs @@ -34,10 +34,7 @@ photo-path (rf/sub [:chats/photo-path public-key]) emoji-string (string/join emoji-hash)] (carousel.animation/use-initialize-animation progress paused? true is-dragging? drag-amount) - (rn/use-effect - (fn [] - (carousel.animation/cleanup-animation progress paused?)) - []) + (rn/use-mount #(carousel.animation/cleanup-animation progress paused?)) [:<> [rn/view {:style style/page-container} [carousel/view diff --git a/src/status_im/contexts/profile/profiles/view.cljs b/src/status_im/contexts/profile/profiles/view.cljs index b6aa3a39fb..16fb76d53f 100644 --- a/src/status_im/contexts/profile/profiles/view.cljs +++ b/src/status_im/contexts/profile/profiles/view.cljs @@ -137,12 +137,12 @@ [{:keys [set-hide-profiles]}] (let [profiles (vals (rf/sub [:profile/profiles-overview])) translate-x (reanimated/use-shared-value @translate-x-atom)] - (rn/use-effect (fn [] - (reset! push-animation-fn-atom #(push-animation translate-x)) - (reset! pop-animation-fn-atom #(pop-animation translate-x)) - (fn [] - (reset! push-animation-fn-atom nil) - (reset! pop-animation-fn-atom nil)))) + (rn/use-mount (fn [] + (reset! push-animation-fn-atom #(push-animation translate-x)) + (reset! pop-animation-fn-atom #(pop-animation translate-x)) + (fn [] + (reset! push-animation-fn-atom nil) + (reset! pop-animation-fn-atom nil)))) [reanimated/view {:style (style/profiles-container translate-x)} [rn/view diff --git a/src/status_im/contexts/shell/jump_to/view.cljs b/src/status_im/contexts/shell/jump_to/view.cljs index b64041ad09..fce1e5e87b 100644 --- a/src/status_im/contexts/shell/jump_to/view.cljs +++ b/src/status_im/contexts/shell/jump_to/view.cljs @@ -33,11 +33,10 @@ (defn f-shell-stack [] (let [shared-values (shared-values/calculate-and-set-shared-values)] - (rn/use-effect + (rn/use-mount (fn [] (rn/hw-back-add-listener navigate-back-handler) - #(rn/hw-back-remove-listener navigate-back-handler)) - []) + #(rn/hw-back-remove-listener navigate-back-handler))) [:<> [jump-to-screen/view] [:f> bottom-tabs/f-bottom-tabs] diff --git a/src/status_im/contexts/syncing/scan_sync_code/view.cljs b/src/status_im/contexts/syncing/scan_sync_code/view.cljs index a6fc5a5c36..19b3f7d4ef 100644 --- a/src/status_im/contexts/syncing/scan_sync_code/view.cljs +++ b/src/status_im/contexts/syncing/scan_sync_code/view.cljs @@ -321,8 +321,7 @@ :subtitle-opacity subtitle-opacity :title-opacity title-opacity})] - (rn/use-effect - #(set-listener-torch-off-on-app-inactive torch?)) + (rn/use-mount #(set-listener-torch-off-on-app-inactive torch?)) (when animated? (rn/use-effect @@ -335,7 +334,7 @@ (animation/animate-title title-opacity) (animation/animate-bottom bottom-view-translate-y)) - (rn/use-effect + (rn/use-mount (fn initialize-component [] (when animated? (animation/animate-content content-opacity) diff --git a/src/status_im/contexts/wallet/collectible/view.cljs b/src/status_im/contexts/wallet/collectible/view.cljs index 44a76f2cfc..aa967222d6 100644 --- a/src/status_im/contexts/wallet/collectible/view.cljs +++ b/src/status_im/contexts/wallet/collectible/view.cljs @@ -116,10 +116,7 @@ {collection-image :image-url collection-name :name} collection-data {collectible-name :name} collectible-data] - (rn/use-effect - (fn [] - #(rf/dispatch [:wallet/clear-last-collectible-details])) - []) + (rn/use-unmount #(rf/dispatch [:wallet/clear-last-collectible-details])) [scroll-page/scroll-page {:navigate-back? true :height 148 diff --git a/src/status_im/contexts/wallet/send/input_amount/view.cljs b/src/status_im/contexts/wallet/send/input_amount/view.cljs index ffcd9e2a8a..b5de6419f2 100644 --- a/src/status_im/contexts/wallet/send/input_amount/view.cljs +++ b/src/status_im/contexts/wallet/send/input_amount/view.cljs @@ -239,7 +239,7 @@ (utils/get-standard-fiat-format fee-in-crypto-formatted currency-symbol fee-in-fiat))] - (rn/use-effect + (rn/use-mount (fn [] (let [dismiss-keyboard-fn #(when (= % "active") (rn/dismiss-keyboard!)) app-keyboard-listener (.addEventListener rn/app-state "change" dismiss-keyboard-fn)]