reducers: use typed useSelector hook

This refactors the code to use a typed useSelector() hook.
This commit is contained in:
David Lechner
2021-12-27 17:48:41 -06:00
parent 8527cf86b2
commit 8d26f768bf
13 changed files with 65 additions and 84 deletions
+4 -4
View File
@@ -3,10 +3,10 @@
import { Classes } from '@blueprintjs/core';
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import SplitterLayout from 'react-splitter-layout';
import Editor from '../editor/Editor';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import { toggleBoolean } from '../settings/actions';
import { BooleanSettingId } from '../settings/defaults';
import StatusBar from '../status-bar/StatusBar';
@@ -122,8 +122,8 @@ const Docs: React.FunctionComponent = (_props) => {
};
const App: React.FunctionComponent = (_props) => {
const darkMode = useSelector((s: RootState): boolean => s.settings.darkMode);
const showDocs = useSelector((s: RootState): boolean => s.settings.showDocs);
const darkMode = useSelector((s): boolean => s.settings.darkMode);
const showDocs = useSelector((s): boolean => s.settings.showDocs);
const [isDragging, setIsDragging] = useState(false);
// darkMode class has to be applied to body element, otherwise it won't
+4 -4
View File
@@ -12,10 +12,10 @@ import tomorrowNightEightiesTheme from 'monaco-themes/themes/Tomorrow-Night-Eigh
import xcodeTheme from 'monaco-themes/themes/Xcode_default.json';
import React, { useEffect, useRef } from 'react';
import MonacoEditor, { monaco } from 'react-monaco-editor';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { IDisposable } from 'xterm';
import { compile } from '../mpy/actions';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import { toggleBoolean } from '../settings/actions';
import { BooleanSettingId } from '../settings/defaults';
import { isMacOS } from '../utils/os';
@@ -63,7 +63,7 @@ const xcodeId = 'xcode';
monaco.editor.defineTheme(xcodeId, xcodeTheme as monaco.editor.IStandaloneThemeData);
const contextMenu = (_props: ContextMenu2ContentProps): JSX.Element => {
const editor = useSelector((state: RootState) => state.editor.current);
const editor = useSelector((s) => s.editor.current);
const [i18n] = useI18n({ id: 'editor', translations: { en }, fallback: en });
@@ -143,7 +143,7 @@ const Editor: React.FunctionComponent = (_props) => {
return () => window.removeEventListener('storage', onStorage);
});
const darkMode = useSelector((state: RootState) => state.settings.darkMode);
const darkMode = useSelector((s) => s.settings.darkMode);
const [i18n] = useI18n({ id: 'editor', translations: { en }, fallback: en });
+3 -3
View File
@@ -2,9 +2,9 @@
// Copyright (c) 2020-2021 The Pybricks Authors
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import * as notificationActions from '../notifications/actions';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import OpenFileButton, { OpenFileButtonProps } from '../toolbar/OpenFileButton';
import { TooltipId } from '../toolbar/i18n';
import * as editorActions from './actions';
@@ -13,7 +13,7 @@ import openIcon from './open.svg';
type OpenButtonProps = Pick<OpenFileButtonProps, 'id'>;
const OpenButton: React.FunctionComponent<OpenButtonProps> = (props) => {
const editor = useSelector((state: RootState) => state.editor.current);
const editor = useSelector((s) => s.editor.current);
const dispatch = useDispatch();
return (
+3 -3
View File
@@ -2,9 +2,9 @@
// Copyright (c) 2020-2021 The Pybricks Authors
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import * as editorActions from '../editor/actions';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
import { TooltipId } from '../toolbar/i18n';
import downloadIcon from './save.svg';
@@ -13,7 +13,7 @@ type SaveAsButtonProps = Pick<ActionButtonProps, 'id'> &
Pick<ActionButtonProps, 'keyboardShortcut'>;
const SaveAsButton: React.FunctionComponent<SaveAsButtonProps> = (props) => {
const editor = useSelector((state: RootState) => state.editor.current);
const editor = useSelector((s) => s.editor.current);
const dispatch = useDispatch();
+6 -8
View File
@@ -2,11 +2,11 @@
// Copyright (c) 2020-2021 The Pybricks Authors
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { BleConnectionState } from '../ble/reducers';
import { BootloaderConnectionState } from '../lwp3-bootloader/reducers';
import * as notificationActions from '../notifications/actions';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import OpenFileButton, { OpenFileButtonProps } from '../toolbar/OpenFileButton';
import { TooltipId } from '../toolbar/i18n';
import { flashFirmware } from './actions';
@@ -15,12 +15,10 @@ import firmwareIcon from './firmware.svg';
type FlashButtonProps = Pick<OpenFileButtonProps, 'id'>;
const FlashButton: React.FunctionComponent<FlashButtonProps> = (props) => {
const bootloaderConnection = useSelector(
(state: RootState) => state.bootloader.connection,
);
const bleConnection = useSelector((state: RootState) => state.ble.connection);
const flashing = useSelector((state: RootState) => state.firmware.flashing);
const progress = useSelector((state: RootState) => state.firmware.progress);
const bootloaderConnection = useSelector((s) => s.bootloader.connection);
const bleConnection = useSelector((s) => s.ble.connection);
const flashing = useSelector((s) => s.firmware.flashing);
const progress = useSelector((s) => s.firmware.progress);
const dispatch = useDispatch();
+4 -6
View File
@@ -2,11 +2,11 @@
// Copyright (c) 2020-2021 The Pybricks Authors
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { toggleBluetooth } from '../ble/actions';
import { BleConnectionState } from '../ble/reducers';
import { BootloaderConnectionState } from '../lwp3-bootloader/reducers';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
import { TooltipId } from '../toolbar/i18n';
import btConnectedIcon from './bt-connected.svg';
@@ -15,10 +15,8 @@ import btDisconnectedIcon from './bt-disconnected.svg';
type BluetoothButtonProps = Pick<ActionButtonProps, 'id'>;
const BluetoothButton: React.FunctionComponent<BluetoothButtonProps> = (props) => {
const bootloaderConnection = useSelector(
(state: RootState) => state.bootloader.connection,
);
const bleConnection = useSelector((state: RootState) => state.ble.connection);
const bootloaderConnection = useSelector((s) => s.bootloader.connection);
const bleConnection = useSelector((s) => s.ble.connection);
const isDisconnected =
bootloaderConnection === BootloaderConnectionState.Disconnected &&
+5 -7
View File
@@ -2,8 +2,8 @@
// Copyright (c) 2020-2021 The Pybricks Authors
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../reducers';
import { useDispatch } from 'react-redux';
import { useSelector } from '../reducers';
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
import { TooltipId } from '../toolbar/i18n';
import { downloadAndRun } from './actions';
@@ -14,11 +14,9 @@ type RunButtonProps = Pick<ActionButtonProps, 'id'> &
Pick<ActionButtonProps, 'keyboardShortcut'>;
const RunButton: React.FunctionComponent<RunButtonProps> = (props) => {
const editor = useSelector((state: RootState) => state.editor.current);
const downloadProgress = useSelector(
(state: RootState) => state.hub.downloadProgress,
);
const runtime = useSelector((state: RootState) => state.hub.runtime);
const editor = useSelector((s) => s.editor.current);
const downloadProgress = useSelector((s) => s.hub.downloadProgress);
const runtime = useSelector((s) => s.hub.runtime);
const dispatch = useDispatch();
+3 -3
View File
@@ -2,8 +2,8 @@
// Copyright (c) 2020-2021 The Pybricks Authors
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../reducers';
import { useDispatch } from 'react-redux';
import { useSelector } from '../reducers';
import ActionButton, { ActionButtonProps } from '../toolbar/ActionButton';
import { TooltipId } from '../toolbar/i18n';
import { stop } from './actions';
@@ -14,7 +14,7 @@ type StopButtonProps = Pick<ActionButtonProps, 'id'> &
Pick<ActionButtonProps, 'keyboardShortcut'>;
const StopButton: React.FunctionComponent<StopButtonProps> = (props) => {
const runtime = useSelector((state: RootState) => state.hub.runtime);
const runtime = useSelector((s) => s.hub.runtime);
const dispatch = useDispatch();
+4 -4
View File
@@ -14,9 +14,9 @@ import {
} from '@blueprintjs/core';
import { useI18n } from '@shopify/react-i18n';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { appName } from '../app/constants';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import { fetchList, select } from './actions';
import { LicenseStringId } from './i18n';
import en from './i18n.en.json';
@@ -31,7 +31,7 @@ type LicenseListPanelProps = {
const LicenseListPanel: React.VoidFunctionComponent<LicenseListPanelProps> = (
props,
) => {
const licenseList = useSelector((state: RootState) => state.licenses.list);
const licenseList = useSelector((s) => s.licenses.list);
return (
<div className="pb-license-list">
@@ -54,7 +54,7 @@ const LicenseListPanel: React.VoidFunctionComponent<LicenseListPanelProps> = (
};
const LicenseInfoPanel = React.forwardRef<HTMLDivElement>((_props, ref) => {
const licenseInfo = useSelector((state: RootState) => state.licenses.selected);
const licenseInfo = useSelector((s) => s.licenses.selected);
const [i18n] = useI18n({ id: 'license', translations: { en }, fallback: en });
+6
View File
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2021 The Pybricks Authors
import { TypedUseSelectorHook, useSelector as useReduxSelector } from 'react-redux';
import { Reducer, combineReducers } from 'redux';
import app from './app/reducers';
import ble from './ble/reducers';
@@ -31,3 +32,8 @@ export const rootReducer = combineReducers({
type StateFromReducer<R> = R extends Reducer<infer S> ? S : never;
export type RootState = StateFromReducer<typeof rootReducer>;
/**
* Typed version of react-redux useSelector() hook.
*/
export const useSelector: TypedUseSelectorHook<RootState> = useReduxSelector;
+13 -27
View File
@@ -19,7 +19,7 @@ import {
import { Tooltip2 } from '@blueprintjs/popover2';
import { useI18n } from '@shopify/react-i18n';
import React, { useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import AboutDialog from '../about/AboutDialog';
import { checkForUpdate, installPrompt, reload } from '../app/actions';
import {
@@ -30,7 +30,7 @@ import {
tooltipDelay,
} from '../app/constants';
import { pseudolocalize } from '../i18n';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import ExternalLinkIcon from '../utils/ExternalLinkIcon';
import { isMacOS } from '../utils/os';
import { setBoolean, setString, toggleBoolean } from './actions';
@@ -47,31 +47,17 @@ type SettingsProps = {
const SettingsDrawer: React.FunctionComponent<SettingsProps> = (props) => {
const [isAboutDialogOpen, setIsAboutDialogOpen] = useState(false);
const showDocs = useSelector((state: RootState) => state.settings.showDocs);
const darkMode = useSelector((state: RootState) => state.settings.darkMode);
const flashCurrentProgram = useSelector(
(state: RootState) => state.settings.flashCurrentProgram,
);
const serviceWorker = useSelector((state: RootState) => state.app.serviceWorker);
const checkingForUpdate = useSelector(
(state: RootState) => state.app.checkingForUpdate,
);
const updateAvailable = useSelector(
(state: RootState) => state.app.updateAvailable,
);
const beforeInstallPrompt = useSelector(
(state: RootState) => state.app.beforeInstallPrompt,
);
const promptingInstall = useSelector(
(state: RootState) => state.app.promptingInstall,
);
const readyForOfflineUse = useSelector(
(state: RootState) => state.app.readyForOfflineUse,
);
const hubName = useSelector((state: RootState) => state.settings.hubName);
const isHubNameValid = useSelector(
(state: RootState) => state.settings.isHubNameValid,
);
const showDocs = useSelector((s) => s.settings.showDocs);
const darkMode = useSelector((s) => s.settings.darkMode);
const flashCurrentProgram = useSelector((s) => s.settings.flashCurrentProgram);
const serviceWorker = useSelector((s) => s.app.serviceWorker);
const checkingForUpdate = useSelector((s) => s.app.checkingForUpdate);
const updateAvailable = useSelector((s) => s.app.updateAvailable);
const beforeInstallPrompt = useSelector((s) => s.app.beforeInstallPrompt);
const promptingInstall = useSelector((s) => s.app.promptingInstall);
const readyForOfflineUse = useSelector((s) => s.app.readyForOfflineUse);
const hubName = useSelector((s) => s.settings.hubName);
const isHubNameValid = useSelector((s) => s.settings.isHubNameValid);
const dispatch = useDispatch();
+7 -12
View File
@@ -5,9 +5,8 @@ import { Button, Intent, ProgressBar } from '@blueprintjs/core';
import { Classes as Classes2, Popover2, Popover2Props } from '@blueprintjs/popover2';
import { useI18n } from '@shopify/react-i18n';
import React from 'react';
import { useSelector } from 'react-redux';
import { BleConnectionState } from '../ble/reducers';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import { MessageId } from './i18n';
import en from './i18n.en.json';
@@ -19,11 +18,9 @@ const commonPopoverProps: Partial<Popover2Props> = {
};
const HubInfoButton: React.VFC = (_props) => {
const deviceName = useSelector((state: RootState) => state.ble.deviceName);
const deviceType = useSelector((state: RootState) => state.ble.deviceType);
const deviceFirmwareVersion = useSelector(
(state: RootState) => state.ble.deviceFirmwareVersion,
);
const deviceName = useSelector((s) => s.ble.deviceName);
const deviceType = useSelector((s) => s.ble.deviceType);
const deviceFirmwareVersion = useSelector((s) => s.ble.deviceFirmwareVersion);
const [i18n] = useI18n({ id: 'statusBar', translations: { en }, fallback: en });
@@ -69,10 +66,8 @@ const HubInfoButton: React.VFC = (_props) => {
};
const BatteryIndicator: React.VFC = (_props) => {
const charging = useSelector((state: RootState) => state.ble.deviceBatteryCharging);
const lowBatteryWarning = useSelector(
(state: RootState) => state.ble.deviceLowBatteryWarning,
);
const charging = useSelector((s) => s.ble.deviceBatteryCharging);
const lowBatteryWarning = useSelector((s) => s.ble.deviceLowBatteryWarning);
const [i18n] = useI18n({ id: 'statusBar', translations: { en }, fallback: en });
@@ -102,7 +97,7 @@ const BatteryIndicator: React.VFC = (_props) => {
};
const StatusBar: React.VFC = (_props) => {
const connection = useSelector((state: RootState) => state.ble.connection);
const connection = useSelector((s) => s.ble.connection);
return (
<div
+3 -3
View File
@@ -5,10 +5,10 @@ import { Menu, MenuDivider, MenuItem, ResizeSensor } from '@blueprintjs/core';
import { ContextMenu2, ContextMenu2ContentProps } from '@blueprintjs/popover2';
import { useI18n } from '@shopify/react-i18n';
import React, { useContext, useEffect, useMemo, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { Terminal as XTerm } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { RootState } from '../reducers';
import { useSelector } from '../reducers';
import { isMacOS } from '../utils/os';
import { TerminalContext } from './TerminalContext';
import { receiveData } from './actions';
@@ -100,7 +100,7 @@ function createContextMenu(
const Terminal: React.FC = (_props) => {
const { xterm, fitAddon } = useMemo(createXTerm, [createXTerm]);
const terminalRef = useRef<HTMLDivElement>(null);
const darkMode = useSelector((state: RootState) => state.settings.darkMode);
const darkMode = useSelector((s) => s.settings.darkMode);
const dispatch = useDispatch();
const terminalStream = useContext(TerminalContext);