Memoize static options in vertical-align* component

Wrap the radio-button options vector in `mf/with-memo []` so the
vector allocation and `(tr ...)` calls happen once per component
mount instead of on every render.

Also document the translation memoization rule in frontend/AGENTS.md:
`(tr ...)` must never be called at namespace level (locale is
runtime-only), and static option lists should always be wrapped in
`mf/with-memo []`.

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
Andrey Antukh 2026-04-21 21:03:47 +00:00 committed by Belén Albeza
parent 11c970a945
commit 6c19c7c0c4
2 changed files with 41 additions and 12 deletions

View File

@ -329,6 +329,31 @@ CSS modules pattern):
- [ ] Selectors are flat (no deep nesting). - [ ] Selectors are flat (no deep nesting).
### Translations (`tr`) and Memoization
`(tr "some.key")` resolves the translation string from the **currently active
locale at call time**. This has two consequences:
- **Never call `(tr ...)` at namespace level** (inside a `def` or `defonce`).
Doing so would freeze the label to the locale active at module load time and
break runtime language switching.
- **Always call `(tr ...)` at render time** — either directly in the component
body or inside a `mf/with-memo` / `mf/use-memo` block.
When a component renders a **static list of options** whose labels come from
`(tr ...)` (e.g. radio button options, select options), wrap the vector in
`mf/with-memo []` with no dependencies. This ensures the vector and its
`(tr ...)` calls are evaluated once per component mount instead of on every
render, while still respecting the render-time requirement:
```clojure
(let [options (mf/with-memo []
[{:value "top" :label (tr "some.key.top")}
{:value "center" :label (tr "some.key.center")}
{:value "bottom" :label (tr "some.key.bottom")}])]
...)
```
### Performance Macros (`app.common.data.macros`) ### Performance Macros (`app.common.data.macros`)
Always prefer these macros over their `clojure.core` equivalents — they compile to faster JavaScript: Always prefer these macros over their `clojure.core` equivalents — they compile to faster JavaScript:

View File

@ -102,6 +102,21 @@
(mf/defc vertical-align* (mf/defc vertical-align*
[{:keys [values on-change on-blur]}] [{:keys [values on-change on-blur]}]
(let [vertical-align (or (:vertical-align values) "top") (let [vertical-align (or (:vertical-align values) "top")
options
(mf/with-memo []
[{:value "top"
:id "vertical-text-align-top"
:label (tr "workspace.options.text-options.align-top")
:icon i/text-top}
{:value "center"
:id "vertical-text-align-center"
:label (tr "workspace.options.text-options.align-middle")
:icon i/text-middle}
{:value "bottom"
:id "vertical-text-align-bottom"
:label (tr "workspace.options.text-options.align-bottom")
:icon i/text-bottom}])
handle-change handle-change
(mf/use-fn (mf/use-fn
(mf/deps on-change on-blur) (mf/deps on-change on-blur)
@ -113,18 +128,7 @@
[:> radio-buttons* {:selected vertical-align [:> radio-buttons* {:selected vertical-align
:on-change handle-change :on-change handle-change
:name "vertical-align-text-options" :name "vertical-align-text-options"
:options [{:value "top" :options options}]]))
:id "vertical-text-align-top"
:label (tr "workspace.options.text-options.align-top")
:icon i/text-top}
{:value "center"
:id "vertical-text-align-center"
:label (tr "workspace.options.text-options.align-middle")
:icon i/text-middle}
{:value "bottom"
:id "vertical-text-align-bottom"
:label (tr "workspace.options.text-options.align-bottom")
:icon i/text-bottom}]}]]))
(mf/defc grow-options* (mf/defc grow-options*
[{:keys [ids values on-blur]}] [{:keys [ids values on-blur]}]