Skip to content

Commit

Permalink
Refactor: Add types to legacy Redux modules
Browse files Browse the repository at this point in the history
In this patch we're adopting the changes in #1855 and updating legacy
reducers, action types, and action creators. This serves both as a guide
for adding new Redux pieces while improving the overall type-safety and
completeness of the Redux subsystem in the app.
  • Loading branch information
dmsnell committed Jan 24, 2020
1 parent 23a31e4 commit d9de6ac
Show file tree
Hide file tree
Showing 13 changed files with 174 additions and 84 deletions.
70 changes: 69 additions & 1 deletion lib/state/action-types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import * as T from '../types';

import { AuthState } from './auth/constants';

export const AUTH_SET = 'AUTH_SET';
export const FILTER_NOTES = 'FILTER_NOTES';
export const TAG_DRAWER_TOGGLE = 'TAG_DRAWER_TOGGLE';
Expand All @@ -7,7 +11,71 @@ export type Action<
Args extends { [extraProps: string]: unknown } = {}
> = { type: T } & Args;

export type ActionType = never;
/*
* Legacy action-creators that are more like global setters than Redux actions
*/
export type SetAccountName = Action<'setAccountName', { accountName: string }>;
export type SetAutoHideMenuBar = Action<
'setAutoHideMenuBar',
{ autoHideMenuBar: boolean }
>;
export type SetFocusMode = Action<
'setFocusMode',
{ focusModeEnabled: boolean }
>;
export type SetFontSize = Action<'setFontSize', { fontSize?: number }>;
export type SetLineLength = Action<
'setLineLength',
{ lineLength: T.LineLength }
>;
export type SetMarkdownEnabled = Action<
'setMarkdownEnabled',
{ markdownEnabled: boolean }
>;
export type SetNoteDisplay = Action<
'setNoteDisplay',
{ noteDisplay: T.ListDisplayMode }
>;
export type SetSortReversed = Action<
'setSortReversed',
{ sortReversed: boolean }
>;
export type SetSortTagsAlpha = Action<
'setSortTagsAlpha',
{ sortTagsAlpha: boolean }
>;
export type SetSortType = Action<'setSortType', { sortType: T.SortType }>;
export type SetSpellCheck = Action<
'setSpellCheck',
{ spellCheckEnabled: boolean }
>;
export type SetTheme = Action<'setTheme', { theme: T.Theme }>;
export type SetWPToken = Action<'setWPToken', { token: string }>;

/*
* Normal action types
*/
export type FilterNotes = Action<'FILTER_NOTES', { notes: T.NoteEntity[] }>;
export type SetAuth = Action<'AUTH_SET', { status: AuthState }>;
export type ToggleTagDrawer = Action<'TAG_DRAWER_TOGGLE', { show: boolean }>;

export type ActionType =
| FilterNotes
| SetAccountName
| SetAuth
| SetAutoHideMenuBar
| SetFocusMode
| SetFontSize
| SetLineLength
| SetMarkdownEnabled
| SetNoteDisplay
| SetSortReversed
| SetSortTagsAlpha
| SetSortType
| SetSpellCheck
| SetTheme
| SetWPToken
| ToggleTagDrawer;

export type ActionCreator<A extends ActionType> = (...args: any[]) => A;
export type Reducer<S> = (state: S | undefined, action: ActionType) => S;
2 changes: 2 additions & 0 deletions lib/state/actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as auth from './auth/actions';
import * as settings from './settings/actions';
import * as ui from './ui/actions';

export default {
auth,
settings,
ui,
};
40 changes: 16 additions & 24 deletions lib/state/auth/actions.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
import { AUTH_SET } from '../action-types';
import * as A from '../action-types';

import {
Authorized,
Authorizing,
InvalidCredentials,
LoginError,
NotAuthorized,
} from './constants';

export const reset = () => ({
type: AUTH_SET,
status: NotAuthorized,
export const reset: A.ActionCreator<A.SetAuth> = () => ({
type: 'AUTH_SET',
status: 'not-authorized',
});

export const setInvalidCredentials = () => ({
type: AUTH_SET,
status: InvalidCredentials,
export const setInvalidCredentials: A.ActionCreator<A.SetAuth> = () => ({
type: 'AUTH_SET',
status: 'invalid-credentials',
});

export const setLoginError = () => ({
type: AUTH_SET,
status: LoginError,
export const setLoginError: A.ActionCreator<A.SetAuth> = () => ({
type: 'AUTH_SET',
status: 'login-error',
});

export const setPending = () => ({
type: AUTH_SET,
status: Authorizing,
export const setPending: A.ActionCreator<A.SetAuth> = () => ({
type: 'AUTH_SET',
status: 'authorizing',
});

export const setAuthorized = () => ({
type: AUTH_SET,
status: Authorized,
export const setAuthorized: A.ActionCreator<A.SetAuth> = () => ({
type: 'AUTH_SET',
status: 'authorized',
});
11 changes: 6 additions & 5 deletions lib/state/auth/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const Authorized = Symbol();
export const Authorizing = Symbol();
export const InvalidCredentials = Symbol();
export const LoginError = Symbol();
export const NotAuthorized = Symbol();
export type AuthState =
| 'authorized'
| 'authorizing'
| 'invalid-credentials'
| 'login-error'
| 'not-authorized';
11 changes: 6 additions & 5 deletions lib/state/auth/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { combineReducers } from 'redux';

import { AUTH_SET } from '../action-types';
import * as A from '../action-types';
import { AuthState } from './constants';

import { NotAuthorized } from './constants';

export const authStatus = (state = NotAuthorized, { type, status }) =>
AUTH_SET === type ? status : state;
export const authStatus: A.Reducer<AuthState> = (
state = 'not-authorized',
action
) => ('AUTH_SET' === action.type ? action.status : state);

export default combineReducers({
authStatus,
Expand Down
25 changes: 9 additions & 16 deletions lib/state/auth/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { get } from 'lodash';
import * as S from '../';

import {
Authorized,
Authorizing,
InvalidCredentials,
LoginError,
} from './constants';
export const authIsPending = (state: S.State) =>
'authorizing' === state.auth.authStatus;

export const authIsPending = state =>
Authorizing === get(state, 'auth.authStatus');
export const hasInvalidCredentials = (state: S.State) =>
'invalid-credentials' === state.auth.authStatus;

export const hasInvalidCredentials = state =>
InvalidCredentials === get(state, 'auth.authStatus');
export const hasLoginError = (state: S.State) =>
'login-error' === state.auth.authStatus;

export const hasLoginError = state =>
LoginError === get(state, 'auth.authStatus');

export const isAuthorized = state =>
Authorized === get(state, 'auth.authStatus');
export const isAuthorized = (state: S.State) =>
'authorized' === state.auth.authStatus;
10 changes: 9 additions & 1 deletion lib/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
* All data should flow through here
*/

import { compose, createStore, combineReducers, applyMiddleware } from 'redux';
import {
Store as ReduxStore,
compose,
createStore,
combineReducers,
applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import persistState from 'redux-localstorage';
import { omit } from 'lodash';
Expand Down Expand Up @@ -73,6 +79,8 @@ export const store = createStore(
)
);

export type Store = ReduxStore<State, A.ActionType>;

export type MapState<StateProps, OwnProps = {}> = (
state: State,
ownProps: OwnProps
Expand Down
23 changes: 14 additions & 9 deletions lib/state/settings/actions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { getIpcRenderer } from '../../utils/electron';

import * as A from '../action-types';

const ipc = getIpcRenderer();

export const setFontSize = fontSize => ({
export const setFontSize: A.ActionCreator<A.SetFontSize> = (
fontSize?: number
) => ({
type: 'setFontSize',
fontSize,
});
Expand All @@ -23,19 +27,20 @@ export const decreaseFontSize = () => (dispatch, getState) => {
dispatch(setFontSize(fontSize - 1));
};

export const resetFontSize = () => setFontSize(undefined);
export const resetFontSize: A.ActionCreator<A.SetFontSize> = () =>
setFontSize(undefined);

export const activateTheme = theme => ({
export const activateTheme: A.ActionCreator<A.SetTheme> = theme => ({
type: 'setTheme',
theme,
});

export const setNoteDisplay = noteDisplay => ({
export const setNoteDisplay: A.ActionCreator<A.SetNoteDisplay> = noteDisplay => ({
type: 'setNoteDisplay',
noteDisplay,
});

export const setLineLength = lineLength => ({
export const setLineLength: A.ActionCreator<A.SetLineLength> = lineLength => ({
type: 'setLineLength',
lineLength,
});
Expand All @@ -47,7 +52,7 @@ export const toggleSortOrder = () => (dispatch, getState) => {
});
};

export const setSortType = sortType => ({
export const setSortType: A.ActionCreator<A.SetSortType> = sortType => ({
type: 'setSortType',
sortType,
});
Expand All @@ -59,17 +64,17 @@ export const toggleSortTagsAlpha = () => (dispatch, getState) => {
});
};

export const setMarkdown = markdownEnabled => ({
export const setMarkdown: A.ActionCreator<A.SetMarkdownEnabled> = markdownEnabled => ({
type: 'setMarkdownEnabled',
markdownEnabled,
});

export const setAccountName = accountName => ({
export const setAccountName: A.ActionCreator<A.SetAccountName> = accountName => ({
type: 'setAccountName',
accountName,
});

export const setWPToken = token => ({
export const setWPToken: A.ActionCreator<A.SetWPToken> = token => ({
type: 'setWPToken',
token,
});
Expand Down
22 changes: 14 additions & 8 deletions lib/state/settings/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { clamp } from 'lodash';

import * as A from '../action-types';
import * as T from '../../types';

export const initialState = {
accountName: null,
accountName: null as string | null,
autoHideMenuBar: false,
focusModeEnabled: false,
fontSize: 16,
lineLength: 'narrow',
lineLength: 'narrow' as T.LineLength,
markdownEnabled: false,
noteDisplay: 'comfy',
noteDisplay: 'comfy' as T.ListDisplayMode,
sortReversed: false,
sortTagsAlpha: false,
sortType: 'modificationDate',
sortType: 'modificationDate' as T.SortType,
spellCheckEnabled: true,
theme: 'system',
wpToken: false,
theme: 'system' as T.Theme,
wpToken: false as string | boolean,
};

function reducer(state = initialState, action) {
const reducer: A.Reducer<typeof initialState> = (
state = initialState,
action
) => {
switch (action.type) {
case 'setAccountName':
return { ...state, accountName: action.accountName };
Expand Down Expand Up @@ -50,6 +56,6 @@ function reducer(state = initialState, action) {
default:
return state;
}
}
};

export default reducer;
15 changes: 9 additions & 6 deletions lib/state/ui/actions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { FILTER_NOTES, TAG_DRAWER_TOGGLE } from '../action-types';

import * as A from '../action-types';
import * as T from '../../types';

export const filterNotes = (notes: T.NoteEntity[]) => ({
type: FILTER_NOTES,
export const filterNotes: A.ActionCreator<A.FilterNotes> = (
notes: T.NoteEntity[]
) => ({
type: 'FILTER_NOTES',
notes,
});

export const toggleTagDrawer = (show: boolean) => ({
type: TAG_DRAWER_TOGGLE,
export const toggleTagDrawer: A.ActionCreator<A.ToggleTagDrawer> = (
show: boolean
) => ({
type: 'TAG_DRAWER_TOGGLE',
show,
});
8 changes: 6 additions & 2 deletions lib/state/ui/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { AnyAction } from 'redux';
import { AnyAction, Dispatch, Middleware } from 'redux';
import { filterNotes as filterAction } from './actions';
import filterNotes from '../../utils/filter-notes';

import * as S from '../';

let searchTimeout: NodeJS.Timeout;

export default store => {
export const middleware: Middleware<{}, S.State, Dispatch> = store => {
const updateNotes = () =>
store.dispatch(filterAction(filterNotes(store.getState().appState)));

Expand Down Expand Up @@ -43,3 +45,5 @@ export default store => {
return result;
};
};

export default middleware;
Loading

0 comments on commit d9de6ac

Please sign in to comment.