|
| 1 | +--- |
| 2 | +title: Hot Module Replacement |
| 3 | +--- |
| 4 | + |
| 5 | +# Hot Module Replacement |
| 6 | + |
| 7 | +Hot Module Replacement is a technique for updating modules in your app without needing to reload the page. |
| 8 | +It's a great developer experience, and React Router supports it when using Vite. |
| 9 | + |
| 10 | +HMR does its best to preserve browser state across updates. |
| 11 | +For example, let's say you have form within a modal and you fill out all the fields. |
| 12 | +As soon as you save any changes to the code, traditional live reload would hard refresh the page causing all of those fields to be reset. |
| 13 | +Every time you make a change, you'd have to open up the modal _again_ and fill out the form _again_. |
| 14 | + |
| 15 | +But with HMR, all of that state is preserved _across updates_. |
| 16 | + |
| 17 | +## React Fast Refresh |
| 18 | + |
| 19 | +React already has mechanisms for updating the DOM via its [virtual DOM][virtual-dom] in response to user interactions like clicking a button. |
| 20 | +Wouldn't it be great if React could handle updating the DOM in response to code changes too? |
| 21 | + |
| 22 | +That's exactly what [React Fast Refresh][react-refresh] is all about! |
| 23 | +Of course, React is all about components, not general JavaScript code, so React Fast Refresh only handles hot updates for exported React components. |
| 24 | + |
| 25 | +But React Fast Refresh does have some limitations that you should be aware of. |
| 26 | + |
| 27 | +### Class Component State |
| 28 | + |
| 29 | +React Fast Refresh does not preserve state for class components. |
| 30 | +This includes higher-order components that internally return classes: |
| 31 | + |
| 32 | +```tsx |
| 33 | +export class ComponentA extends Component {} // ❌ |
| 34 | + |
| 35 | +export const ComponentB = HOC(ComponentC); // ❌ Won't work if HOC returns a class component |
| 36 | + |
| 37 | +export function ComponentD() {} // ✅ |
| 38 | +export const ComponentE = () => {}; // ✅ |
| 39 | +export default function ComponentF() {} // ✅ |
| 40 | +``` |
| 41 | + |
| 42 | +### Named Function Components |
| 43 | + |
| 44 | +Function components must be named, not anonymous, for React Fast Refresh to track changes: |
| 45 | + |
| 46 | +```tsx |
| 47 | +export default () => {}; // ❌ |
| 48 | +export default function () {} // ❌ |
| 49 | + |
| 50 | +const ComponentA = () => {}; |
| 51 | +export default ComponentA; // ✅ |
| 52 | + |
| 53 | +export default function ComponentB() {} // ✅ |
| 54 | +``` |
| 55 | + |
| 56 | +### Supported Exports |
| 57 | + |
| 58 | +React Fast Refresh can only handle component exports. While React Router manages [route exports like `action`, ` headers`, `links`, `loader`, and `meta`][route-module] for you, any user-defined exports will cause full reloads: |
| 59 | + |
| 60 | +```tsx |
| 61 | +// These exports are handled by the React Router Vite plugin |
| 62 | +// to be HMR-compatible |
| 63 | +export const meta = { title: "Home" }; // ✅ |
| 64 | +export const links = [ |
| 65 | + { rel: "stylesheet", href: "style.css" }, |
| 66 | +]; // ✅ |
| 67 | + |
| 68 | +// These exports are removed by the React Router Vite plugin |
| 69 | +// so they never affect HMR |
| 70 | +export const headers = { "Cache-Control": "max-age=3600" }; // ✅ |
| 71 | +export const loader = async () => {}; // ✅ |
| 72 | +export const action = async () => {}; // ✅ |
| 73 | + |
| 74 | +// This is not a route module export, nor a component export, |
| 75 | +// so it will cause a full reload for this route |
| 76 | +export const myValue = "some value"; // ❌ |
| 77 | + |
| 78 | +export default function Route() {} // ✅ |
| 79 | +``` |
| 80 | + |
| 81 | +👆 Routes probably shouldn't be exporting random values like that anyway. |
| 82 | +If you want to reuse values across routes, stick them in their own non-route module: |
| 83 | + |
| 84 | +```ts filename=my-custom-value.ts |
| 85 | +export const myValue = "some value"; |
| 86 | +``` |
| 87 | + |
| 88 | +### Changing Hooks |
| 89 | + |
| 90 | +React Fast Refresh cannot track changes for a component when hooks are being added or removed from it, causing full reloads just for the next render. After the hooks have been updated, changes should result in hot updates again. For example, if you add a `useState` to your component, you may lose that component's local state for the next render. |
| 91 | + |
| 92 | +Additionally, if you are destructuring a hook's return value, React Fast Refresh will not be able to preserve state for the component if the destructured key is removed or renamed. |
| 93 | +For example: |
| 94 | + |
| 95 | +```tsx |
| 96 | +export default function Component({ loaderData }) { |
| 97 | + const { pet } = useMyCustomHook(); |
| 98 | + return ( |
| 99 | + <div> |
| 100 | + <input /> |
| 101 | + <p>My dog's name is {pet.name}!</p> |
| 102 | + </div> |
| 103 | + ); |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +If you change the key `pet` to `dog`: |
| 108 | + |
| 109 | +```diff |
| 110 | + export default function Component() { |
| 111 | +- const { pet } = useMyCustomHook(); |
| 112 | ++ const { dog } = useMyCustomHook(); |
| 113 | + return ( |
| 114 | + <div> |
| 115 | + <input /> |
| 116 | +- <p>My dog's name is {pet.name}!</p> |
| 117 | ++ <p>My dog's name is {dog.name}!</p> |
| 118 | + </div> |
| 119 | + ); |
| 120 | + } |
| 121 | +``` |
| 122 | + |
| 123 | +then React Fast Refresh will not be able to preserve state `<input />` ❌. |
| 124 | + |
| 125 | +### Component Keys |
| 126 | + |
| 127 | +In some cases, React cannot distinguish between existing components being changed and new components being added. [React needs `key`s][react-keys] to disambiguate these cases and track changes when sibling elements are modified. |
| 128 | + |
| 129 | +[virtual-dom]: https://reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom |
| 130 | +[react-refresh]: https://github.com/facebook/react/tree/main/packages/react-refresh |
| 131 | +[react-keys]: https://react.dev/learn/rendering-lists#why-does-react-need-keys |
| 132 | +[route-module]: ../start/framework/route-module |
0 commit comments