I converted the rest of the site over to using Redux createSlice. I also refactored Drag & Drop to have its own slice. After considering how things need to work, I really need an area that is separate just to maintain drag & drop. With that said, I also needed to store what was being dragged and dropped, since the id’s are not unique across all tables.

The state tracks what is being dragged, dropped, and the dragged item that was recently dropped. This helps with the flash animation once you drop the item. It also tracks the item being hovered over as well, but the state cleans up after itself, so you’ll only see over or drop in the state, but not both.
This was plenty of fun, but then it led down a long path of setting up external reducers and checking the drag & drop types before making changes to the state.
Similar to how the slice has extraReducers, I also setup my sagas in a similar fashion to have an extraSagas folder to separate handling actions from outside of the slice. In all honesty, it’s not really all that necessary. The extra reducers were setup to allow additional reducers without creating actions, and they are mapped out with a builder. The extraSagas were simply setup as an additional fork, purely for code maintainability.

To keep things a bit more organized, these folders are also broken down by slice.

Just watching the Redux DevTools inspector, it’s clear to see the difference between Drag & Drop actions and data being fetched.

I was hard-coding a string value to represent the type of data being drag & dropped, but then I decided that using the slice.name may be more optimal since the visual cues with emoji are baked in and easier to associate with each slice.

The Drag & Drop attributes were changed as well to include both the id and type so that a document.querySelector can find it for drop effects.
<li draggable drag-none data-dnd-id="1" data-dnd-type="types 🦊" draggable="true" data-drop-target-for-element="true" >
With having two separate attributes, the document query selector needed to be updated to look for elements matching both datattributes.
const element = document.querySelector(
`[data-dnd-id="${id}"][data-dnd-type="${type}"]`
);
Other Stuff
Other things that happened today – selectors returning functions. I decided to go ahead and use this approach in order to pass an id to my child components, and let them ask for data.
const getItem = useSelector(selectGetItem); const item = useMemo(() => getItem(id), [getItem, id]);
I still don’t know if this is the proper way of doing things, or a coding nightmare. I can’t find much about selectors that return functions when it comes to Redux. It feels like they should only return primitive data.
The button component that lists all the types now only asks for the ID’s to list instead of item data. It too has a function component that it calls when the parent id changes
Effects have been separated out of some of my components. The code to setup Drag & Drop is fairly large, so I moved it out of the components and made things a bit more generic along the way. Once you start modularizing the building of effects, it sometimes reveals a dependency that you hadn’t considered
The root reducer and saga have been moved to a “root” folder, to mimic how slices are setup, and to provide a similar structure. This is in part to start moving to the features pattern, and to get some of the code refactored before I make the jump. I was able to setup an ActionReducerMapBuilder for the root state to simplify the need to run an action to restore the root state. I haven’t found a way to use it as the reducer, or hook it into the reducers created from “combineReducers”. For now, I’m using the old method where I have a switch case statement, and then run all other reducers afterwards.
const sliceReducers = combineReducers(reducers);
export const reducer: Reducer<RootState> = (
state: RootState | undefined = getInitialState(),
action: UnknownAction
) => {
switch (action.type) {
case actions.restore.type:
state = restore(
state,
action as ReturnType<typeof actions.restore>
);
break;
default:
break;
}
return sliceReducers(state, action);
};
export const newReducer = (
_state: RootState,
builder: ActionReducerMapBuilder<RootState>
) => {
builder.addCase(actions.restore.type, restore);
return sliceReducers;
};
RTK Query is hooked up and running. However, I haven’t made use of it just yet. I’m getting there.
