Skip to content

Commit a492d30

Browse files
Add redux to Chisel
1 parent 2b89800 commit a492d30

17 files changed

+588
-170
lines changed

jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ export default {
175175
// A map from regular expressions to paths to transformers
176176
// transform: {},
177177
extensionsToTreatAsEsm: [".ts", ".tsx"],
178+
setupFiles: [
179+
"./setupJestMock.ts",
180+
],
178181

179182
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
180183
// transformIgnorePatterns: [

library.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ import * as React from "react";
22
import * as ReactDOM from "react-dom/client";
33
import { BrowserRouter } from "react-router-dom";
44
import App from "./src/App";
5+
import { Provider } from 'react-redux';
6+
import { store } from './src/store';
57

68
const domNode = document.getElementById("root");
79
const root = ReactDOM.createRoot(domNode);
810
root.render(
9-
<BrowserRouter>
10-
<App />
11-
</BrowserRouter>,
11+
<Provider store={store}>
12+
<BrowserRouter>
13+
<App />
14+
</BrowserRouter>
15+
</Provider>,
1216
);

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"dependencies": {
4545
"@headlessui/react": "^1.7.13",
4646
"@heroicons/react": "^2.0.16",
47+
"@reduxjs/toolkit": "^1.9.5",
4748
"@tailwindcss/forms": "^0.5.3",
4849
"@types/node": "^18.15.9",
4950
"cookie-parser": "^1.4.6",
@@ -66,6 +67,7 @@
6667
"react-dom": "^18.2.0",
6768
"react-draggable": "^4.4.5",
6869
"react-quill": "^2.0.0",
70+
"react-redux": "^8.0.5",
6971
"react-router-dom": "^6.9.0",
7072
"syllable": "^5.0.1"
7173
},

setupJestMock.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const localStorageMock = {
2+
store: {},
3+
getItem(key) {
4+
return this.store[key];
5+
},
6+
setItem(key, value) {
7+
this.store[key] = value.toString();
8+
},
9+
clear() {
10+
this.store = {};
11+
},
12+
removeItem(key) {
13+
delete this.store[key];
14+
},
15+
};
16+
17+
Object.defineProperty(global, 'localStorage', { value: localStorageMock });

src/BookList.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import ListItem from "./ListItem";
99
import Popup from "./Popup";
1010
import { getCsrfToken } from "./utils";
1111
import * as fd from "./fetchData";
12+
import { useDispatch } from "react-redux";
13+
import { librarySlice } from "./reducers/librarySlice";
1214

1315
async function deleteBook(bookid: string, onDelete) {
1416
const res = await fd.deleteBook(bookid);
@@ -36,7 +38,7 @@ async function newBook(dispatch) {
3638
} else {
3739
const book = res.payload;
3840
console.log("new book", book);
39-
dispatch({ type: "ADD_BOOK", payload: book });
41+
dispatch(librarySlice.actions.addBook(book));
4042
}
4143
}
4244

@@ -49,17 +51,16 @@ export default function BookList({
4951
onChange,
5052
onDelete,
5153
saveBook,
52-
dispatch,
5354
canCloseSidebar = true,
5455
}: {
5556
books: t.Book[];
5657
selectedBookId: string;
5758
onChange: () => void;
5859
onDelete: (bookid: string) => void;
5960
saveBook: (book: t.Book) => void;
60-
dispatch: React.Dispatch<any>;
6161
canCloseSidebar?: boolean;
6262
}) {
63+
const dispatch = useDispatch();
6364
const [showPopup, setShowPopup] = React.useState(false);
6465
const [currentBook, setCurrentBook] = React.useState(books[0]);
6566

@@ -92,7 +93,7 @@ export default function BookList({
9293
const rightMenuItem = canCloseSidebar && {
9394
label: "Close",
9495
icon: <XMarkIcon className="w-4 h-4 xl:w-5 xl:h-5" />,
95-
onClick: () => dispatch({ type: "CLOSE_BOOK_LIST" }),
96+
onClick: () => dispatch(librarySlice.actions.closeBookList()),
9697
className: buttonStyles,
9798
};
9899

src/ChapterList.test.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { BrowserRouter } from "react-router-dom";
1010
import { render, fireEvent, screen } from "@testing-library/react";
1111
import "@testing-library/jest-dom";
1212
import { act } from "react-dom/test-utils";
13+
import { store } from "./store";
14+
import { Provider } from 'react-redux';
1315

1416
const props = {
1517
chapters: [chapter1, chapter2],
@@ -36,9 +38,12 @@ describe("ChapterList", () => {
3638
let container;
3739
beforeEach(() => {
3840
const res = render(
39-
<BrowserRouter>
40-
<ChapterList {...props} />
41-
</BrowserRouter>,
41+
42+
<Provider store={store}>
43+
<BrowserRouter>
44+
<ChapterList {...props} />
45+
</BrowserRouter>
46+
</Provider>,
4247
);
4348
container = res.container;
4449
});

src/ChapterList.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import ListMenu from "./ListMenu";
1717
import ListItem from "./ListItem";
1818
import Popup from "./Popup";
1919
import { getCsrfToken } from "./utils";
20+
import { useDispatch } from "react-redux";
21+
import { librarySlice } from "./reducers/librarySlice";
2022
// import Draggable from "react-draggable";
2123

2224
export default function ChapterList({
@@ -27,7 +29,6 @@ export default function ChapterList({
2729
onDelete,
2830
saveChapter,
2931
closeSidebar,
30-
dispatch,
3132
canCloseSidebar = true,
3233
}: {
3334
chapters: t.Chapter[];
@@ -37,24 +38,24 @@ export default function ChapterList({
3738
onDelete: any;
3839
saveChapter: any;
3940
closeSidebar: () => void;
40-
dispatch: React.Dispatch<t.ReducerAction>;
4141
canCloseSidebar?: boolean;
4242
}) {
43+
const dispatch = useDispatch();
4344
const [editing, setEditing] = React.useState(false);
4445
const [showPopup, setShowPopup] = React.useState(false);
4546
const [currentChapter, setCurrentChapter] = React.useState(chapters[0]);
4647

4748
async function deleteChapter(chapterid: string) {
4849
console.log("delete chapter", chapterid);
49-
dispatch({ type: "LOADING" });
50+
dispatch(librarySlice.actions.loading);
5051
const res = await fetch(`/api/deleteChapter`, {
5152
method: "POST",
5253
headers: {
5354
"Content-Type": "application/json",
5455
},
5556
body: JSON.stringify({ bookid, chapterid, csrfToken: getCsrfToken() }),
5657
});
57-
dispatch({ type: "LOADED" });
58+
dispatch(librarySlice.actions.loaded);
5859
if (!res.ok) {
5960
console.log(res.statusText);
6061
return;
@@ -64,15 +65,15 @@ export default function ChapterList({
6465

6566
async function favoriteChapter(chapterid: string) {
6667
console.log("favorite chapter", chapterid);
67-
dispatch({ type: "LOADING" });
68+
dispatch(librarySlice.actions.loading());
6869
const res = await fetch(`/api/favoriteChapter`, {
6970
method: "POST",
7071
headers: {
7172
"Content-Type": "application/json",
7273
},
7374
body: JSON.stringify({ bookid, chapterid, csrfToken: getCsrfToken() }),
7475
});
75-
dispatch({ type: "LOADED" });
76+
dispatch(librarySlice.actions.loaded());
7677
if (!res.ok) {
7778
console.log(res.statusText);
7879
return;
@@ -81,11 +82,11 @@ export default function ChapterList({
8182
}
8283

8384
const newChapter = async (title = "New Chapter", text = "") => {
84-
dispatch({ type: "LOADING" });
85+
dispatch(librarySlice.actions.loading());
8586
const result = await fd.newChapter(bookid, title, text);
86-
dispatch({ type: "LOADED" });
87+
dispatch(librarySlice.actions.loaded());
8788
if (result.tag === "error") {
88-
dispatch({ type: "SET_ERROR", payload: result.message });
89+
dispatch(librarySlice.actions.setError(result.message));
8990
return;
9091
}
9192
await onChange();
@@ -120,7 +121,7 @@ export default function ChapterList({
120121
const [removed] = ids.splice(result.source.index, 1);
121122
ids.splice(result.destination.index, 0, removed);
122123

123-
dispatch({ type: "SET_CHAPTER_ORDER", payload: { bookid, ids } });
124+
dispatch(librarySlice.actions.setChapterOrder({ bookid, ids }));
124125
};
125126

126127
const sublist = () => chapters.map((chapter, index) => (

src/Editor.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import "./globals.css";
33
import TextEditor from "./TextEditor";
44
import * as t from "./Types";
55
import { getCsrfToken } from "./utils";
6+
import { useDispatch, useSelector } from "react-redux";
7+
import { RootState } from "./store";
8+
import { librarySlice } from "./reducers/librarySlice";
69

710
export default function Editor({
8-
state,
9-
dispatch,
1011
onSave,
1112
}: {
12-
state: t.State;
13-
dispatch: React.Dispatch<t.ReducerAction>;
1413
onSave: (state: t.State) => void;
1514
}) {
15+
const state = useSelector((state: RootState) => state.library);
16+
const dispatch = useDispatch();
1617
async function saveChapter(state: t.State) {
1718
if (state.saved) return;
1819
if (!state.chapter) {
@@ -33,10 +34,10 @@ export default function Editor({
3334
});
3435

3536
if (!result.ok) {
36-
dispatch({ type: "SET_ERROR", payload: result.statusText });
37+
dispatch(librarySlice.actions.setError(result.statusText));
3738
} else {
38-
dispatch({ type: "CLEAR_ERROR" });
39-
dispatch({ type: "SET_SAVED", payload: true });
39+
dispatch(librarySlice.actions.clearError());
40+
dispatch(librarySlice.actions.setSaved(true));
4041
}
4142
}
4243

@@ -57,8 +58,6 @@ export default function Editor({
5758
</div>
5859
<div className="h-full w-full">
5960
<TextEditor
60-
dispatch={dispatch as any}
61-
state={state.editor}
6261
chapterid={state.chapter.chapterid}
6362
saved={state.saved}
6463
onSave={() => onSave(state)}

0 commit comments

Comments
 (0)