From ab8e0b23cefa6e95d5ccba5e856c7be77ea3a695 Mon Sep 17 00:00:00 2001 From: "NITECO\\thanh.bui2" Date: Fri, 4 Oct 2024 17:22:30 +0700 Subject: [PATCH 1/7] feat: add input search function --- src/components/Input/index.tsx | 79 +++++++++++++++++++++++++++++++-- src/components/Input/input.scss | 42 ++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 453c742..e3a7c71 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -2,22 +2,93 @@ import "./input.scss"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; +import { ChangeEvent, useCallback, useRef, useState } from "react"; export interface InputProps { /** Placeholder of the input */ placeholder?: string; /** On click item handler */ onSelectItem: (item: string) => void; + /** debounce time */ + debounceTime?: number; } -const Input = ({ placeholder, onSelectItem }: InputProps) => { +const Input = ({ placeholder, onSelectItem, debounceTime = 100 }: InputProps) => { // DO NOT remove this log - console.log('input re-render') + console.log("input re-render"); + + const inputRef = useRef(null); + const [tempValue, setTempValue] = useState(""); + const [isSearching, setIsSearching] = useState(false); + const [searchData, setSearchData] = useState([]); + + const handleCallAPI = useCallback(async (searchTerm: string) => { + if (!searchTerm) return; + setIsSearching(true); + try { + const response = await fetchData(searchTerm); + console.log(response); + setSearchData(response); + } catch (error) { + console.log("error when searching", error); + } finally { + setIsSearching(false); + } + }, []); + + const renderResultBlock = (searchData: string[]) => { + return ( + <> + {!!searchData?.length ? ( +
+ {searchData.map((item, index) => { + return ( +
+ ) => { + e.stopPropagation; + onSelectItem(item); + }} + > + {item} +
+ ); + })} +
+ ) : ( +
+ No data matching your search. Pleas try again! +
+ )} + + ); + }; // Your code start here - return + return ( +
+ ) => { + e.preventDefault(); + const searchTerm = e.target?.value; + setTempValue(searchTerm); + handleCallAPI(searchTerm || ""); + }, debounceTime)} + className={"input-search-component__input-search"} + /> + {!!tempValue && ( +
+ {isSearching ? : renderResultBlock(searchData)} +
+ )} +
+ ); // Your code end here }; export default Input; - diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index 1dafbe7..edbe97b 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -4,3 +4,45 @@ html{ font-size: 16px; } + +.input-search-component { + position: relative; + + &__input-search { + height: 48px; + border: 1px solid #333; + border-radius: 5px; + font-size: 16px; + font-weight: 500; + padding-inline: 8px; + width: 300px; + } + + &__result-block { + position: absolute; + top: 56px; + left: 0; + border-radius: 5px; + width: 100%; + min-height: 80px; + max-height: 300px; + overflow: auto; + + &--no-data { + text-align: center; + } + + &--has-data { + border: 1px solid #999; + } + } + + &__result-item { + padding: 12px 16px; + cursor: pointer; + + &:hover { + background-color: #ddd; + } + } +} From e5dbbed809413a2be162ae4be8b6247cc245c7e2 Mon Sep 17 00:00:00 2001 From: "NITECO\\thanh.bui2" Date: Mon, 7 Oct 2024 17:53:54 +0700 Subject: [PATCH 2/7] Remove temp debounce time --- src/components/Input/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index e3a7c71..2d2157a 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -9,15 +9,12 @@ export interface InputProps { placeholder?: string; /** On click item handler */ onSelectItem: (item: string) => void; - /** debounce time */ - debounceTime?: number; } -const Input = ({ placeholder, onSelectItem, debounceTime = 100 }: InputProps) => { +const Input = ({ placeholder, onSelectItem}: InputProps) => { // DO NOT remove this log console.log("input re-render"); - const inputRef = useRef(null); const [tempValue, setTempValue] = useState(""); const [isSearching, setIsSearching] = useState(false); const [searchData, setSearchData] = useState([]); @@ -71,14 +68,13 @@ const Input = ({ placeholder, onSelectItem, debounceTime = 100 }: InputProps) => return (
) => { e.preventDefault(); const searchTerm = e.target?.value; setTempValue(searchTerm); handleCallAPI(searchTerm || ""); - }, debounceTime)} + }, 300)} className={"input-search-component__input-search"} /> {!!tempValue && ( From 813e926714e68a715bf39c6f0f28dc61b3e52da8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NC3R0V8\\thanh" Date: Sun, 20 Oct 2024 23:03:15 +0700 Subject: [PATCH 3/7] First version todoMVC --- src/components/Input/index.tsx | 6 +- src/components/TodoMVC/index.tsx | 286 ++++++++++++++++++++++++++++ src/components/TodoMVC/todoMVC.scss | 127 ++++++++++++ src/stories/TodoMVC.stories.ts | 27 +++ 4 files changed, 443 insertions(+), 3 deletions(-) create mode 100644 src/components/TodoMVC/index.tsx create mode 100644 src/components/TodoMVC/todoMVC.scss create mode 100644 src/stories/TodoMVC.stories.ts diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 2d2157a..526a1f5 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -11,7 +11,7 @@ export interface InputProps { onSelectItem: (item: string) => void; } -const Input = ({ placeholder, onSelectItem}: InputProps) => { +const Input = ({ placeholder, onSelectItem }: InputProps) => { // DO NOT remove this log console.log("input re-render"); @@ -36,7 +36,7 @@ const Input = ({ placeholder, onSelectItem}: InputProps) => { const renderResultBlock = (searchData: string[]) => { return ( <> - {!!searchData?.length ? ( + {searchData?.length ? (
{searchData.map((item, index) => { return ( @@ -46,7 +46,7 @@ const Input = ({ placeholder, onSelectItem}: InputProps) => { onClick={( e: React.MouseEvent ) => { - e.stopPropagation; + e.stopPropagation(); onSelectItem(item); }} > diff --git a/src/components/TodoMVC/index.tsx b/src/components/TodoMVC/index.tsx new file mode 100644 index 0000000..de6e62f --- /dev/null +++ b/src/components/TodoMVC/index.tsx @@ -0,0 +1,286 @@ +import "./todoMVC.scss"; +import { useEffect, useState } from "react"; + +export interface InputProps { + /** Placeholder of the input */ + placeholder?: string; +} + +export interface ITodoListProps { + value: string; + isCompleted: boolean; + id: number; +} + +const TodoMVC = () => { + // DO NOT remove this log + console.log("input re-render"); + + const [todoList, setTodoList] = useState([]); + const [todoCompletedList, setTodoCompletedList] = useState( + [] + ); + const [todoInCompleteList, setTodoInCompleteList] = useState< + ITodoListProps[] + >([]); + const [inputValue, setInputValue] = useState(""); + const [todoListToShow, setTodoListToShow] = useState("All"); + const [isCheckedAll, setIsCheckedAll] = useState( + todoList.some((todo) => todo.isCompleted) + ); + + const handleInput = ( + e: + | React.ChangeEvent + | React.KeyboardEvent + | React.BaseSyntheticEvent + ) => { + if (e.type === "change") { + setInputValue(e.target.value); + } + + if (e.type === "keydown" && (e as React.KeyboardEvent).key === "Enter") { + console.log("Add todo", inputValue); + const preparedTodoTask = { + isCompleted: false, + value: e?.target?.value, + id: Math.floor(Math.random() * 1000) + 1, + }; + setTodoList((prevState) => [...prevState, preparedTodoTask]); + setInputValue(""); + } + }; + + const onChangingTodoTaskStatus = (id: number) => { + if (!todoList?.length) return; + setTodoList((prevState) => + prevState.map((todo) => + todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo + ) + ); + }; + + const onRemovingTodoTask = (id: number) => { + if (!todoList?.length) return; + setTodoList((prevState) => prevState.filter((todo) => todo.id !== id)); + }; + + const onClearingCompleteTask = () => { + setTodoList((prevState) => prevState.filter((todo) => !todo.isCompleted)); + }; + + const onFilteringTodoTask = (type = "All") => { + console.log(type); + + switch (type) { + case "Active": + setTodoListToShow("Active"); + break; + case "Completed": + setTodoListToShow("Completed"); + break; + + default: + setTodoListToShow("All"); + break; + } + }; + + const onMarkDoneAllTasks = () => { + setIsCheckedAll(!isCheckedAll); + setTodoList((prevState) => + prevState.map((todo) => { + return { ...todo, isCompleted: isCheckedAll }; + }) + ); + }; + + useEffect(() => { + if (!todoList?.length) return; + setTodoCompletedList(todoList.filter((todo) => todo.isCompleted)); + setTodoInCompleteList(todoList.filter((todo) => !todo.isCompleted)); + }, [todoList]); + + // Your code start here + return ( +
+
+
{ + onMarkDoneAllTasks(); + }} + > + ✅ +
+ +
+ {!!todoList?.length && ( +
+ {todoListToShow === "All" && + todoList.map((item) => { + return ( +
+ + + +
+ ); + })} + {todoListToShow === "Active" && + todoInCompleteList.map((item) => { + return ( +
+ + + +
+ ); + })} + {todoListToShow === "Completed" && + todoCompletedList.map((item) => { + return ( +
+ + + +
+ ); + })} +
+
+ {todoInCompleteList?.length || 0} item + {todoInCompleteList.length > 1 ? "s" : ""} left! +
+
+
{ + onFilteringTodoTask("All"); + }} + > + All +
+
{ + onFilteringTodoTask("Active"); + }} + > + Active +
+
{ + onFilteringTodoTask("Completed"); + }} + > + Completed +
+
+
+ Clear completed +
+
+
+ )} +
+ ); + // Your code end here +}; + +export default TodoMVC; diff --git a/src/components/TodoMVC/todoMVC.scss b/src/components/TodoMVC/todoMVC.scss new file mode 100644 index 0000000..49a1844 --- /dev/null +++ b/src/components/TodoMVC/todoMVC.scss @@ -0,0 +1,127 @@ +* { + box-sizing: border-box; +} +html { + font: 14px Helvetica Neue, Helvetica, Arial, sans-serif; +} + +@mixin flexbox($direction: row, $justify: center, $align: center) { + display: flex; + direction: $direction; + justify-content: $justify; + align-items: $align; +} + +.todo-mvc-component { + position: relative; + + &__input { + height: 48px; + border: 1px solid #333; + border-radius: 5px; + font-size: 16px; + font-weight: 500; + padding-left: 40px; + padding-right: 8px; + width: 400px; + + &--wrapper { + position: relative; + } + } + + &__tick-all-btn { + position: absolute; + width: 40px; + height: 40px; + left: 0; + top: 3px; + cursor: pointer; + @include flexbox(); + } + + &__todo-list { + position: absolute; + top: 56px; + left: 0; + border-radius: 5px; + width: 100%; + min-height: 40px; + max-height: 400px; + border: 1px solid #999; + } + + &__todo-item { + @include flexbox($align: center); + padding: 12px 16px; + gap: 8px; + border-bottom: 1px solid #ededed; + &:hover { + --set-todo-mvc-component__remove-btn--opacity: 1; + } + } + + &__checkbox-btn { + @include flexbox(); + background: none; + border: none; + padding: 0; + cursor: pointer; + + &__icon { + width: 40px; + height: 40px; + background-position: 0; + background-repeat: no-repeat; + + &--completed { + background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E"); + } + + &--not-yet { + background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E"); + } + } + } + + &__remove-btn { + @include flexbox(); + background: none; + border: none; + padding: 0; + margin-left: auto; + cursor: pointer; + opacity: var(--set-todo-mvc-component__remove-btn--opacity, 0); + } + + &__footer { + @include flexbox($justify: space-between); + padding: 12px 16px; + font-size: 12px; + } + + &__item-count { + padding-inline: 2px; + } + + &__filters { + @include flexbox(); + gap: 4px; + } + + &__filter { + padding: 2px 3px; + border-radius: 3px; + cursor: pointer; + border: 1px solid transparent; + + &:hover, + &.active { + border-color: #cb9295; + } + } + + &__clear-complete-btn { + cursor: pointer; + } +} diff --git a/src/stories/TodoMVC.stories.ts b/src/stories/TodoMVC.stories.ts new file mode 100644 index 0000000..d92259a --- /dev/null +++ b/src/stories/TodoMVC.stories.ts @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import TodoMVC from "../components/TodoMVC"; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: "Example/TodoMVC", + component: TodoMVC, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: "centered", + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ["autodocs"], + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + // args: { onClick: fn() }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Primary: Story = { + args: { + placeholder: "Type something to search...", + }, +}; From d3e85e5f7b165ca84e712fcf0f90095f4867837d Mon Sep 17 00:00:00 2001 From: "NITECO\\thanh.bui2" Date: Mon, 21 Oct 2024 18:48:15 +0700 Subject: [PATCH 4/7] Add Todo Task Input --- src/components/TodoMVC/TodoFilterOption.tsx | 30 +++ src/components/TodoMVC/TodoItem.tsx | 104 +++++++++++ src/components/TodoMVC/index.tsx | 195 +++++++------------- src/components/TodoMVC/todoMVC.scss | 9 + 4 files changed, 209 insertions(+), 129 deletions(-) create mode 100644 src/components/TodoMVC/TodoFilterOption.tsx create mode 100644 src/components/TodoMVC/TodoItem.tsx diff --git a/src/components/TodoMVC/TodoFilterOption.tsx b/src/components/TodoMVC/TodoFilterOption.tsx new file mode 100644 index 0000000..9f7ed1d --- /dev/null +++ b/src/components/TodoMVC/TodoFilterOption.tsx @@ -0,0 +1,30 @@ +import "./todoMVC.scss"; + +interface ITodoFilterOptionProps { + onFilteringTodoTask: (id: string) => void; + todoListToShow: string; + isActive: boolean; +} + +const TodoFilterOption = ({ + onFilteringTodoTask, + todoListToShow = '', + isActive = false, +}: ITodoFilterOptionProps) => { + console.log(todoListToShow); + + return ( +
{ + onFilteringTodoTask(todoListToShow); + }} + > + {todoListToShow} +
+ ); +}; + +export default TodoFilterOption; diff --git a/src/components/TodoMVC/TodoItem.tsx b/src/components/TodoMVC/TodoItem.tsx new file mode 100644 index 0000000..54f10a6 --- /dev/null +++ b/src/components/TodoMVC/TodoItem.tsx @@ -0,0 +1,104 @@ +import { useState } from "react"; +import { ITodoListProps } from "."; +import "./todoMVC.scss"; + +interface ITodoItemProps { + todoItem: ITodoListProps; + onChangingTodoTaskStatus: (id: number) => void; + onRemovingTodoTask: (id: number) => void; + onChangingTodoTaskValue: (id: number, value: string) => void; +} + +const TodoItem = ({ + todoItem, + onChangingTodoTaskStatus, + onRemovingTodoTask, + onChangingTodoTaskValue, +}: ITodoItemProps) => { + const [isDoubleClicked, setIsDoubleClicked] = useState(false); + + const handleOnDoubleClickTodoItem = (e: React.UIEvent) => { + if (e.detail === 2) { + console.log("double click"); + setIsDoubleClicked(true); + setTimeout(() => { + document.getElementById(`todo-label-${todoItem.id}`)?.focus(); + }, 300); + } + }; + + const handleOnBlurTodoItem = () => { + setIsDoubleClicked(false); + }; + + const handleChangeTodoItem = ( + e: + | React.ChangeEvent + | React.KeyboardEvent + | React.BaseSyntheticEvent + ) => { + if (e.type === "keydown" && (e as React.KeyboardEvent).key === "Enter") { + if (!e?.target?.value) return; + if (!onChangingTodoTaskValue) return; + + onChangingTodoTaskValue(todoItem.id, e?.target?.value); + setIsDoubleClicked(false); + } + }; + + return ( +
+ {!isDoubleClicked && ( + <> + + + + + )} + {isDoubleClicked && ( +
+ +
+ )} +
+ ); +}; + +export default TodoItem; diff --git a/src/components/TodoMVC/index.tsx b/src/components/TodoMVC/index.tsx index de6e62f..becc03c 100644 --- a/src/components/TodoMVC/index.tsx +++ b/src/components/TodoMVC/index.tsx @@ -1,3 +1,5 @@ +import TodoFilterOption from "./TodoFilterOption"; +import TodoItem from "./TodoItem"; import "./todoMVC.scss"; import { useEffect, useState } from "react"; @@ -12,6 +14,8 @@ export interface ITodoListProps { id: number; } +let isTheFirstHit = true; + const TodoMVC = () => { // DO NOT remove this log console.log("input re-render"); @@ -26,7 +30,7 @@ const TodoMVC = () => { const [inputValue, setInputValue] = useState(""); const [todoListToShow, setTodoListToShow] = useState("All"); const [isCheckedAll, setIsCheckedAll] = useState( - todoList.some((todo) => todo.isCompleted) + isTheFirstHit && !todoList.some((todo) => !todo.isCompleted) ); const handleInput = ( @@ -40,7 +44,7 @@ const TodoMVC = () => { } if (e.type === "keydown" && (e as React.KeyboardEvent).key === "Enter") { - console.log("Add todo", inputValue); + if (!e?.target?.value) return; const preparedTodoTask = { isCompleted: false, value: e?.target?.value, @@ -87,11 +91,31 @@ const TodoMVC = () => { }; const onMarkDoneAllTasks = () => { - setIsCheckedAll(!isCheckedAll); + if (isTheFirstHit) { + setIsCheckedAll(true); + setTodoList((prevState) => + prevState.map((todo) => { + return { ...todo, isCompleted: true }; + }) + ); + isTheFirstHit = false; + } else { + setIsCheckedAll(!isCheckedAll); + setTodoList((prevState) => + prevState.map((todo) => { + return { ...todo, isCompleted: !isCheckedAll }; + }) + ); + } + }; + + const onChangingTodoTaskValue = (id: number, value: string) => { + console.log(value); + setTodoList((prevState) => - prevState.map((todo) => { - return { ...todo, isCompleted: isCheckedAll }; - }) + prevState.map((todo) => + todo.id === id ? { ...todo, value: value } : todo + ) ); }; @@ -127,109 +151,37 @@ const TodoMVC = () => { {todoListToShow === "All" && todoList.map((item) => { return ( -
- - - -
+ ); })} {todoListToShow === "Active" && todoInCompleteList.map((item) => { return ( -
- - - -
+ ); })} {todoListToShow === "Completed" && todoCompletedList.map((item) => { return ( -
- - - -
+ ); })}
@@ -238,36 +190,21 @@ const TodoMVC = () => { {todoInCompleteList.length > 1 ? "s" : ""} left!
-
{ - onFilteringTodoTask("All"); - }} - > - All -
-
{ - onFilteringTodoTask("Active"); - }} - > - Active -
-
{ - onFilteringTodoTask("Completed"); - }} - > - Completed -
+ + +
Date: Mon, 21 Oct 2024 21:42:18 +0700 Subject: [PATCH 5/7] feat: Update save and load data from localstorage --- src/components/TodoMVC/TodoItem.tsx | 18 +++++++++++------- src/components/TodoMVC/index.tsx | 19 +++++++++++-------- src/components/TodoMVC/todoMVC.scss | 28 +++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/components/TodoMVC/TodoItem.tsx b/src/components/TodoMVC/TodoItem.tsx index 54f10a6..c632e86 100644 --- a/src/components/TodoMVC/TodoItem.tsx +++ b/src/components/TodoMVC/TodoItem.tsx @@ -47,12 +47,17 @@ const TodoItem = ({ }; return ( -
+
{!isDoubleClicked && ( <> -
Date: Mon, 21 Oct 2024 21:44:09 +0700 Subject: [PATCH 6/7] feat: remove redundant --- src/components/Input/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 526a1f5..575a21e 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -2,7 +2,7 @@ import "./input.scss"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; -import { ChangeEvent, useCallback, useRef, useState } from "react"; +import { ChangeEvent, useCallback, useState } from "react"; export interface InputProps { /** Placeholder of the input */ From fbd8d14a518dc73ed6f583f31cc0d8326c28ec39 Mon Sep 17 00:00:00 2001 From: "DESKTOP-NC3R0V8\\thanh" Date: Wed, 23 Oct 2024 00:10:10 +0700 Subject: [PATCH 7/7] Fix issue save to localstorage --- src/components/TodoMVC/index.tsx | 95 ++++++++++++++------------------ 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/src/components/TodoMVC/index.tsx b/src/components/TodoMVC/index.tsx index 7af0028..11ed58b 100644 --- a/src/components/TodoMVC/index.tsx +++ b/src/components/TodoMVC/index.tsx @@ -29,6 +29,7 @@ const TodoMVC = () => { const [isCheckedAll, setIsCheckedAll] = useState( isTheFirstHit && !todoList.some((todo) => !todo.isCompleted) ); + const listStatus = ["All", "Active", "Completed"]; const handleInput = ( e: @@ -114,13 +115,6 @@ const TodoMVC = () => { ); }; - useEffect(() => { - localStorage?.setItem("todoList", JSON.stringify(todoList)); - if (!todoList?.length) return; - setTodoCompletedList(todoList.filter((todo) => todo.isCompleted)); - setTodoInCompleteList(todoList.filter((todo) => !todo.isCompleted)); - }, [todoList]); - useEffect(() => { const itemTodoList = JSON.parse( localStorage.getItem("todoList") as string @@ -130,6 +124,13 @@ const TodoMVC = () => { } }, []); + useEffect(() => { + localStorage?.setItem("todoList", JSON.stringify(todoList)); + if (!todoList?.length) return; + setTodoCompletedList(todoList.filter((todo) => todo.isCompleted)); + setTodoInCompleteList(todoList.filter((todo) => !todo.isCompleted)); + }, [todoList]); + // Your code start here return (
@@ -151,40 +152,29 @@ const TodoMVC = () => {
{!!todoList?.length && (
- {todoListToShow === "All" && - todoList.map((item) => { - return ( - - ); - })} - {todoListToShow === "Active" && - todoInCompleteList.map((item) => { - return ( - - ); - })} - {todoListToShow === "Completed" && - todoCompletedList.map((item) => { + {!!listStatus?.length && + listStatus.map((status) => { + const isShow = todoListToShow === status; + const newTodoList = + todoListToShow === "All" + ? todoList + : todoListToShow === "Active" + ? todoInCompleteList + : todoCompletedList; return ( - + isShow && + !!newTodoList?.length && + newTodoList.map((item) => { + return ( + + ); + }) ); })}
@@ -193,21 +183,16 @@ const TodoMVC = () => { {todoInCompleteList.length > 1 ? "s" : ""} left!
- - - + {!!listStatus?.length && + listStatus.map((status) => { + return ( + + ); + })}