I'm working on a Todo App, I'd like to replace a particular div element (when a button is clicked) with a form component.
There’s this particular component that renders all my todos. on each todo item it renders, there’s a description and two buttons. one of the buttons is for deleting the particular todo item, the other is for updating it. I don’t have an issue deleting a particular todo. where i have an issue is updating a particular one. When I click the update button, it’s supposed to replace that particular todo item with a custom form component. then I can click update on that form and it’ll update, in theory. Tthe actual problem now is that when I click the update button on a particular todo item, it replaces all the todo items w the custom form component.
const Todos: React.FC<Props> = props => {
const ctx = useContext(TodoContext);
const updateForm = <TodoUpdateForm />;
const editTodo = (value: Todo) => {
ctx?.setClicked(true);
const updatedTodo = ctx?.todos.filter(todo => todo.id === value.id);
console.log(updatedTodo);
};
const completeTodo = (todo: string) => ctx?.completeTodo(todo);
const removeTodo = (todo: string) => ctx?.removeTodo(todo);
return (
<div>
<AnimatePresence>
{ctx?.todos.map((todo, index) => {
return (
<motion.div
key={index}
whileInView={{ x: [-100, 0], opacity: [0, 1] }}
exit={{ x: [0, 20], opacity: [1, 0] }}
transition={{
duration: 1,
ease: 'easeInOut',
delayChildren: 0.5,
}}
className={`${
todo.isComplete ? 'todo-row complete' : 'todo-row'
} app__flex`}
>
{ctx.isClicked ? (
updateForm
) : (
<div key={todo.id} className="todos-item">
<div
key={todo.id}
onClick={() => completeTodo(todo.id)}
className="app__todo-list-item"
>
{todo.text}
</div>
<motion.div
whileInView={{ opacity: [0, 1] }}
transition={{
duration: 2,
ease: 'easeInOut',
delayChildren: 0.5,
}}
className="icons app__flex"
>
<RiCloseCircleLine
className="delete-icon"
onClick={() => removeTodo(todo.id)}
/>
<TiEdit
className="edit-icon"
onClick={() => editTodo(todo)}
/>
</motion.div>
</div>
)}
</motion.div>
);
})}
</AnimatePresence>
</div>
);
};
export default Todos;
the Context Provider
export const TodoContext = React.createContext<TodoContextObj | null>({
todos: [],
edit: { id: '', value: '' },
isClicked: false,
addTodo: (todo: Todo) => {},
removeTodo: (id: string) => {},
completeTodo: (id: string) => {},
updateTodo: (id: string, todo: Todo) => {},
deleteTodos: (key: string) => {},
setClicked: (state: boolean) => {},
});
type Props = { children: React.ReactNode };
const initialValue: InitialState = { id: null, value: '' };
const ContextProvider: React.FC<Props> = props => {
const [todos, setTodos] = useState<Array<Todo>>([]);
const [edit, setEdit] = useState(initialValue);
const [isClicked, setIsClicked] = useState(false);
useEffect(() => {
const data = localStorage.getItem('todos');
if (data) setTodos(JSON.parse(data));
return () => {};
}, []);
const setClicked = (state: boolean) => setIsClicked(state);
const addTodo = (todo: Todo) => {
// Guard Clause
if (!todo.text || /^\s*$/.test(todo.text)) return;
const newTodos = [...todos, todo];
setTodos(newTodos);
setIsClicked(false);
localStorage.setItem('todos', JSON.stringify(newTodos));
};
const updateTodo = (id: string, value: Todo) => {
// Guard Clause
if (!value.text || /^\s*$/.test(value.text)) return;
if (edit.id) setEdit({ id: value.id, value: value.text });
localStorage.removeItem('todos');
setTodos(prev => prev.map(item => (item.id === id ? value : item)));
setIsClicked(false);
localStorage.setItem('todos', JSON.stringify(todos));
};
const removeTodo = (id: string) => {
const updatedTodo = [...todos].filter(todo => todo.id !== id);
localStorage.removeItem('todos');
setTodos(updatedTodo);
localStorage.setItem('todos', JSON.stringify(updatedTodo));
};
const completeTodo = (id: string) => {
let updateTodos = todos.map(todo => {
if (todo.id === id) todo.isComplete = !todo.isComplete;
return todo;
});
setTodos(updateTodos);
};
const deleteTodos = (key: string) => {
localStorage.removeItem(key);
setTodos([]);
};
const contextValue: TodoContextObj = {
todos,
isClicked,
addTodo,
removeTodo,
completeTodo,
updateTodo,
deleteTodos,
setClicked,
};
return (
<TodoContext.Provider value={contextValue}>
{props.children}
</TodoContext.Provider>
);
};
export default ContextProvider;
CodePudding user response:
You get this pattern a lot. The simplest solution is just to make a component that represents a single Todo. You can then give that component the editing state.
CodePudding user response:
You are setting a simple bool flag to say if you are in edit mode. This is not enough info for the component to know which todo is in edit mode. You need to change setIsClicked
(which i recommend you rename to setEditTodoId
or something) to accept todo.id
then set this string all the way down. You can type it so null is also allowed.
Then, in each loop, you will check if the current todo.id
matches the stored id. If it does, you'd render the form.