gaearon / gaearon/react-hot-loader
đ track "principles" compliance
- Dominant language
- JavaScript
- Stars
- 12.2k
- Forks
- 775
- PR merge metrics
- No merged PRs in 30d
Description
https://overreacted.io/my-wishlist-for-hot-reloading/
## Correctness
- [x] (`partialy`) __Hot reloading should be unobservable before the first edit__. Until you save a file, the code should behave exactly as it would if hot reloading was disabled. Itâs expected that things like fn.toString() donât match, which is already the case with minification. But it shouldnât break reasonable application and library logic.
Hot reload shouldnât break React rules. Components shouldnât get their lifecycles called in an unexpected way, accidentally swap state between unrelated trees, or do other non-Reacty things.
- [x] (`partialy`)__Element type should always match the expected type__. Some approaches wrap component types but this can break .type === MyThing. This is a common source of bugs and should not happen.
- [x] __It should be easy to support all React types__. lazy, memo, forwardRef â they should all be supported and it shouldnât be hard to add support for more. Nested variations like memo(memo(...)) should also work. We should always remount when the type shape changes.
It shouldnât reimplement a non-trivial chunk of React. Itâs hard to keep up with React. If a solution reimplements React it poses problems in longer term as React adds features like Suspense.
- [x] __Re-exports shouldnât break__. If a component re-exports components from other modules (whether own or from node_modules), that shouldnât cause issues.
Static fields shouldnât break. If you define a ProfilePage.onEnter method, youâd expect an importing module to be able to read it. Sometimes libraries rely on this so itâs important that itâs possible to read and write static properties, and for component itself to âseeâ the same values on itself.
- [ ] __It is better to lose local state than to behave incorrectly__. If we canât reliably patch something (for example, a class), it is better to lose its local state than to do a mixed success effort at updating it. The developer will be suspicious anyway and likely force a refresh. We should be intentional about which cases weâre confident we can handle, and discard the rest.
It is better to lose local state than use an old version. This is a more specific variation of the previous principle. For example, if a class couldnât be hot reloaded, the code should force a remount for those components with the new version rather than keep rendering a zombie.
## Locality
- [x] (`partialy`)__Editing a module should re-execute as few modules as possible__. Side effects during component module initialization are generally discouraged. But the more code you execute, the more likely something will cause a mess when called twice. Weâre writing JavaScript, and React components are islands of (relative) purity but even there we donât have strong guarantees. So if I edit a module, my hot reloading solution should re-execute that module and try to stop there if possible.
- [x] __Editing a component shouldnât destroy the state of its parents or siblings__. Similar to how setState() only affects the tree below, editing a component shouldnât affect anything above it.
- [x] __Edits to non-React code should propagate upwards__. If you edit a file with constants or pure functions thatâs imported from several components, those components should update. It is acceptable to lose module state in such files.
- [x] __A runtime error introduced during hot reloading should not propagate__. If you make a mistake in one component, it shouldnât break your whole app. In React, this is usually solved by error boundaries. However, they are too coarse for the countless typos we make while editing. I should be able to make and fix runtime errors while I work on a component without its siblings or parents unmounting. However, errors that donât happen during hot reload (and are legitimate bugs in my code) should go to the closest error boundary.
- [ ] __Preserve own state unless itâs clear the developer doesnât want to__. If youâre just tweaking styles, itâs frustrating for the state to reset on every edit. On the other hand, if you just changed the state shape or the initial state, youâll often prefer it to reset. By default we should try our best to preserve state. But if it leads to an error during hot reload, this is often a sign some assumption has changed, so we should should reset state and retry rendering in that case. Commenting things out and back in is common so itâs important to handle that gracefully. For example, removing Hooks at the end shouldnât reset state.
- [ ] __Discard state when itâs clear the developer wants to__. In some cases we can also proactively detect that the user wants to reset. For example, if the Hook order changed, or if primitive Hooks like useState change their initial state type. We can also offer a lightweight annotation that you can use to force a component to reset on every edit. Such as // ! or some similar convention thatâs fast to add and remove while you focus on how component mounts.
Support updating âfixedâ things. If a component is wrapped in memo(), hot reload should still update it. If an effect is called with [], it should still be replaced. Code is like an invisible variable. Previously, I thought it was important to force deep updates below for things like renderRow={this.renderRow}. But in the Hooks world, we rely on closures anyway this seems unnecessary anymore. A different reference should be sufficient.
- [x] __Support multiple components in one file__. It is a common pattern that multiple components are defined in the same file. Even if we only keep the state for function components, we want to make sure putting them in one file doesnât cause them to lose state. Note these can be mutually recursive.
- [x] __When possible, preserve the state of children__. If you edit a component, itâs always frustrating if its children unintentionally lose state. As long as the element types of children are defined in other files, we expect their state to be preserved. If theyâre in the same file, we should do our best effort.
- [x] __Support custom Hooks__. For well-written custom Hooks (some cases like useInterval() can be a bit tricky), hot reloading any arguments (including functions) should work. This shouldnât need extra work and follows from the design of Hooks. Our solution just shouldnât get in the way.
- [x] __Support render props__. This usually doesnât pose problems but itâs worth verifying they work and get updated as expected.
- [x] __Support higher-order components__. Wrapping export into a higher-order component like connect shouldnât break hot reloading or state preservation. If you use a component created from a HOC in JSX (such as styled), and that component is a class, itâs expected that it loses state when instantiated in the edited file. But A HOC that returns a function component (potentially using Hooks) shouldnât shouldnât lose state even if itâs defined in the same file. In fact, even edits to its arguments (e.g. mapStateToProps) should be reflected.
## Feedback
- [x] __Both success and failure should provide visual feedback__. You should always be confident whether a hot reload succeeded or failed. In case of a runtime or a syntax error you should see an overlay which should be automatically be dismissed after it is irrelevant. When hot reload is successful, there should be some visual feedback such as flashing updated components or a notification.
- [x] __A syntax error shouldnât cause a runtime error or a refresh__. When you edit the code and you have a syntax error, it should be shown in a modal overlay (ideally, with a click-through to the editor). If you make another syntax error, the existing overlay is updated. Hot reloading is only attempted after you fix your syntax errors. Syntax error shouldnât make you lose the state.
A syntax error after reload should still be visible. If you see a modal syntax error overlay and refresh, you should still be seeing it. It categorically should not let you run the last successful version (Iâve seen that in some setups).
- [ ] __Consider exposing power user tools__. With hot reloading, code itself can be your âterminalâ. In addition to the hypothetical // ! command to force remount, there could be e.g. an // inspect command that shows a panel with props values next to the component. Be creative!
- [x] (`partialy`)__Minimize the noise__. DevTools and warning messages shouldnât expose that weâre doing something special. Avoid breaking displayNames or adding useless wrappers to the debug output.
- [x] __Debugging in major browsers should show the most recent code__. While this doesnât exactly depend on us, we should do our best to ensure the browser debugger shows the most recent version of any file and that breakpoints work as expected.
- [ ] __Optimize for fast iteration, not long refactoring__. This is JavaScript, not Elm. Any long-running series of edits likely wonât hot reload well due to a bunch of mistakes that need to be fixed one by one. When in doubt, optimize for the use case of tweaking a few components in a tight iteration loop rather than for a big refactor. And be predictable. Keep in mind that if you lose developerâs trust theyâll refresh anyway.
Contributor guide
Assessment
This issue has not been assessed yet.