-
Notifications
You must be signed in to change notification settings - Fork 2
General Development Tips
This document contains a grab-bag of tips, gotchas and information that is generally relevant for frontend development. They are not all necessarily specific to development with Reflet.
Avoid dereferencing subscriptions, reactions, or ratoms outside the
render phase. They can be out of sync with React props, which can
introduce subtle concurrency problems if you're not careful. This
includes all the component lifecycles besides :reagent-render
, as
well as any callbacks used in React :ref
s.
Take care with the interactions between :component-did-mount
and
:component-will-unmount
across component lifecycles. There is no way
for mount methods to safely see the state effects of unmount methods
from a previous lifecycle. You can introduce concurrency problems if
the :component-will-unmount
and the :component-did-mount
produce
mutations on the same piece of app state.
The reason why is fairly complicated, and requires a full itinerary of what happens during the React lifecycle of a single component:
-
Reagent constructs the component with an initial props value. Closures are created around the lifecycle methods.
-
React runs an initial render using the initial props value.
-
React computes which components are no longer needed, and their reaction on-dispose functions and
:component-will-unmount
methods are called, in that order. Note that the reaction on-dispose includeswith-let
finally clauses andwith-ref
cleanup methods. -
The callback function passed to React
:ref
s fires for a newly mounted component. However, this function will have closed over the initial props value, and will not see any changes from the unmount methods in step 3. -
Next, the
:component-did-mount
is called, but again with the initial React props. Any effects from the:component-will-unmount
or on-dispose methods that had just been called in step 3 will not be seen by this method. -
Finally the component has a chance to re-render in response to changes from unmount methods, and receive a new set of props. But by this point the
:component-did-mount
lifecycle is long gone, and may have produced side-effects that conflict with those of the unmount methods.
Careful when passing transient refs around outside the component tree in which they were made. In a very real way they are tied to things that are essentially ephemeral. You can end up writing to state after that state has already been cleaned up. Reflet will warn you about this, but it is best avoided. Three good rules of thumb are:
-
Avoid transient refs as joins in persistent data entities
;; Assuming that [:cmp/uuid #uuid "b"] is transient, ;; but [:system/uuid #uuid "a"] is persistent. {::db/data {[:system/uuid #uuid "a"] {:system/uuid #uuid "a" :kr/join [:cmp/uuid #uuid "b"]} ; <- Careful! [:cmp/uuid #uuid "b"] {:cmp/uuid #uuid "b" :cmp/flag true}}}
-
Avoid transient refs as joins in link entries
;; Assuming that [:cmp/uuid #uuid "b"] is transient. {::db/data {:my.link/entry #{[:cmp/uuid #uuid "b"]} ; <- Careful! [:cmp/uuid #uuid "b"] {:cmp/uuid #uuid "b" :cmp/flag true}}}
-
Do not store transient refs using non-graph operations, where they may persist beyond the
with-ref
lifecycle
In each of these three cases, after the entity referenced by
[:cmp/uuid #uuid "b"]
is cleaned up, the other references will
remain (assuming the user does not do additional cleanup). While this
is relatively harmless, these left-over transient references could
then find their way into an entity transaction, and lead to ephemeral
state being stored again after it has been cleaned up.
Careful with where you use references and where you use unique values (like UUIDs):
;; reference
[:cmp/uuid #uuid "bf8cc02d-271d-4779-843c-e3829f800cb6"]
;; unique value
#uuid "bf8cc02d-271d-4779-843c-e3829f800cb6"
In general you should prefer refs over unique values everywhere. But especially in:
- Prop attributes in components
- Join attributes in db entities
;; Correct
[my-component {:my/prop [:cmp/uuid #uuid "bf8cc02d-271d-4779-843c-e3829f800cb6"]}]
;; Correct
{:kr.track/artist [:system/uuid #uuid"3e57d7a2-9148-47b0-81b8-950ed11f74d0"] ...}
;; Wrong
[my-component {:my/prop #uuid "bf8cc02d-271d-4779-843c-e3829f800cb6"}]
;; Wrong
{:kr.track/artist #uuid"3e57d7a2-9148-47b0-81b8-950ed11f74d0" ...}
The only place you should prefer unique values is:
- Unique attributes values in db entities:
;; Correct
{:cmp/uuid #uuid "bf8cc02d-271d-4779-843c-e3829f800cb6" ...}
;; Wrong
{:cmp/uuid [:cmp/uuid #uuid "bf8cc02d-271d-4779-843c-e3829f800cb6"] ...}
Careful not to confuse props attributes with unique attributes, especially when merging.
In components:
;; prop attribute --v v--- unique attributes
[my-component {:my/prop [:cmp/uuid #uuid "bf8cc02d-271d-4779-843c-e3829f800cb6"]}]
In db entities:
;; v-- unique attribute v---- prop attribute
{:cmp/uuid (second (:my/prop props))}
Home: Home