Reputation: 155
I have a very basic todo list with add todo, toggle complete and delete functionality. I want to be able to have infinitely nested todos. I managed to figure out how to display nested todos through recursion but I can't figure out how to manipulate the nested elements recursively.
When I interact with an element I get the correct id back to the functions in the main component but I also need the id of the parent. I was able to manipulate data one layer deep by making new functions that took two parameters instead of one, the parent id and the target id but of course this defeats the purpose of having recursive nesting. I have also considered giving up the recursive approach and changing to a flat data structure where the nested elements have a reference to the parent instead.
Here is a simplified version of the code:
import React, { useState } from 'react';
const data = {
todos: [
{
id: 'a',
text: 'go shopping',
subTodos: [
{
id: 'aa',
text: 'buy fruit',
subTodos: [
{
id: 'aaa',
text: 'oranges',
subTodos: [],
},
],
},
],
},
{
id: 'b',
text: 'learn recursion',
subTodos: []
},
],
};
const Todos = () => {
const [todos, setTodos] = useState(data);
const handleDelete = (id) => {
const newTodoArray = todos.todos.filter(todo => (todo.id !== id))
setTodos(prev => ({...prev, todos: newTodoArray}))
}
return (
<div>
{todos.todos.map(todo => (
<Todo
key={todo.id}
todo={todo}
handleDelete={handleDelete}/>
))}
</div>
);
}
const Todo = ({todo, handleDelete}) => {
return (
<div key={todo.id} style={{paddingLeft: '20px'}}>
<div style={{display: 'flex'}}>
<div>{todo.text}</div>
<button onClick={() => handleDelete(todo.id)}>Delete</button>
</div>
<div>
{todo.subTodos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
handleDelete={handleDelete}>
</Todo>))
}
</div>
</div>
)
}
export default Todos;
I would appreciate any advice regarding my approach or any resources that could be of help.
Upvotes: 4
Views: 1363
Reputation: 370759
I think the right way to handle this would be to put each sublist from props into state of the sub-todo component. When a todo needs to be deleted, tell the parent todo (or the parent Todos
, in the case of the top level) to delete the item from state. This way, each todo handles its children's state, and when a click occurs, you don't have to recursively iterate all the way back to the parent (which would make for some tedious nested object traversal when you need to set state again).
const Todo = ({ todo, handleDelete }) => {
const [subTodos, setSubTodos] = React.useState(todo.subTodos);
const handleSubTodoDelete = (id) => {
setSubTodos(subTodos.filter(todo => todo.id !== id));
}
return (
<div key={todo.id} style={{ paddingLeft: '20px' }}>
<div style={{ display: 'flex' }}>
<div>{todo.text}</div>
<button onClick={() => handleDelete(todo.id)}>Delete</button>
</div>
<div>
{subTodos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
handleDelete={handleSubTodoDelete}>
</Todo>))
Here's a live snippet:
const data = {
todos: [
{
id: 'a',
text: 'go shopping',
subTodos: [
{
id: 'aa',
text: 'buy fruit',
subTodos: [
{
id: 'aaa',
text: 'oranges',
subTodos: [],
},
],
},
],
},
{
id: 'b',
text: 'learn recursion',
subTodos: []
},
],
};
const Todos = () => {
const [todos, setTodos] = React.useState(data);
const handleDelete = (id) => {
const newTodoArray = todos.todos.filter(todo => (todo.id !== id))
setTodos(prev => ({ ...prev, todos: newTodoArray }))
}
return (
<div>
{todos.todos.map(todo => (
<Todo
key={todo.id}
todo={todo}
handleDelete={handleDelete} />
))}
</div>
);
}
const Todo = ({ todo, handleDelete }) => {
const [subTodos, setSubTodos] = React.useState(todo.subTodos);
const handleSubTodoDelete = (id) => {
setSubTodos(subTodos.filter(todo => todo.id !== id));
}
return (
<div key={todo.id} style={{ paddingLeft: '20px' }}>
<div style={{ display: 'flex' }}>
<div>{todo.text}</div>
<button onClick={() => handleDelete(todo.id)}>Delete</button>
</div>
<div>
{subTodos.map((todo) => (
<Todo
key={todo.id}
todo={todo}
handleDelete={handleSubTodoDelete}>
</Todo>))
}
</div>
</div>
)
}
ReactDOM.render(<Todos />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class="react"></div>
The handleDelete
prop is created in the parent and passed down as a prop. In each child, a handleSubTodoDelete
function is created and passed down to manage that child's state.
Upvotes: 2