Skip to content

Commit 43fbe40

Browse files
John DoeGitHub Actions bot
authored andcommitted
Implement todos list
1 parent 797d4f6 commit 43fbe40

File tree

9 files changed

+194
-11
lines changed

9 files changed

+194
-11
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
{
22
"name": "todo-app",
33
"scripts": {
4-
"start": "esbuild src/app.jsx --bundle --outdir=www/js --servedir=www",
5-
"build": "esbuild src/app.jsx --bundle --outdir=www/js --minify"
4+
"start": "esbuild src/index.jsx --bundle --outdir=www/js --servedir=www",
5+
"build": "esbuild src/index.jsx --bundle --outdir=www/js --minify"
66
},
77
"dependencies": {
8+
"moment": "^2.29.4",
89
"react": "^18.2.0",
910
"react-dom": "^18.2.0"
1011
},

src/App.jsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import CreateTodo from './components/CreateTodo';
3+
import TodoFilter from './components/TodoFilter';
4+
import TodoList from './components/TodoList';
5+
import { useTodos } from './hooks/useTodos';
6+
7+
const App = () => {
8+
const { loading, todos, onCreate, onEdit, setQuery, setHideComplete } =
9+
useTodos();
10+
11+
return (
12+
<div>
13+
<h1>TODOs</h1>
14+
<TodoFilter setQuery={setQuery} setHideComplete={setHideComplete} />
15+
<TodoList todos={todos} onEdit={onEdit} />
16+
<CreateTodo onCreate={onCreate} />
17+
</div>
18+
);
19+
};
20+
21+
export default App;

src/app.jsx

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/components/CreateTodo.jsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { useState } from 'react';
2+
3+
const CreateTodo = props => {
4+
const [title, setTitle] = useState('');
5+
6+
return (
7+
<form
8+
style={{
9+
position: 'fixed',
10+
bottom: 0,
11+
right: 0,
12+
}}
13+
onSubmit={event => {
14+
event.preventDefault();
15+
props.onCreate(title);
16+
setTitle('');
17+
}}
18+
>
19+
<input
20+
value={title}
21+
onInput={event => {
22+
setTitle(event.target.value);
23+
}}
24+
/>
25+
<button>Add</button>
26+
</form>
27+
);
28+
};
29+
30+
export default CreateTodo;

src/components/TodoFilter.jsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
3+
const TodoFilter = props => {
4+
return (
5+
<div>
6+
<input
7+
type="search"
8+
placeholder="Search"
9+
onInput={event => {
10+
props.setQuery(event.target.value);
11+
}}
12+
/>
13+
14+
<label>
15+
<input
16+
type="checkbox"
17+
onChange={event => {
18+
props.setHideComplete(event.target.checked);
19+
}}
20+
/>
21+
Hide complete
22+
</label>
23+
</div>
24+
);
25+
};
26+
27+
export default TodoFilter;

src/components/TodoList.jsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import moment from 'moment';
2+
import React from 'react';
3+
4+
const TodoList = props => (
5+
<ul>
6+
{props.todos.map(todo => (
7+
<li>
8+
<label>
9+
<input
10+
type="checkbox"
11+
checked={todo.complete}
12+
onChange={event => {
13+
props.onEdit({
14+
...todo,
15+
complete: event.target.checked,
16+
});
17+
}}
18+
/>
19+
<span
20+
style={{
21+
textDecoration: todo.complete ? 'line-through' : 'none',
22+
}}
23+
>
24+
{todo.title}
25+
</span>
26+
{todo.dueDate && <b> - due {moment(todo.dueDate).fromNow()}</b>}
27+
</label>
28+
</li>
29+
))}
30+
</ul>
31+
);
32+
33+
export default TodoList;

src/hooks/useTodos.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { useCallback, useEffect, useMemo, useState } from 'react';
2+
3+
export const useTodos = () => {
4+
const [loading, setLoading] = useState(false);
5+
const [data, setData] = useState([]);
6+
7+
useEffect(() => {
8+
setLoading(true);
9+
fetch('https://jsonplaceholder.typicode.com/todos')
10+
.then(resp => resp.json())
11+
.then(data => {
12+
setData(data);
13+
setLoading(false);
14+
});
15+
}, []);
16+
17+
const onCreate = useCallback(title => {
18+
const body = JSON.stringify({
19+
title: title,
20+
complete: false,
21+
});
22+
23+
fetch('https://jsonplaceholder.typicode.com/todos', {
24+
method: 'POST',
25+
body,
26+
})
27+
.then(resp => resp.json())
28+
.then(({ id }) => {
29+
setData(data => [
30+
...data,
31+
{
32+
id: id,
33+
title: title,
34+
complete: false,
35+
},
36+
]);
37+
});
38+
});
39+
40+
const onEdit = useCallback(todo => {
41+
setData(data => data.map(t => (t.id == todo.id ? todo : t)));
42+
fetch(`https://jsonplaceholder.typicode.com/todos/${todo.id}`, {
43+
method: 'PUT',
44+
body: JSON.stringify(todo),
45+
});
46+
});
47+
48+
const [query, setQuery] = useState('');
49+
const [hideComplete, setHideComplete] = useState(false);
50+
51+
const todos = useMemo(
52+
() =>
53+
data.filter(todo => {
54+
if (query && !todo.title.toLowerCase().includes(query.toLowerCase())) {
55+
return false;
56+
}
57+
if (hideComplete && todo.complete) {
58+
return false;
59+
}
60+
return true;
61+
}),
62+
[data, query, hideComplete]
63+
);
64+
65+
return {
66+
loading,
67+
todos,
68+
onCreate,
69+
onEdit,
70+
setQuery,
71+
setHideComplete,
72+
};
73+
};

src/index.jsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import React from 'react';
2+
import { createRoot } from 'react-dom/client';
3+
import App from './App';
4+
5+
let root = createRoot(document.querySelector('#root'));
6+
root.render(<App />);

www/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
<html>
33
<body>
44
<div id="root" />
5-
<script src="js/app.js"></script>
5+
<script src="js/index.js"></script>
66
</body>
77
</html>

0 commit comments

Comments
 (0)