Skip to content
This repository was archived by the owner on May 1, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
{
"name": "swpp-p3-react-tutorial",
"version": "0.1.0",
"private": true,
"private": false,
"proxy": "http://127.0.0.1:8000",
"dependencies": {
"@reduxjs/toolkit": "^1.8.5",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.3.0",
"@testing-library/user-event": "13.5.0",
"@types/jest": "27.5.2",
"@types/node": "16.11.56",
"@types/react": "18.0.17",
"@types/react-dom": "18.0.6",
"axios": "^0.27.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.4",
"react-router": "6.3.0",
"react-router-dom": "6.3.0",
"react-scripts": "5.0.1",
"redux": "^4.2.0",
"typescript": "4.7.4",
"web-vitals": "2.1.4"
},
Expand Down
27 changes: 27 additions & 0 deletions redux-basics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { configureStore } = require("@reduxjs/toolkit"); // load module in Node.js
const initialState = { number: 0 }; // default state

// create identity reducer
const reducer = (state = initialState, action) => {
if (action.type === "ADD") {
return { ...state, number: state.number + 1 };
} else if (action.type === "ADD_VALUE") {
return {
...state,
number: state.number + action.value,
};
}
return state;
};

// create redux store
const store = configureStore({ reducer: reducer });

store.subscribe(() => {
console.log("[Subscription]", store.getState());
});

store.dispatch({ type: "ADD" });
store.dispatch({ type: "ADD_VALUE", value: 5 });

console.log(store.getState());
13 changes: 11 additions & 2 deletions src/components/Todo/Todo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,26 @@ import "./Todo.css";

interface IProps {
title: string;
clicked?: React.MouseEventHandler<HTMLDivElement>; // Defined by React
clickDetail?: React.MouseEventHandler<HTMLDivElement>; // Defined by React
clickDone?: () => void;
clickDelete?: () => void;
done: boolean;
}

const Todo = (props: IProps) => {
return (
<div className="Todo">
<div className={`text ${props.done && "done"}`} onClick={props.clicked}>
<div
className={`text ${props.done && "done"}`}
onClick={props.clickDetail}
>
{props.title}
</div>
{props.done && <div className="done-mark">&#x2713;</div>}
<button onClick={props.clickDone}>
{props.done ? "Undone" : "Done"}
</button>
<button onClick={props.clickDelete}>Delete</button>
</div>
);
};
Expand Down
23 changes: 16 additions & 7 deletions src/components/TodoDetail/TodoDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useParams } from "react-router";
import "./TodoDetail.css";
import { AppDispatch } from "../../store";
import { fetchTodo, selectTodo } from "../../store/slices/todo";

type Props = {
title: string;
content: string;
};
const TodoDetail = () => {
const { id } = useParams();
const dispatch = useDispatch<AppDispatch>();
const todoState = useSelector(selectTodo);

useEffect(() => {
dispatch(fetchTodo(Number(id)));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);

const TodoDetail = (props: Props) => {
return (
<div className="TodoDetail">
<div className="row">
<div className="left">Name:</div>
<div className="right">{props.title}</div>
<div className="right">{todoState.selectedTodo?.title}</div>
</div>
<div className="row">
<div className="left">Content:</div>
<div className="right">{props.content}</div>
<div className="right">{todoState.selectedTodo?.content}</div>
</div>
</div>
);
Expand Down
16 changes: 12 additions & 4 deletions src/containers/TodoList/NewTodo/NewTodo.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { useState } from "react";
import { useDispatch } from "react-redux";
import { Navigate } from "react-router-dom";
import { AppDispatch } from "../../../store";
import { postTodo } from "../../../store/slices/todo";
// import { useNavigate } from "react-router-dom";
import "./NewTodo.css";

export default function NewTodo() {
const [title, setTitle] = useState<string>("");
const [content, setContent] = useState<string>("");
const [submitted, setSubmitted] = useState<boolean>(false);
const dispatch = useDispatch<AppDispatch>();

// const navigate = useNavigate()
// const postTodoHandler = () => {
Expand All @@ -16,10 +20,14 @@ export default function NewTodo() {
// navigate('/todos')
// };

const postTodoHandler = () => {
const postTodoHandler = async () => {
const data = { title: title, content: content };
alert("Submitted\n" + data.title + "\n" + data.content);
setSubmitted(true);
const result = await dispatch(postTodo(data));
if (result.payload) {
setSubmitted(true);
} else {
alert("Error on post Todo");
}
};

if (submitted) {
Expand All @@ -40,7 +48,7 @@ export default function NewTodo() {
value={content}
onChange={(event) => setContent(event.target.value)}
/>
<button onClick={() => postTodoHandler()}>Submit</button>
<button onClick={postTodoHandler}>Submit</button>
</div>
);
}
Expand Down
49 changes: 25 additions & 24 deletions src/containers/TodoList/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { useMemo, useState } from "react";
import { NavLink } from "react-router-dom";
import Todo from "../../components/Todo/Todo";
import TodoDetail from "../../components/TodoDetail/TodoDetail";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { NavLink, useNavigate } from "react-router-dom";
import "./TodoList.css";
import Todo from "../../components/Todo/Todo";
import {
deleteTodo,
fetchTodos,
selectTodo,
toggleDone,
} from "../../store/slices/todo";
import { AppDispatch } from "../../store";

interface IProps {
title: string;
Expand All @@ -11,45 +18,39 @@ interface IProps {
type TodoType = { id: number; title: string; content: string; done: boolean };

export default function TodoList(props: IProps) {
const navigate = useNavigate();
const { title } = props;
const [selectedTodo, setSelectedTodo] = useState<TodoType | null>(null);

const [todos, setTodos] = useState<TodoType[]>([
{ id: 1, title: "SWPP", content: "take swpp class", done: true },
{ id: 2, title: "Movie", content: "watch movie", done: false },
{ id: 3, title: "Dinner", content: "eat dinner", done: false },
]);
const todoState = useSelector(selectTodo);

const clickTodoHandler = (td: TodoType) => {
if (selectedTodo === td) {
setSelectedTodo(null);
} else {
setSelectedTodo(td);
}
navigate("/todos/" + td.id);
};

const todoDetail = useMemo(() => {
return selectedTodo ? (
<TodoDetail title={selectedTodo.title} content={selectedTodo.content} />
) : null;
}, [selectedTodo]);
const dispatch = useDispatch<AppDispatch>();

useEffect(() => {
dispatch(fetchTodos());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<div className="TodoList">
<div className="title">{title}</div>
<div className="todos">
{todos.map((td) => {
{todoState.todos.map((td) => {
return (
<Todo
key={`${td.id}_todo`}
title={td.title}
done={td.done}
clicked={() => clickTodoHandler(td)}
clickDetail={() => clickTodoHandler(td)}
clickDone={() => dispatch(toggleDone(td.id))}
clickDelete={() => dispatch(deleteTodo(td.id))}
/>
);
})}
{todoDetail}
<NavLink to="/new-todo" >New Todo</NavLink>
<NavLink to="/new-todo">New Todo</NavLink>
</div>
</div>
);
Expand Down
16 changes: 10 additions & 6 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import { store } from "./store";

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<App />
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
9 changes: 9 additions & 0 deletions src/store/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { configureStore } from "@reduxjs/toolkit";
import todoReducer from "./slices/todo";

export const store = configureStore({
reducer: { todo: todoReducer },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Loading