This project was bootstrapped with Create React App. It uses Gnome TODO as inspiration.
To start it, run npm install
and then npm start
.
Its aim is to showcase how easy is to use Redux ToolKit (RTK) instead of pure Redux, while building a todo app. RTK includes utilities that help simplify many common use cases, including store setup, creating reducers and writing immutable update logic, and even creating entire "slices" of state at once.
Two smaller demos are set up on CodeSandbox:
The Redux Toolkit package is intended to be the standard way to write Redux logic. It was originally created to help address three common concerns about Redux:
- "Configuring a Redux store is too complicated"
- "I have to add a lot of packages to get Redux to do anything useful"
- "Redux requires too much boilerplate code"
<App />
<NavigationMenu />
- this is the navigation menu on the left<Calendar />
- connects to Redux to get all the Todos which have adueDate
by using theselectWithDueDate
selector<Overview />
- connects to Redux to get all the Todos viaselectAll
or incomplete ones viaselectAllIncomplete
; optionally, if it receiveslabelId
as a URL parameter, then it passes this to the aforementioned selectors; it also dispatchescreate
actions;<Todo title description completionDate dueDate labels priority />
- used to render a Todo and to dispatchupdate
andremove
actions<LabelsSelect {...TextFieldProps} /> and <LabelsPreview value />
- components which help in working with Labels
Although the implementation is in React, this component structure would also work in Svelte, Vue, or Angular.
The store is used to hold multiple Todos and Labels.
interface Todo {
id: string;
title: string;
description?: string;
creationDate: Date | string;
dueDate?: Date | string;
priority: TTodoPriority;
completionDate?: Date | string;
labels: Array<Label["id"]>;
}
interface Label {
id: string;
title: string;
// TODO add a color?: string; property
}
There are only 3 actions:
todosActions.create({title}: {title: string})
todosActions.update({id, changes}: {id: string; changes: Partial<Todo>})
todosActions.remove(id: string)
These are the two main functions from Redux Toolkit that were used:
A function that generates a set of prebuilt reducers and selectors for performing CRUD operations on a normalized state structure containing instances of a particular type of data object.
const todosAdapter = createEntityAdapter<Todo>({
sortComparer: (a, b) => {
// sorting logic: by completion, priority, due date, creation date
},
});
A function that accepts an initial state, an object full of reducer functions, and a "slice name", and automatically generates action creators and action types that correspond to the reducers and state.
export const { actions, reducer } = createSlice({
name: "todos",
initialState: filledState,
reducers: {
create: {
prepare(partialTodo: Partial<Todo>) {
const payload = new Todo(partialTodo);
// "Class instances are by definition not fully serializable"
// See https://stackoverflow.com/questions/61704805/getting-an-error-a-non-serializable-value-was-detected-in-the-state-when-using
return { payload: { ...payload } };
},
reducer: todosAdapter.addOne,
},
update: todosAdapter.updateOne,
remove: todosAdapter.removeOne,
},
});