1- /* eslint-disable react-hooks/exhaustive-deps */
21import { castImmutable , type Draft , enablePatches , freeze , type Immutable , produce } from "immer" ;
3- import { useCallback , useEffect , useRef , useState } from "react" ;
4- import { execCmd , logMessage } from "../Common" ;
2+ import { useCallback , useRef , useState } from "react" ;
3+ import { execCmd , getDispatch , logMessage , runInit , useIsMounted , useRedux , useReInit , useSubscription } from "../Common" ;
54import { getFakeOptionsOnce } from "../fakeOptions" ;
65import { Services } from "../Init" ;
7- import { isReduxDevToolsEnabled , type ReduxDevTools } from "../reduxDevTools" ;
8- import { type Cmd , type Dispatch , type InitFunction , type Message , type Nullable , subscriptionIsFunctionArray } from "../Types" ;
6+ import type { Cmd , Dispatch , InitFunction , Message , Nullable } from "../Types" ;
97import { createCallBase } from "./createCallBase" ;
108import { createDefer } from "./createDefer" ;
119import type { Subscription , UpdateFunction , UpdateFunctionOptions , UpdateMap , UpdateReturnType } from "./Types" ;
@@ -64,39 +62,10 @@ function useElmish<TProps, TModel, TMessage extends Message>({
6462 update,
6563 subscription,
6664} : UseElmishOptions < TProps , TModel , TMessage > ) : [ Immutable < TModel > , Dispatch < TMessage > ] {
67- let running = false ;
68- const buffer : TMessage [ ] = [ ] ;
69-
70- const firstCallRef = useRef ( true ) ;
7165 const [ model , setModel ] = useState < Nullable < Immutable < TModel > > > ( null ) ;
7266 const propsRef = useRef ( props ) ;
73- const isMountedRef = useRef ( true ) ;
74-
75- const devTools = useRef < ReduxDevTools | null > ( null ) ;
76-
77- useEffect ( ( ) => {
78- let reduxUnsubscribe : ( ( ) => void ) | undefined ;
79-
80- if ( Services . enableDevTools === true && isReduxDevToolsEnabled ( window ) ) {
81- // eslint-disable-next-line no-underscore-dangle
82- devTools . current = window . __REDUX_DEVTOOLS_EXTENSION__ . connect ( { name, serialize : { options : true } } ) ;
83-
84- reduxUnsubscribe = devTools . current . subscribe ( ( message ) => {
85- if ( message . type === "DISPATCH" && message . payload . type === "JUMP_TO_ACTION" ) {
86- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
87- setModel ( JSON . parse ( message . state ) as Immutable < TModel > ) ;
88- }
89- } ) ;
90- }
91-
92- isMountedRef . current = true ;
93-
94- return ( ) => {
95- isMountedRef . current = false ;
96-
97- reduxUnsubscribe ?.( ) ;
98- } ;
99- } , [ name ] ) ;
67+ const devToolsRef = useRedux ( name , setModel ) ;
68+ const isMountedRef = useIsMounted ( ) ;
10069
10170 let currentModel = model ;
10271
@@ -105,96 +74,53 @@ function useElmish<TProps, TModel, TMessage extends Message>({
10574 }
10675
10776 const fakeOptions = getFakeOptionsOnce < TModel , TMessage > ( ) ;
108- const dispatch = useCallback (
109- fakeOptions ?. dispatch ??
110- ( ( msg : TMessage ) : void => {
111- if ( running ) {
112- buffer . push ( msg ) ;
11377
78+ // eslint-disable-next-line react-hooks/exhaustive-deps
79+ const dispatch = useCallback (
80+ getDispatch (
81+ handleMessage ,
82+ ( ) => {
83+ if ( ! isMountedRef . current ) {
11484 return ;
11585 }
11686
117- running = true ;
118-
119- let nextMsg : TMessage | undefined = msg ;
120- let modified = false ;
121-
122- do {
123- if ( handleMessage ( nextMsg ) ) {
124- modified = true ;
125- }
126-
127- if ( devTools . current ) {
128- devTools . current . send ( nextMsg . name , currentModel ) ;
129- }
130-
131- nextMsg = buffer . shift ( ) ;
132- } while ( nextMsg ) ;
133-
134- running = false ;
135-
136- if ( isMountedRef . current && modified ) {
137- setModel ( ( ) => {
138- Services . logger ?. debug ( "Update model for" , name , currentModel ) ;
139-
140- return currentModel ;
141- } ) ;
142- }
143- } ) ,
87+ setModel ( ( ) => {
88+ Services . logger ?. debug ( "Update model for" , name , currentModel ) ;
89+
90+ return currentModel ;
91+ } ) ;
92+ } ,
93+ ( msg ) => {
94+ devToolsRef . current ?. send ( msg . name , currentModel ) ;
95+ } ,
96+ fakeOptions ?. dispatch ,
97+ ) ,
14498 [ ] ,
14599 ) ;
146100
147- let initializedModel = model ;
101+ let initializedModel = currentModel ;
148102
149103 if ( ! initializedModel ) {
150104 enablePatches ( ) ;
151105
152- const [ initModel , ...initCommands ] = fakeOptions ?. model ? [ fakeOptions . model ] : init ( props ) ;
153-
154- initializedModel = castImmutable ( freeze ( initModel , true ) ) ;
155- currentModel = initializedModel ;
156- setModel ( initializedModel ) ;
157-
158- devTools . current ?. init ( initializedModel ) ;
159-
160- Services . logger ?. debug ( "Initial model for" , name , initializedModel ) ;
161-
162- execCmd ( dispatch , ...initCommands ) ;
106+ initializedModel = castImmutable (
107+ runInit (
108+ name ,
109+ init ,
110+ props ,
111+ ( initModel ) => {
112+ currentModel = castImmutable ( freeze ( initModel , true ) ) ;
113+ setModel ( currentModel ) ;
114+ } ,
115+ dispatch ,
116+ devToolsRef . current ,
117+ fakeOptions ?. model ,
118+ ) ,
119+ ) ;
163120 }
164121
165- useEffect ( ( ) => {
166- if ( firstCallRef . current ) {
167- firstCallRef . current = false ;
168-
169- return ;
170- }
171-
172- setModel ( null ) ;
173- // biome-ignore lint/correctness/useExhaustiveDependencies: We only want to reinitialize when the reInitOn dependencies change
174- } , reInitOn ?? [ ] ) ;
175-
176- useEffect ( ( ) => {
177- if ( subscription ) {
178- const subscriptionResult = subscription ( initializedModel , props ) ;
179-
180- if ( subscriptionIsFunctionArray ( subscriptionResult ) ) {
181- const destructors = subscriptionResult . map ( ( sub ) => sub ( dispatch ) ) . filter ( ( destructor ) => destructor !== undefined ) ;
182-
183- return function combinedDestructor ( ) {
184- for ( const destructor of destructors ) {
185- destructor ( ) ;
186- }
187- } ;
188- }
189-
190- const [ subCmd , destructor ] = subscriptionResult ;
191-
192- execCmd ( dispatch , subCmd ) ;
193-
194- return destructor ;
195- }
196- // biome-ignore lint/correctness/useExhaustiveDependencies: We only want to reinitialize when the reInitOn dependencies change
197- } , reInitOn ?? [ ] ) ;
122+ useReInit ( setModel , reInitOn ) ;
123+ useSubscription ( subscription , initializedModel , props , dispatch , reInitOn ) ;
198124
199125 return [ initializedModel , dispatch ] ;
200126
0 commit comments