diff --git a/js/actionCreators/equalizer.ts b/js/actionCreators/equalizer.ts
index 5eb690ab..4755de57 100644
--- a/js/actionCreators/equalizer.ts
+++ b/js/actionCreators/equalizer.ts
@@ -64,7 +64,9 @@ export function toggleEq(): Thunk {
}
export function toggleEqAuto(): Thunk {
- return (dispatch, getState) => {
- dispatch({ type: SET_EQ_AUTO, value: !getState().equalizer.auto });
+ return dispatch => {
+ // We don't actually support this feature yet so don't let the user ever turn it on.
+ // dispatch({ type: SET_EQ_AUTO, value: !getState().equalizer.auto });
+ dispatch({ type: SET_EQ_AUTO, value: false });
};
}
diff --git a/js/components/EqualizerWindow/EqAuto.tsx b/js/components/EqualizerWindow/EqAuto.tsx
index 110f7a31..51c8da84 100644
--- a/js/components/EqualizerWindow/EqAuto.tsx
+++ b/js/components/EqualizerWindow/EqAuto.tsx
@@ -1,34 +1,15 @@
import React from "react";
-import { connect } from "react-redux";
import classnames from "classnames";
-import { SET_EQ_AUTO } from "../../actionTypes";
-import { Dispatch, AppState } from "../../types";
+import * as Actions from "../../actionCreators";
+import { useTypedSelector, useActionCreator } from "../../hooks";
-interface StateProps {
- auto: boolean;
-}
+const EqAuto = React.memo(() => {
+ const selected = useTypedSelector(state => state.equalizer.auto);
+ const toggleAuto = useActionCreator(Actions.toggleEqAuto);
+ return (
+
+ );
+});
-interface DispatchProps {
- toggleAuto(): void;
-}
-
-const EqAuto = (props: StateProps & DispatchProps) => {
- const className = classnames({ selected: props.auto });
- return ;
-};
-
-const mapStateToProps = (state: AppState): StateProps => {
- return { auto: state.equalizer.auto };
-};
-const mapDispatchToProps = () => (dispatch: Dispatch): DispatchProps => {
- // We don't support auto.
- return {
- toggleAuto: () => dispatch({ type: SET_EQ_AUTO, value: false }),
- };
-};
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(EqAuto);
+export default EqAuto;
diff --git a/js/components/MainWindow/ActionButtons.tsx b/js/components/MainWindow/ActionButtons.tsx
index 77d256a1..50442ac9 100644
--- a/js/components/MainWindow/ActionButtons.tsx
+++ b/js/components/MainWindow/ActionButtons.tsx
@@ -1,37 +1,21 @@
import React from "react";
-import { connect } from "react-redux";
-import { previous, play, pause, stop, next } from "../../actionCreators";
-import { Dispatch } from "../../types";
+import * as Actions from "../../actionCreators";
+import { useActionCreator } from "../../hooks";
-interface DispatchProps {
- previous(): void;
- play(): void;
- pause(): void;
- stop(): void;
- next(): void;
-}
+const ActionButtons = React.memo(() => {
+ const previous = useActionCreator(Actions.previous);
+ const play = useActionCreator(Actions.play);
+ const pause = useActionCreator(Actions.pause);
+ const next = useActionCreator(Actions.next);
+ return (
+
+ );
+});
-const ActionButtons = (props: DispatchProps) => (
-
-);
-
-const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
- return {
- previous: () => dispatch(previous()),
- play: () => dispatch(play()),
- pause: () => dispatch(pause()),
- stop: () => dispatch(stop()),
- next: () => dispatch(next()),
- };
-};
-
-export default connect(
- null,
- mapDispatchToProps
-)(ActionButtons);
+export default ActionButtons;
diff --git a/js/components/MainWindow/Close.tsx b/js/components/MainWindow/Close.tsx
index 6921b174..d24032d0 100644
--- a/js/components/MainWindow/Close.tsx
+++ b/js/components/MainWindow/Close.tsx
@@ -1,23 +1,12 @@
import React from "react";
-import { connect } from "react-redux";
import ClickedDiv from "../ClickedDiv";
+import { useActionCreator } from "../../hooks";
-import { close } from "../../actionCreators";
-import { Dispatch } from "../../types";
+import * as Actions from "../../actionCreators";
-interface DispatchProps {
- onClick: () => void;
-}
+const Close = React.memo(() => {
+ const close = useActionCreator(Actions.close);
+ return ;
+});
-const Close = ({ onClick }: DispatchProps) => (
-
-);
-
-const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
- return { onClick: () => dispatch(close()) };
-};
-
-export default connect(
- null,
- mapDispatchToProps
-)(Close);
+export default Close;
diff --git a/js/components/MainWindow/ClutterBar.tsx b/js/components/MainWindow/ClutterBar.tsx
index 4b8a0b2a..1be1df34 100644
--- a/js/components/MainWindow/ClutterBar.tsx
+++ b/js/components/MainWindow/ClutterBar.tsx
@@ -1,53 +1,45 @@
import React from "react";
-import { connect } from "react-redux";
import classnames from "classnames";
-import { SET_FOCUS, UNSET_FOCUS } from "../../actionTypes";
-import { toggleDoubleSizeMode } from "../../actionCreators";
-import { AppState, Dispatch } from "../../types";
+import * as Actions from "../../actionCreators";
+import { Action, Dispatch, Thunk } from "../../types";
import OptionsContextMenu from "../OptionsContextMenu";
import ContextMenuTarget from "../ContextMenuTarget";
+import { useActionCreator, useTypedSelector } from "../../hooks";
+import * as Selectors from "../../selectors";
-interface StateProps {
- doubled: boolean;
+function setFocusDouble(): Action {
+ return Actions.setFocus("double");
}
-interface DispatchProps {
- handleMouseDown(): void;
- handleMouseUp(): void;
+function mouseUp(): Thunk {
+ return dispatch => {
+ dispatch(Actions.toggleDoubleSizeMode());
+ dispatch(Actions.unsetFocus());
+ };
}
-const ClutterBar = (props: StateProps & DispatchProps) => (
-
-);
-
-const mapStateToProps = (state: AppState): StateProps => ({
- doubled: state.display.doubled,
+const ClutterBar = React.memo(() => {
+ const handleMouseDown = useActionCreator(setFocusDouble);
+ const handleMouseUp = useActionCreator(mouseUp);
+ const doubled = useTypedSelector(Selectors.getDoubled);
+ return (
+
+ );
});
-const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
- handleMouseDown: () => dispatch({ type: SET_FOCUS, input: "double" }),
- handleMouseUp: () => {
- dispatch(toggleDoubleSizeMode());
- dispatch({ type: UNSET_FOCUS });
- },
-});
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(ClutterBar);
+export default ClutterBar;
diff --git a/js/components/MainWindow/Kbps.tsx b/js/components/MainWindow/Kbps.tsx
index d7b3d87f..73512baf 100644
--- a/js/components/MainWindow/Kbps.tsx
+++ b/js/components/MainWindow/Kbps.tsx
@@ -1,22 +1,16 @@
import React from "react";
-import { connect } from "react-redux";
import CharacterString from "../CharacterString";
-import { AppState } from "../../types";
import * as Selectors from "../../selectors";
+import { useTypedSelector } from "../../hooks";
-interface StateProps {
- kbps: string | null;
-}
+const Kbps = React.memo(() => {
+ const kbps = useTypedSelector(Selectors.getKbps);
+ return (
+
+ {kbps || ""}
+
+ );
+});
-const Kbps = (props: StateProps) => (
-
- {props.kbps || ""}
-
-);
-
-function mapStateToProps(state: AppState): StateProps {
- return { kbps: Selectors.getKbps(state) };
-}
-
-export default connect(mapStateToProps)(Kbps);
+export default Kbps;
diff --git a/js/components/MainWindow/Khz.tsx b/js/components/MainWindow/Khz.tsx
index 8ddd9ab2..53c4ee54 100644
--- a/js/components/MainWindow/Khz.tsx
+++ b/js/components/MainWindow/Khz.tsx
@@ -1,22 +1,16 @@
import React from "react";
-import { connect } from "react-redux";
import CharacterString from "../CharacterString";
-import { AppState } from "../../types";
import * as Selectors from "../../selectors";
+import { useTypedSelector } from "../../hooks";
-interface StateProps {
- khz: string | null;
-}
+const Khz = React.memo(() => {
+ const khz = useTypedSelector(Selectors.getKhz);
+ return (
+
+ {khz || ""}
+
+ );
+});
-const Khz = (props: StateProps) => (
-
- {props.khz || ""}
-
-);
-
-function mapStateToProps(state: AppState): StateProps {
- return { khz: Selectors.getKhz(state) };
-}
-
-export default connect(mapStateToProps)(Khz);
+export default Khz;
diff --git a/js/components/MainWindow/MainBalance.tsx b/js/components/MainWindow/MainBalance.tsx
index 91effaf7..a8d334be 100644
--- a/js/components/MainWindow/MainBalance.tsx
+++ b/js/components/MainWindow/MainBalance.tsx
@@ -1,12 +1,8 @@
import React from "react";
-import { connect } from "react-redux";
import Balance from "../Balance";
-import { AppState } from "../../types";
-
-interface StateProps {
- balance: number;
-}
+import * as Selectors from "../../selectors";
+import { useTypedSelector } from "../../hooks";
export const offsetFromBalance = (balance: number): number => {
const percent = Math.abs(balance) / 100;
@@ -15,15 +11,14 @@ export const offsetFromBalance = (balance: number): number => {
return offset;
};
-const MainBalance = (props: StateProps) => (
-
-);
-
-const mapStateToProps = (state: AppState): StateProps => ({
- balance: state.media.balance,
+const MainBalance = React.memo(() => {
+ const balance = useTypedSelector(Selectors.getBalance);
+ return (
+
+ );
});
-export default connect(mapStateToProps)(MainBalance);
+export default MainBalance;
diff --git a/js/components/MainWindow/MainVolume.tsx b/js/components/MainWindow/MainVolume.tsx
index 7a7918d9..142c75e2 100644
--- a/js/components/MainWindow/MainVolume.tsx
+++ b/js/components/MainWindow/MainVolume.tsx
@@ -1,16 +1,11 @@
import React from "react";
-import { connect } from "react-redux";
import * as Selectors from "../../selectors";
import Volume from "../Volume";
-import { AppState } from "../../types";
+import { useTypedSelector } from "../../hooks";
-interface Props {
- volume: number;
-}
-
-const MainVolume = (props: Props) => {
- const { volume } = props;
+const MainVolume = React.memo(() => {
+ const volume = useTypedSelector(Selectors.getVolume);
const percent = volume / 100;
const sprite = Math.round(percent * 28);
const offset = (sprite - 1) * 15;
@@ -23,10 +18,6 @@ const MainVolume = (props: Props) => {
);
-};
-
-const mapStateToProps = (state: AppState): Props => ({
- volume: Selectors.getVolume(state),
});
-export default connect(mapStateToProps)(MainVolume);
+export default MainVolume;
diff --git a/js/components/MainWindow/Minimize.tsx b/js/components/MainWindow/Minimize.tsx
index ceb5fd9f..27a57380 100644
--- a/js/components/MainWindow/Minimize.tsx
+++ b/js/components/MainWindow/Minimize.tsx
@@ -1,22 +1,11 @@
import React from "react";
-import { connect } from "react-redux";
import ClickedDiv from "../ClickedDiv";
import * as Actions from "../../actionCreators";
-import { Dispatch } from "../../types";
+import { useActionCreator } from "../../hooks";
-interface Props {
- minimize(): void;
-}
-
-const Minimize = ({ minimize }: Props) => (
-
-);
-
-const mapDispatchToProps = (dispatch: Dispatch) => ({
- minimize: () => dispatch(Actions.minimize()),
+const Minimize = React.memo(() => {
+ const minimize = useActionCreator(Actions.minimize);
+ return ;
});
-export default connect(
- null,
- mapDispatchToProps
-)(Minimize);
+export default Minimize;
diff --git a/js/components/MainWindow/MonoStereo.tsx b/js/components/MainWindow/MonoStereo.tsx
index f1f2f90a..6bcbebf3 100644
--- a/js/components/MainWindow/MonoStereo.tsx
+++ b/js/components/MainWindow/MonoStereo.tsx
@@ -1,27 +1,16 @@
import React from "react";
-import { connect } from "react-redux";
import classnames from "classnames";
-import { AppState } from "../../types";
import * as Selectors from "../../selectors";
+import { useTypedSelector } from "../../hooks";
-interface Props {
- channels: number | null;
-}
+const MonoStereo = React.memo(() => {
+ const channels = useTypedSelector(Selectors.getChannels);
+ return (
+
+ );
+});
-const MonoStereo = (props: Props) => (
-
-);
-
-const mapStateToProps = (state: AppState): Props => {
- return {
- channels: Selectors.getChannels(state),
- };
-};
-
-export default connect(mapStateToProps)(MonoStereo);
+export default MonoStereo;
diff --git a/js/components/MainWindow/PlaylistToggleButton.js b/js/components/MainWindow/PlaylistToggleButton.js
index 3fe51319..4c6bbd85 100644
--- a/js/components/MainWindow/PlaylistToggleButton.js
+++ b/js/components/MainWindow/PlaylistToggleButton.js
@@ -1,28 +1,25 @@
import React from "react";
-import { connect } from "react-redux";
import classnames from "classnames";
-import { getWindowOpen } from "../../selectors";
-import { toggleWindow } from "../../actionCreators";
+import * as Selectors from "../../selectors";
+import * as Actions from "../../actionCreators";
+import { useTypedSelector, useActionCreator } from "../../hooks";
-const PlaylistToggleButton = props => (
-
-);
+function togglePlaylist() {
+ return Actions.toggleWindow("playlist");
+}
-const mapStateToProps = state => ({
- selected: getWindowOpen(state)("playlist"),
+const PlaylistToggleButton = React.memo(() => {
+ const selected = useTypedSelector(Selectors.getWindowOpen)("playlist");
+ const handleClick = useActionCreator(togglePlaylist);
+ return (
+
+ );
});
-const mapDispatchToProps = {
- handleClick: () => toggleWindow("playlist"),
-};
-
-export default connect(
- mapStateToProps,
- mapDispatchToProps
-)(PlaylistToggleButton);
+export default PlaylistToggleButton;
diff --git a/js/hooks.ts b/js/hooks.ts
index 0353554f..1e6e6266 100644
--- a/js/hooks.ts
+++ b/js/hooks.ts
@@ -1,5 +1,7 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, useCallback } from "react";
+import { useDispatch, useSelector } from "react-redux";
import * as Utils from "./utils";
+import { Action, Thunk, AppState } from "./types";
interface Size {
width: number;
@@ -25,3 +27,18 @@ export function useWindowSize() {
}, [setSize, handler]);
return size;
}
+
+export function useActionCreator Action | Thunk>(
+ actionCreator: T
+): (...funcArgs: Parameters) => void {
+ const dispatch = useDispatch();
+ return useCallback((...args) => dispatch(actionCreator(...args)), [
+ dispatch,
+ actionCreator,
+ ]);
+}
+
+// TODO: Return useSelector directly and apply the type without wrapping
+export function useTypedSelector(selector: (state: AppState) => T): T {
+ return useSelector(selector);
+}
diff --git a/js/serialization.test.ts b/js/serialization.test.ts
index 41cd714a..b0248dfc 100644
--- a/js/serialization.test.ts
+++ b/js/serialization.test.ts
@@ -138,14 +138,6 @@ describe("can serialize", () => {
expected: false,
});
- testSerialization({
- name: "equalizer auto",
- // @ts-ignore
- action: Actions.toggleEqAuto(),
- selector: Selectors.getEqualizerAuto,
- expected: true,
- });
-
testSerialization({
name: "equalizer band",
action: Actions.setEqBand(60, 100),