Extract useSelectState from SelectContext

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
This commit is contained in:
Qstick
2023-05-31 19:41:55 -05:00
parent 78aab80703
commit 9fd3eb4d6b
9 changed files with 162 additions and 133 deletions
+35 -115
View File
@@ -1,58 +1,28 @@
import { cloneDeep } from 'lodash';
import React, { useEffect } from 'react';
import areAllSelected from 'Utilities/Table/areAllSelected';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import React, { useCallback, useEffect } from 'react';
import useSelectState, { SelectState } from 'Helpers/Hooks/useSelectState';
import ModelBase from './ModelBase';
export enum SelectActionType {
Reset,
SelectAll,
UnselectAll,
ToggleSelected,
RemoveItem,
UpdateItems,
}
type SelectedState = Record<number, boolean>;
interface SelectState {
selectedState: SelectedState;
lastToggled: number | null;
allSelected: boolean;
allUnselected: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
items: any[];
}
type SelectAction =
| { type: SelectActionType.Reset }
| { type: SelectActionType.SelectAll }
| { type: SelectActionType.UnselectAll }
export type SelectContextAction =
| { type: 'reset' }
| { type: 'selectAll' }
| { type: 'unselectAll' }
| {
type: SelectActionType.ToggleSelected;
type: 'toggleSelected';
id: number;
isSelected: boolean;
shiftKey: boolean;
}
| {
type: SelectActionType.RemoveItem;
type: 'removeItem';
id: number;
}
| {
type: SelectActionType.UpdateItems;
type: 'updateItems';
items: ModelBase[];
};
type Dispatch = (action: SelectAction) => void;
const initialState = {
selectedState: {},
lastToggled: null,
allSelected: false,
allUnselected: true,
items: [],
};
export type SelectDispatch = (action: SelectContextAction) => void;
interface SelectProviderOptions<T extends ModelBase> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -60,90 +30,40 @@ interface SelectProviderOptions<T extends ModelBase> {
items: Array<T>;
}
function getSelectedState(items: ModelBase[], existingState: SelectedState) {
return items.reduce((acc: SelectedState, item) => {
const id = item.id;
acc[id] = existingState[id] ?? false;
return acc;
}, {});
}
// TODO: Can this be reused?
const SelectContext = React.createContext<[SelectState, Dispatch] | undefined>(
cloneDeep(undefined)
);
function selectReducer(state: SelectState, action: SelectAction): SelectState {
const { items, selectedState } = state;
switch (action.type) {
case SelectActionType.Reset: {
return cloneDeep(initialState);
}
case SelectActionType.SelectAll: {
return {
items,
...selectAll(selectedState, true),
};
}
case SelectActionType.UnselectAll: {
return {
items,
...selectAll(selectedState, false),
};
}
case SelectActionType.ToggleSelected: {
const result = {
items,
...toggleSelected(
state,
items,
action.id,
action.isSelected,
action.shiftKey
),
};
return result;
}
case SelectActionType.UpdateItems: {
const nextSelectedState = getSelectedState(action.items, selectedState);
return {
...state,
...areAllSelected(nextSelectedState),
selectedState: nextSelectedState,
items: action.items,
};
}
default: {
throw new Error(`Unhandled action type: ${action.type}`);
}
}
}
const SelectContext = React.createContext<
[SelectState, SelectDispatch] | undefined
>(cloneDeep(undefined));
export function SelectProvider<T extends ModelBase>(
props: SelectProviderOptions<T>
) {
const { items } = props;
const selectedState = getSelectedState(items, {});
const [state, dispatch] = useSelectState();
const [state, dispatch] = React.useReducer(selectReducer, {
selectedState,
lastToggled: null,
allSelected: false,
allUnselected: true,
items,
});
const dispatchWrapper = useCallback(
(action: SelectContextAction) => {
switch (action.type) {
case 'reset':
case 'removeItem':
dispatch(action);
break;
const value: [SelectState, Dispatch] = [state, dispatch];
default:
dispatch({
...action,
items,
});
break;
}
},
[items, dispatch]
);
const value: [SelectState, SelectDispatch] = [state, dispatchWrapper];
useEffect(() => {
dispatch({ type: SelectActionType.UpdateItems, items });
}, [items]);
dispatch({ type: 'updateItems', items });
}, [items, dispatch]);
return (
<SelectContext.Provider value={value}>