( React: typeof window.React, createController: (host: ReactiveControllerHost) => C )
| 112 | * create function is only called once per component. |
| 113 | */ |
| 114 | export const useController = <C extends ReactiveController>( |
| 115 | React: typeof window.React, |
| 116 | createController: (host: ReactiveControllerHost) => C |
| 117 | ): C => { |
| 118 | const {useState, useLayoutEffect} = React; |
| 119 | |
| 120 | // State to force updates of the React component |
| 121 | const [kickCount, kick] = useState(0); |
| 122 | |
| 123 | // Create and store the controller instance. We use useState() instead of |
| 124 | // useMemo() because React does not guarantee that it will preserve values |
| 125 | // created with useMemo(). |
| 126 | // TODO (justinfagnani): since this controller are mutable, this may cause |
| 127 | // issues such as "shearing" with React concurrent mode. The solution there |
| 128 | // will likely be to snapshot the controller state with something like |
| 129 | // `useMutableSource`: |
| 130 | // https://github.com/reactjs/rfcs/blob/master/text/0147-use-mutable-source.md |
| 131 | // We can address this when React's concurrent mode is closer to shipping. |
| 132 | |
| 133 | let shouldDisconnect = false; |
| 134 | const [host] = useState(() => { |
| 135 | const host = new ReactControllerHost<C>(kickCount, kick); |
| 136 | const controller = createController(host); |
| 137 | host._primaryController = controller; |
| 138 | // Note, calls to `useState` are expected to produce no side effects and in |
| 139 | // StrictMode this is enforced by not running effects for the first render. |
| 140 | // |
| 141 | // This happens in StrictMode: |
| 142 | // 1. Throw away render: component function runs but does not call effects |
| 143 | // 2. Real render: component function runs and *does* call effects, |
| 144 | // 2.a. if first render, run effects and |
| 145 | // 2.a.1 mount, |
| 146 | // 2.a.2 unmount, |
| 147 | // 2.a.3 remount |
| 148 | // 2b. if not first render, just run effects |
| 149 | // |
| 150 | // To preserve update lifecycle ordering and run it before this hook |
| 151 | // returns, run connected here but schedule and async disconnect (handles |
| 152 | // lifecycle balance for `(1) Throw away render`). |
| 153 | // The disconnect is cancelled if the effects actually run (handles |
| 154 | // `(2.a.1) Real render, mount`). |
| 155 | host._connected(); |
| 156 | shouldDisconnect = true; |
| 157 | microtask.then(() => { |
| 158 | if (shouldDisconnect) { |
| 159 | host._disconnected(); |
| 160 | } |
| 161 | }); |
| 162 | return host; |
| 163 | }); |
| 164 | |
| 165 | host._updatePending = true; |
| 166 | |
| 167 | // This effect runs only on mount/unmount of the component (via the empty |
| 168 | // deps array). If the controller has just been created, it's scheduled |
| 169 | // a disconnect so that it behaves correctly in StrictMode (see above). |
| 170 | // The returned callback here disconnects the host when the component is |
| 171 | // unmounted (handles `(2.a.2) Real render, unmount` above). |
no test coverage detected
searching dependent graphs…