From 655fffe7c9e15ab242ebebe0b6c611ddb3ad346f Mon Sep 17 00:00:00 2001 From: Vladislav Shkodin Date: Sun, 20 Dec 2020 12:59:25 +0200 Subject: [PATCH] refactor: remove immutable from status and auth (#4727) --- .../netlify-cms-core/src/actions/status.ts | 6 +-- .../src/components/App/App.js | 11 ++--- .../src/components/App/Header.js | 4 +- .../src/components/Editor/Editor.js | 4 +- .../src/components/Editor/EditorInterface.js | 2 +- .../src/components/Editor/EditorToolbar.js | 4 +- .../src/reducers/__tests__/auth.spec.ts | 24 ++++++---- .../netlify-cms-core/src/reducers/auth.ts | 48 ++++++++++--------- .../netlify-cms-core/src/reducers/status.ts | 45 +++++++---------- 9 files changed, 73 insertions(+), 75 deletions(-) diff --git a/packages/netlify-cms-core/src/actions/status.ts b/packages/netlify-cms-core/src/actions/status.ts index b39faee8..296fcc04 100644 --- a/packages/netlify-cms-core/src/actions/status.ts +++ b/packages/netlify-cms-core/src/actions/status.ts @@ -1,8 +1,8 @@ -import { State } from '../types/redux'; -import { currentBackend } from '../backend'; import { ThunkDispatch } from 'redux-thunk'; import { AnyAction } from 'redux'; import { actions as notifActions } from 'redux-notifications'; +import { State } from '../types/redux'; +import { currentBackend } from '../backend'; const { notifSend, notifDismiss } = notifActions; @@ -37,7 +37,7 @@ export function checkBackendStatus() { return async (dispatch: ThunkDispatch, getState: () => State) => { try { const state = getState(); - if (state.status.get('isFetching')) { + if (state.status.isFetching) { return; } diff --git a/packages/netlify-cms-core/src/components/App/App.js b/packages/netlify-cms-core/src/components/App/App.js index e1fb8ca1..30441c44 100644 --- a/packages/netlify-cms-core/src/components/App/App.js +++ b/packages/netlify-cms-core/src/components/App/App.js @@ -73,13 +73,13 @@ const RouteInCollection = ({ collections, render, ...props }) => { class App extends React.Component { static propTypes = { - auth: ImmutablePropTypes.map, + auth: PropTypes.object.isRequired, config: ImmutablePropTypes.map, collections: ImmutablePropTypes.orderedMap, loadConfig: PropTypes.func.isRequired, loginUser: PropTypes.func.isRequired, logoutUser: PropTypes.func.isRequired, - user: ImmutablePropTypes.map, + user: PropTypes.object, isFetching: PropTypes.bool.isRequired, publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]), siteId: PropTypes.string, @@ -129,9 +129,8 @@ class App extends React.Component { {React.createElement(backend.authComponent(), { onLogin: this.handleLogin.bind(this), - error: auth && auth.get('error'), - isFetching: auth && auth.get('isFetching'), - inProgress: (auth && auth.get('isFetching')) || false, + error: auth.error, + inProgress: auth.isFetching, siteId: this.props.config.getIn(['backend', 'site_domain']), base_url: this.props.config.getIn(['backend', 'base_url'], null), authEndpoint: this.props.config.getIn(['backend', 'auth_endpoint']), @@ -262,7 +261,7 @@ class App extends React.Component { function mapStateToProps(state) { const { auth, config, collections, globalUI, mediaLibrary } = state; - const user = auth && auth.get('user'); + const user = auth.user; const isFetching = globalUI.get('isFetching'); const publishMode = config && config.get('publish_mode'); const useMediaLibrary = !mediaLibrary.get('externalLibrary'); diff --git a/packages/netlify-cms-core/src/components/App/Header.js b/packages/netlify-cms-core/src/components/App/Header.js index 2f7567b9..2b90e30b 100644 --- a/packages/netlify-cms-core/src/components/App/Header.js +++ b/packages/netlify-cms-core/src/components/App/Header.js @@ -113,7 +113,7 @@ const AppHeaderNavList = styled.ul` class Header extends React.Component { static propTypes = { - user: ImmutablePropTypes.map.isRequired, + user: PropTypes.object.isRequired, collections: ImmutablePropTypes.orderedMap.isRequired, onCreateEntryClick: PropTypes.func.isRequired, onLogoutClick: PropTypes.func.isRequired, @@ -216,7 +216,7 @@ class Header extends React.Component { diff --git a/packages/netlify-cms-core/src/components/Editor/Editor.js b/packages/netlify-cms-core/src/components/Editor/Editor.js index 9a099928..58c7571b 100644 --- a/packages/netlify-cms-core/src/components/Editor/Editor.js +++ b/packages/netlify-cms-core/src/components/Editor/Editor.js @@ -67,7 +67,7 @@ export class Editor extends React.Component { deployPreview: ImmutablePropTypes.map, loadDeployPreview: PropTypes.func.isRequired, currentStatus: PropTypes.string, - user: ImmutablePropTypes.map.isRequired, + user: PropTypes.object, location: PropTypes.shape({ pathname: PropTypes.string, search: PropTypes.string, @@ -432,7 +432,7 @@ function mapStateToProps(state, ownProps) { const newEntry = ownProps.newRecord === true; const fields = selectFields(collection, slug); const entry = newEntry ? null : selectEntry(state, collectionName, slug); - const user = auth && auth.get('user'); + const user = auth.user; const hasChanged = entryDraft.get('hasChanged'); const displayUrl = config.get('display_url'); const hasWorkflow = config.get('publish_mode') === EDITORIAL_WORKFLOW; diff --git a/packages/netlify-cms-core/src/components/Editor/EditorInterface.js b/packages/netlify-cms-core/src/components/Editor/EditorInterface.js index b326ddce..09a6b92e 100644 --- a/packages/netlify-cms-core/src/components/Editor/EditorInterface.js +++ b/packages/netlify-cms-core/src/components/Editor/EditorInterface.js @@ -410,7 +410,7 @@ EditorInterface.propTypes = { unPublish: PropTypes.func.isRequired, onDuplicate: PropTypes.func.isRequired, onChangeStatus: PropTypes.func.isRequired, - user: ImmutablePropTypes.map.isRequired, + user: PropTypes.object, hasChanged: PropTypes.bool, displayUrl: PropTypes.string, hasWorkflow: PropTypes.bool, diff --git a/packages/netlify-cms-core/src/components/Editor/EditorToolbar.js b/packages/netlify-cms-core/src/components/Editor/EditorToolbar.js index 08e8dfec..df3695ac 100644 --- a/packages/netlify-cms-core/src/components/Editor/EditorToolbar.js +++ b/packages/netlify-cms-core/src/components/Editor/EditorToolbar.js @@ -233,7 +233,7 @@ class EditorToolbar extends React.Component { onDuplicate: PropTypes.func.isRequired, onPublishAndNew: PropTypes.func.isRequired, onPublishAndDuplicate: PropTypes.func.isRequired, - user: ImmutablePropTypes.map.isRequired, + user: PropTypes.object, hasChanged: PropTypes.bool, displayUrl: PropTypes.string, collection: ImmutablePropTypes.map.isRequired, @@ -606,7 +606,7 @@ class EditorToolbar extends React.Component { diff --git a/packages/netlify-cms-core/src/reducers/__tests__/auth.spec.ts b/packages/netlify-cms-core/src/reducers/__tests__/auth.spec.ts index 7cc7fde4..02eae1a2 100644 --- a/packages/netlify-cms-core/src/reducers/__tests__/auth.spec.ts +++ b/packages/netlify-cms-core/src/reducers/__tests__/auth.spec.ts @@ -1,32 +1,38 @@ -import { fromJS } from 'immutable'; import { authenticating, authenticate, authError, logout } from '../../actions/auth'; import auth, { defaultState } from '../auth'; describe('auth', () => { it('should handle an empty state', () => { // eslint-disable-next-line @typescript-eslint/ban-ts-ignore - // @ts-ignore + // @ts-ignore auth reducer doesn't accept empty action expect(auth(undefined, {})).toEqual(defaultState); }); it('should handle an authentication request', () => { - expect(auth(undefined, authenticating())).toEqual(defaultState.set('isFetching', true)); + expect(auth(undefined, authenticating())).toEqual({ + ...defaultState, + isFetching: true, + }); }); it('should handle authentication', () => { const user = { name: 'joe', token: 'token' }; - expect(auth(undefined, authenticate(user))).toEqual(defaultState.set('user', fromJS(user))); + expect(auth(undefined, authenticate(user))).toEqual({ + ...defaultState, + user, + }); }); it('should handle an authentication error', () => { - expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual( - defaultState.set('error', 'Error: Bad credentials'), - ); + expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual({ + ...defaultState, + error: 'Error: Bad credentials', + }); }); it('should handle logout', () => { const user = { name: 'joe', token: 'token' }; - const newState = auth(defaultState.set('user', fromJS(user)), logout()); - expect(newState.get('user')).toBeUndefined(); + const newState = auth({ ...defaultState, user }, logout()); + expect(newState.user).toBeUndefined(); }); }); diff --git a/packages/netlify-cms-core/src/reducers/auth.ts b/packages/netlify-cms-core/src/reducers/auth.ts index ab66b37c..11d582f1 100644 --- a/packages/netlify-cms-core/src/reducers/auth.ts +++ b/packages/netlify-cms-core/src/reducers/auth.ts @@ -1,4 +1,4 @@ -import { fromJS } from 'immutable'; +import { produce } from 'immer'; import { User } from 'netlify-cms-lib-util'; import { AUTH_REQUEST, @@ -8,35 +8,37 @@ import { LOGOUT, AuthAction, } from '../actions/auth'; -import { StaticallyTypedRecord } from '../types/immutable'; -export type Auth = StaticallyTypedRecord<{ +export type Auth = { isFetching: boolean; - user: StaticallyTypedRecord | undefined; + user: User | undefined; error: string | undefined; -}>; +}; -export const defaultState = fromJS({ +export const defaultState: Auth = { isFetching: false, user: undefined, error: undefined, -}) as Auth; - -const auth = (state = defaultState, action: AuthAction) => { - switch (action.type) { - case AUTH_REQUEST: - return state.set('isFetching', true); - case AUTH_SUCCESS: - return state.set('user', fromJS(action.payload)); - case AUTH_FAILURE: - return state.set('error', action.payload && action.payload.toString()); - case AUTH_REQUEST_DONE: - return state.set('isFetching', false); - case LOGOUT: - return state.set('user', undefined).set('isFetching', false); - default: - return state; - } }; +const auth = produce((state: Auth, action: AuthAction) => { + switch (action.type) { + case AUTH_REQUEST: + state.isFetching = true; + break; + case AUTH_SUCCESS: + state.user = action.payload; + break; + case AUTH_FAILURE: + state.error = action.payload && action.payload.toString(); + break; + case AUTH_REQUEST_DONE: + state.isFetching = false; + break; + case LOGOUT: + state.user = undefined; + state.isFetching = false; + } +}, defaultState); + export default auth; diff --git a/packages/netlify-cms-core/src/reducers/status.ts b/packages/netlify-cms-core/src/reducers/status.ts index 5b14305a..994f4afb 100644 --- a/packages/netlify-cms-core/src/reducers/status.ts +++ b/packages/netlify-cms-core/src/reducers/status.ts @@ -1,46 +1,37 @@ -import { fromJS } from 'immutable'; +import { produce } from 'immer'; import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE, StatusAction } from '../actions/status'; -import { StaticallyTypedRecord } from '../types/immutable'; -export type Status = StaticallyTypedRecord<{ +export type Status = { isFetching: boolean; - status: StaticallyTypedRecord<{ - auth: StaticallyTypedRecord<{ status: boolean }>; - api: StaticallyTypedRecord<{ status: boolean; statusPage: string }>; - }>; + status: { + auth: { status: boolean }; + api: { status: boolean; statusPage: string }; + }; error: Error | undefined; -}>; +}; -const defaultState = fromJS({ +const defaultState: Status = { isFetching: false, status: { auth: { status: true }, api: { status: true, statusPage: '' }, }, error: undefined, -}) as Status; +}; -const status = (state = defaultState, action: StatusAction) => { +const status = produce((state: Status, action: StatusAction) => { switch (action.type) { case STATUS_REQUEST: - return state.set('isFetching', true); + state.isFetching = true; + break; case STATUS_SUCCESS: - return state.withMutations(map => { - map.set('isFetching', false); - map.set('status', fromJS(action.payload.status)); - }); + state.isFetching = false; + state.status = action.payload.status; + break; case STATUS_FAILURE: - return state.withMutations(map => { - map.set('isFetching', false); - map.set('error', action.payload.error); - }); - default: - return state; + state.isFetching = false; + state.error = action.payload.error; } -}; - -export const selectStatus = (status: Status) => { - return status.get('status').toJS(); -}; +}, defaultState); export default status;