I’m making a todo app using react and firebase. I followed a tutorial from youtube to write the code.
Now I’m trying to add firebase. I’m using a useEffect
hook to fetch data from firebase. It works on first render, but if I use other functionality like read, update it doesn’t re-render. On refresh I get the result I wanted. If I add todos
as a dependency to the useEffect
, it works correctly but their are too many api calls.
App.jsx
function App() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
console.log(input);
// Create todo
const createTodo = async (e) => {
e.preventDefault(e);
if (input === "") {
alert("Please enter input");
return;
}
await addDoc(collection(db, "react-todo"), {
text: input,
complete: false,
});
setInput("");
};
// Read todo from firebase
useEffect(() => {
const getTodos = async () => {
const data = await getDocs(collection(db, "react-todo"));
let todoArray = [];
data.forEach((doc) => {
todoArray.push({ ...doc.data(), id: doc.id });
});
setTodos(todoArray);
};
getTodos();
}, []);
// update todo in firebase
const toggleComplete = async (todo) => {
await updateDoc(doc(db, "react-todo", todo.id), {
complete: !todo.complete,
});
};
// delete todo
const deleteTodo = async (todo) => {
await deleteDoc(doc(db, "react-todo", todo.id));
};
return (
<div className={style.bg}>
<div className={style.container}>
<h3 className={style.heading}>Todo App</h3>
<form onSubmit={createTodo} className={style.form}>
<input
type="text"
placeholder="Add Todo"
className={style.input}
value={input}
onChangeCapture={(e) => setInput(e.target.value)}
/>
<button className={style.button}>
<AiOutlinePlus size={30} />
</button>
</form>
<ul>
{todos.map((todo, index) => (
<Todo
key={index}
todo={todo}
toggleComplete={toggleComplete}
deleteTodo={deleteTodo}
/>
))}
</ul>
<p className={style.count}>{`You have ${todos.length} no. of todos`}</p>
</div>
</div>
);
}
Todo.jsx
function Todo({ todo, toggleComplete, deleteTodo }) {
return (
<li className={todo.complete ? style.liComplete : style.li}>
<div className={style.row}>
<input
onChange={() => toggleComplete(todo)}
type="checkbox"
checked={todo.complete ? "checked" : ""}
/>
<p
onClick={() => toggleComplete(todo)}
className={todo.complete ? style.textComplete : style.text}
>
{todo.text}
</p>
</div>
<button onClick={() => deleteTodo(todo)}>{<FaRegTrashAlt />}</button>
</li>
);
}
2
Answers
If you want to avoid an API call on every action so you should update your
todos
locally manipulating data you’ve changed:Only make sure your data was saved successfully and now you can just update what user see locally. Hope this helps
Adding
todos
to your dependency array will create an infinite loop and is not the way to do it. As you said "there are too many api calls. This is becausesetTodos
creates a new array each time it’s called, which then causes theuseEffect
to run…which then callssetTodos
, and so on.Even though you call
getTodos
on first mount, react doesn’t have any way of knowing that your database has updated when you call functions likedeleteTodo
. You need to manually refetch the data after each call. Something like this should work:App.jsx