Skip to content

Commit

Permalink
Replace redux connector w/ hooks (#131)
Browse files Browse the repository at this point in the history
Decorate storybook components individually to fix storyshots
Propertly wire up to redux store to storybook to minimize snapshot changes
Fix drag n drop on the web
  • Loading branch information
wcjordan authored Apr 21, 2024
1 parent 2060016 commit 5cbdc69
Show file tree
Hide file tree
Showing 24 changed files with 2,060 additions and 1,959 deletions.
2 changes: 1 addition & 1 deletion ui/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"react-dom": "~18.2.0",
"react-native": "0.73.5",
"react-native-draggable-flatlist": "4.0.1",
"react-native-gesture-handler": "~2.15.0",
"react-native-gesture-handler": "~2.14.0",
"react-native-paper": "~5.12.3",
"react-native-reanimated": "3.7.2",
"react-native-safe-area-context": "4.9.0",
Expand Down
114 changes: 69 additions & 45 deletions ui/js/src/App.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import _ from 'lodash';
import React from 'react';
import { Provider } from 'react-redux';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { AppLayout } from './App';
import { FILTER_STATUS, Todo, TodoPatch } from './redux/types';
import App from './App';
import { setupStore } from './redux/store';

function stubTodo(patch: TodoPatch): Todo {
return Object.assign(
Expand Down Expand Up @@ -30,45 +33,44 @@ function stubTodo(patch: TodoPatch): Todo {
);
}

const defaultProps = {
labels: [
{ name: 'low-energy' },
{ name: 'high-energy' },
{ name: 'vague' },
{ name: 'work' },
{ name: 'home' },
{ name: 'errand' },
{ name: 'mobile' },
{ name: 'desktop' },
{ name: 'email' },
{ name: 'urgent' },
{ name: '5 minutes' },
{ name: '25 minutes' },
{ name: '60 minutes' },
],
notificationQueue: [],
selectedPickerLabels: {
'5 minutes': true,
work: true,
home: true,
'low-energy': true,
mobile: true,
const defaultState = {
labelsApi: {
entries: [
{ name: 'low-energy' },
{ name: 'high-energy' },
{ name: 'vague' },
{ name: 'work' },
{ name: 'home' },
{ name: 'errand' },
{ name: 'mobile' },
{ name: 'desktop' },
{ name: 'email' },
{ name: 'urgent' },
{ name: '5 minutes' },
{ name: '25 minutes' },
{ name: '60 minutes' },
],
},
notifications: {
notificationQueue: [],
},
todosApi: {
entries: [
stubTodo({
id: 1,
description: 'First todo',
}),
stubTodo({
id: 2,
description: '2nd todo',
}),
],
},
todos: [
stubTodo({
id: 1,
description: 'First todo',
}),
stubTodo({
id: 2,
description: '2nd todo',
}),
],
workspace: {
csrfToken: null,
filterLabels: {
'5 minutes': FILTER_STATUS.Active,
work: FILTER_STATUS.Inverted,
work: FILTER_STATUS.Active,
home: FILTER_STATUS.Active,
'low-energy': FILTER_STATUS.Active,
mobile: FILTER_STATUS.Active,
Expand All @@ -78,25 +80,47 @@ const defaultProps = {
},
};

const wrapper = (appLayout) => <SafeAreaProvider>{appLayout}</SafeAreaProvider>;
const wrapper = (component, stateOverrides={}) => {
const initialState = _.mergeWith({}, defaultState, stateOverrides, (objValue, srcValue) => {
if (_.isArray(objValue)) {
return srcValue;
}
return undefined;
});
return (
<SafeAreaProvider>
<Provider store={setupStore(initialState)}>
{component}
</Provider>
</SafeAreaProvider>
);
};

export default {
title: 'App Layout',
component: AppLayout,
component: App,
};
export const DefaultLayout: React.FC = () =>
wrapper(<AppLayout {...defaultProps} todos={[]} />);
wrapper(<App />, {
todosApi: {
entries: []
}
});

export const ListTodosLayout: React.FC = () =>
wrapper(<AppLayout {...defaultProps} />);
wrapper(<App />);

const labelPickerWorkspace = Object.assign({}, defaultProps.workspace, {
labelTodoId: 1,
});
export const LabelPickerLayout: React.FC = () =>
wrapper(<AppLayout {...defaultProps} workspace={labelPickerWorkspace} />);
wrapper(<App />, {
workspace: {
labelTodoId: 1,
},
});

export const NotificationLayout: React.FC = () =>
wrapper(
<AppLayout {...defaultProps} notificationQueue={['Error logging in...']} />,
);
<App />, {
notifications: {
notificationQueue: ['Error logging in...']
}
});
129 changes: 6 additions & 123 deletions ui/js/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,9 @@
import { connect, ConnectedProps } from 'react-redux';
import React from 'react';
import { StatusBar, StyleSheet, View, ViewStyle } from 'react-native';

import ErrorBar from './components/ErrorBar';
import Login from './components/Login';
import TodoList from './components/TodoList';
import { useAppSelector } from './hooks';
import {
Label,
MoveTodoOperation,
Todo,
TodoPatch,
WorkspaceState,
} from './redux/types';
import {
addNotification,
completeAuthentication,
createTodo,
dismissNotification,
moveTodo,
setEditTodoId,
setLabelTodoId,
setWorkContext,
toggleLabel,
toggleShowCompletedTodos,
toggleShowLabelFilter,
updateTodo,
updateTodoLabels,
} from './redux/reducers';
import { RootState } from './redux/store';
import {
selectActiveWorkContext,
selectFilteredTodos,
selectIsLoading,
selectSelectedPickerLabels,
} from './selectors';
import { workContexts } from './redux/workspaceSlice';

interface Style {
root: ViewStyle;
Expand All @@ -56,56 +24,18 @@ const styles = StyleSheet.create<Style>({
},
});

const App: React.FC<ConnectedProps<typeof connector>> = function (
props: ConnectedProps<typeof connector>,
) {
const selectedPickerLabels = useAppSelector(selectSelectedPickerLabels);
const filteredTodos = useAppSelector(selectFilteredTodos);
const activeWorkContext = useAppSelector(selectActiveWorkContext);
const isLoading = useAppSelector(selectIsLoading);
return (
<AppLayout
{...props}
activeWorkContext={activeWorkContext}
filteredTodos={filteredTodos}
isLoading={isLoading}
selectedPickerLabels={selectedPickerLabels}
/>
);
};

export const AppLayout: React.FC<LayoutProps> = function (props: LayoutProps) {
const {
activeWorkContext,
addNotification,
completeAuthentication,
dismissNotification,
filteredTodos,
notificationQueue,
workspace,
...otherProps
} = props;
const { loggedIn, showCompletedTodos, showLabelFilter } = workspace;
const App: React.FC = function () {
const loggedIn = useAppSelector(state => state.workspace.loggedIn);
const notificationQueue = useAppSelector(state => state.notifications.notificationQueue);

let content: JSX.Element | null = null;
if (!loggedIn) {
content = (
<Login
addNotification={addNotification}
completeAuthentication={completeAuthentication}
/>
<Login />
);
} else {
content = (
<TodoList
activeWorkContext={activeWorkContext}
showCompletedTodos={showCompletedTodos}
showLabelFilter={showLabelFilter}
todos={filteredTodos}
workContexts={workContexts}
workspace={workspace}
{...otherProps}
/>
<TodoList />
);
}

Expand All @@ -122,56 +52,9 @@ export const AppLayout: React.FC<LayoutProps> = function (props: LayoutProps) {
<ErrorBar
key={notificationText}
text={notificationText}
dismissNotification={dismissNotification}
/>
</View>
);
};

type LayoutProps = {
activeWorkContext: string | undefined;
addNotification: (text: string) => void;
completeAuthentication: (token: string) => void;
createTodo: (description: string) => void;
dismissNotification: () => void;
filteredTodos: Todo[];
isLoading: boolean;
labels: Label[];
moveTodo: (operation: MoveTodoOperation) => void;
notificationQueue: string[];
selectedPickerLabels: { [label: string]: boolean };
setEditTodoId: (id: number | null) => void;
setLabelTodoId: (id: number | null) => void;
setWorkContext: (workContext: string) => void;
toggleLabel: (label: string) => void;
toggleShowCompletedTodos: () => void;
toggleShowLabelFilter: () => void;
updateTodo: (todoPatch: TodoPatch) => void;
updateTodoLabels: (labels: string[]) => void;
workspace: WorkspaceState;
};

const mapStateToProps = (state: RootState) => {
return {
labels: state.labelsApi.entries,
workspace: state.workspace,
notificationQueue: state.notifications.notificationQueue,
};
};
const mapDispatchToProps = {
addNotification,
completeAuthentication,
createTodo,
dismissNotification,
moveTodo,
setEditTodoId,
setLabelTodoId,
setWorkContext,
toggleLabel,
toggleShowCompletedTodos,
toggleShowLabelFilter,
updateTodo,
updateTodoLabels,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
export default connector(App);
export default App;
Loading

0 comments on commit 5cbdc69

Please sign in to comment.