Why do we call clear-subscription-cache!
when reloading code with Figwheel?
Pour yourself a drink, as this is a circuitous tale involving one of the hardest problems in Computer Science.
1: Humble beginnings
When React is rendering, if an exception is thrown, it doesn't catch or
handle the errors gracefully. Instead, all of the React components up to
the root are destroyed. When these components are destroyed, none of their
standard lifecycle methods are called, like ComponentDidUnmount
.
2: Simple assumptions
Reagent tracks the watchers of a Reaction to know when no-one is watching and
it can call the Reaction's on-dispose
. Part of the book-keeping involved in
this requires running the on-dispose
in a React ComponentWillUnmount
lifecycle
method.
At this point, your spidey senses are probably tingling.
3: The hardest problem in CS
re-frame subscriptions are created as Reactions. re-frame helpfully deduplicates
subscriptions if multiple parts of the view request the same subscription. This
is a big efficiency boost. When re-frame creates the subscription Reaction, it
sets the on-dispose
method of that subscription to remove itself from the
subscription cache. This means that when that subscription isn't being watched
by any part of the view, it can be disposed.
4: The gnarly implications
If you are
- Writing a re-frame app
- Write a bug in your subscription code (your one bug for the year)
- Which causes an exception to be thrown in your rendering code
then:
- React will destroy all of the components in your view without calling
ComponentWillUnmount
. - Reagent will not get notified that some subscriptions are not needed anymore.
- The subscription on-dispose functions that should have been run, are not.
- re-frame's subscription cache will not be invalidated correctly, and the subscription with the bug is still in the cache.
At this point you are looking at a blank screen. After debugging, you find the problem and fix it. You save your code and Figwheel recompiles and reloads the changed code. Figwheel attempts to re-render from the root. This causes all of the Reagent views to be rendered and to request re-frame subscriptions if they need them. Because the old buggy subscription is still sitting around in the cache, re-frame will return that subscription instead of creating a new one based on the fixed code. The only way around this (once you realise what is going on) is to reload the page.
5: Coda
re-frame 0.9.0 provides a new function: re-frame.core/clear-subscription-cache!
which will run the
on-dispose function for every subscription in the cache, emptying the cache, and causing new subscriptions
to be created after reloading.
Up: FAQ Index