Ver Fonte

refactor: moved state and dispatch context to root component

Fela Maslen há 5 anos atrás
pai
commit
b84cb835c9

+ 13 - 16
gmus-web/src/components/app.tsx

@@ -1,12 +1,11 @@
-import React, { Dispatch, Suspense, useCallback } from 'react';
+import React, { Suspense, useCallback, useContext } from 'react';
 import { StateInspector } from 'reinspect';
 
-import { LocalAction, stateSet } from '../actions';
+import { stateSet } from '../actions';
 import { DispatchContext, StateContext } from '../context/state';
 import { useMaster } from '../hooks/master';
 import { useKeepalive } from '../hooks/socket';
 import { useCurrentlyPlayingSongInfo } from '../hooks/status';
-import { GlobalState } from '../reducer';
 import { isMaster } from '../selectors';
 import { getSongUrl } from '../utils/url';
 import { LoadingWrapper } from './identify';
@@ -16,16 +15,18 @@ import { UIProvider } from './ui/types';
 
 export type Props = {
   socket: WebSocket;
-  state: GlobalState;
-  dispatch: Dispatch<LocalAction>;
 };
 
 const uiProvider = UIProvider.Cmus;
 const UI = uiProviders[uiProvider];
 
-export const App: React.FC<Props> = ({ socket, state, dispatch }) => {
+export const App: React.FC<Props> = ({ socket }) => {
   useKeepalive(socket);
-  useMaster(state, dispatch);
+  useMaster();
+
+  const state = useContext(StateContext);
+  const dispatch = useContext(DispatchContext);
+
   const currentSong = useCurrentlyPlayingSongInfo(state.player.songId);
 
   const onTimeUpdate = useCallback(
@@ -46,15 +47,11 @@ export const App: React.FC<Props> = ({ socket, state, dispatch }) => {
           timeUpdateFPS={1}
         />
       )}
-      <StateContext.Provider value={state}>
-        <DispatchContext.Provider value={dispatch}>
-          <StateInspector name="ui">
-            <Suspense fallback={<LoadingWrapper />}>
-              <UI isMaster={isMaster(state)} currentSong={currentSong} />
-            </Suspense>
-          </StateInspector>
-        </DispatchContext.Provider>
-      </StateContext.Provider>
+      <StateInspector name="ui">
+        <Suspense fallback={<LoadingWrapper />}>
+          <UI isMaster={isMaster(state)} currentSong={currentSong} />
+        </Suspense>
+      </StateInspector>
     </>
   );
 };

+ 8 - 1
gmus-web/src/components/root.tsx

@@ -1,6 +1,7 @@
 import React, { Reducer, useCallback, useReducer } from 'react';
 
 import { AnyAction, nameSet } from '../actions';
+import { DispatchContext, StateContext } from '../context/state';
 import { useDispatchWithEffects, useOnMessage, useSocket } from '../hooks/socket';
 import { globalReducer, GlobalState, initialState } from '../reducer';
 import { init } from '../utils/state';
@@ -31,5 +32,11 @@ export const Root: React.FC = () => {
     return <Identify connecting={connecting} onIdentify={onIdentify} />;
   }
 
-  return <App socket={socket} state={state} dispatch={dispatchWithEffects} />;
+  return (
+    <StateContext.Provider value={state}>
+      <DispatchContext.Provider value={dispatchWithEffects}>
+        <App socket={socket} />
+      </DispatchContext.Provider>
+    </StateContext.Provider>
+  );
 };

+ 23 - 12
gmus-web/src/hooks/master.spec.tsx

@@ -1,8 +1,9 @@
-import { act, render } from '@testing-library/react';
+import { act, render, RenderResult } from '@testing-library/react';
 import React from 'react';
 
 import { masterSet, stateSet } from '../actions';
 import { masterStateUpdateTimeout } from '../constants/system';
+import { DispatchContext, StateContext } from '../context/state';
 import { GlobalState, initialState, nullPlayer } from '../reducer';
 
 import { useMaster } from './master';
@@ -10,11 +11,21 @@ import { useMaster } from './master';
 describe(useMaster.name, () => {
   const dispatch = jest.fn();
 
-  const TestComponent: React.FC<GlobalState> = (state) => {
-    useMaster(state, dispatch);
+  const TestComponent: React.FC = () => {
+    useMaster();
     return null;
   };
 
+  const setup = (state: GlobalState, options: Partial<RenderResult> = {}): RenderResult =>
+    render(
+      <StateContext.Provider value={state}>
+        <DispatchContext.Provider value={dispatch}>
+          <TestComponent />
+        </DispatchContext.Provider>
+      </StateContext.Provider>,
+      options,
+    );
+
   describe('when there is no master initially', () => {
     const stateNoMaster: GlobalState = {
       ...initialState,
@@ -28,7 +39,7 @@ describe(useMaster.name, () => {
 
     it('should take control of master', () => {
       expect.assertions(2);
-      const { unmount } = render(<TestComponent {...stateNoMaster} />);
+      const { unmount } = setup(stateNoMaster);
 
       expect(dispatch).toHaveBeenCalledTimes(1);
       expect(dispatch).toHaveBeenCalledWith(stateSet({ master: 'my-client-name' }));
@@ -44,7 +55,7 @@ describe(useMaster.name, () => {
 
       it('should not take control of master', () => {
         expect.assertions(1);
-        const { unmount } = render(<TestComponent {...stateNoMasterUninit} />);
+        const { unmount } = setup(stateNoMasterUninit);
 
         expect(dispatch).not.toHaveBeenCalled();
 
@@ -84,10 +95,10 @@ describe(useMaster.name, () => {
       expect.assertions(2);
       jest.useFakeTimers();
 
-      const { container, unmount } = render(<TestComponent {...stateWithMaster} />);
+      const { container, unmount } = setup(stateWithMaster);
 
       act(() => {
-        render(<TestComponent {...stateMasterWentAway} />, { container });
+        setup(stateMasterWentAway, { container });
       });
 
       expect(dispatch).not.toHaveBeenCalled();
@@ -119,14 +130,14 @@ describe(useMaster.name, () => {
         expect.assertions(1);
         jest.useFakeTimers();
 
-        const { container, unmount } = render(<TestComponent {...stateWithMaster} />);
+        const { container, unmount } = setup(stateWithMaster);
         act(() => {
-          render(<TestComponent {...stateMasterWentAway} />, { container });
+          setup(stateMasterWentAway, { container });
         });
 
         setImmediate(() => {
           act(() => {
-            render(<TestComponent {...stateMasterWentAwayAnotherTookControl} />, { container });
+            setup(stateMasterWentAwayAnotherTookControl, { container });
           });
         });
 
@@ -155,7 +166,7 @@ describe(useMaster.name, () => {
     it('should continually refresh the server with the current state', () => {
       expect.assertions(6);
       const clock = jest.useFakeTimers();
-      const { unmount } = render(<TestComponent {...stateMaster} />);
+      const { unmount } = setup(stateMaster);
 
       act(() => {
         clock.runTimersToTime(masterStateUpdateTimeout - 1);
@@ -202,7 +213,7 @@ describe(useMaster.name, () => {
     it('should not send state updates periodically', () => {
       expect.assertions(1);
       const clock = jest.useFakeTimers();
-      const { unmount } = render(<TestComponent {...stateSlave} />);
+      const { unmount } = setup(stateSlave);
 
       act(() => {
         clock.runTimersToTime(masterStateUpdateTimeout);

+ 6 - 4
gmus-web/src/hooks/master.ts

@@ -1,11 +1,13 @@
-import { Dispatch, useEffect, useRef } from 'react';
+import { useContext, useEffect, useRef } from 'react';
 
-import { LocalAction, masterSet, stateSet } from '../actions';
+import { masterSet, stateSet } from '../actions';
 import { masterStateUpdateTimeout } from '../constants/system';
-import { GlobalState } from '../reducer';
+import { DispatchContext, StateContext } from '../context/state';
 import { isMaster } from '../selectors';
 
-export function useMaster(state: GlobalState, dispatch: Dispatch<LocalAction>): void {
+export function useMaster(): void {
+  const state = useContext(StateContext);
+  const dispatch = useContext(DispatchContext);
   const clientIsMaster = isMaster(state);
 
   const masterUpdateTimer = useRef<number>(0);