Search code examples
javascriptreactjsfirebaseuse-effectuse-state

React js useState&useEffect array duplicates elements after a change


I am a beginner in react js programming. I'm trying to do the todo project, which is a classic project. When I delete or add an element from the list, the newly formed list appears on the screen by combining with the previous one, I will show it with a picture below. I did not understand the source of the eror so wanted to post it here to get some advices suggestions about why it is happening.Thank you.(I am getting and storing data in firebase firestore database)

Before Adding an element initial array state enter image description here

After adding an element to the array. enter image description here

I am using useState for array and using useEffect to get initial data

MainPage.js that contains form and the list components.

const MainPage = () => {
  const [isLoading, setLoding] = useState(true);
  const [array, setArray] = useState([]);
  const sub = async (email) => {
    var result = [];
    await onSnapshot(doc(db, "users", email), (doc) => {
      var data = doc.data().todos;
      data.forEach((element) => {
        Object.keys(element).map(() => {
          result.push(element["title"]);
        });
      });
      setArray(result);
      setLoding(false);
    });
  };

  useEffect(() => {
    sub(auth.currentUser.email);
  }, []);

  const onAddToDo = (todoTitle) => {
    setArray((prevAray) => {
      return [...prevAray, todoTitle];
    });
  };
  const onRemove = (title) => {
    setArray((prevAray) => {
      return [array.pop(array.indexOf(title))];
    });
  };
  return (
    <div>
      {isLoading && <h1>Loading</h1>}
      {!isLoading && (
        <div>
          <section>
            <NavBar></NavBar>
            <ToDoForm passData={onAddToDo} />
          </section>
          <section>
            <CardList removeCards={onRemove} array={array} />
          </section>
        </div>
      )}
    </div>
  );
};

export default MainPage;

Firebase.js that stores the firebase update methods

export const deleteItem = (title) => {
  updateDoc(doc(db, "users", auth.currentUser.email), {
    todos: arrayRemove({ title: title }),
  });
};
export const addnewTodo = (title) => {
  updateDoc(doc(db, "users", auth.currentUser.email), {
    todos: arrayUnion({ title: title }),
  });
};

TodoForm.js component

const ToDoForm = (props) => {
  const [todoTitle, setTitle] = useState("");
  const titleChangeHandler = (event) => {
    setTitle(event.target.value);
  };
  const newTodoAdder = (event) => {
    event.preventDefault();
    addnewTodo(todoTitle);
    props.passData(todoTitle);
  };

  return (
    <div className="form_holder">
      <div className="form_container">
        <form onSubmit={newTodoAdder}>
          <h3>Add Events</h3>
          <label>Title</label>
          <input
            onChange={titleChangeHandler}
            type="text"
            placeholder="Title"
            id="title"
          ></input>
          <div className="holder">
            <button type="sumbit">Add</button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default ToDoForm;

CardList.js component

const CardList = (props) => {
  const array = props.array;
  if (array.length === 0) {
    return (
      <div className="grid_container">
        <h2>Found no todos</h2>
      </div>
    );
  }

  return (
    <div className="grid_container">
      {array.map((element, index) => {
        return (
          <Card
            removeSelf={() => {
              props.removeCards(element);
            }}
            key={index}
            title={element}
          />
        );
      })}
    </div>
  );
};

export default CardList;

Card.js component

const Card = (props) => {
  const handleRemove = (event) => {
    event.preventDefault();
    deleteItem(props.title);
    props.removeSelf();
  };
  return (
    <div className="card">
      <h2 className="card__title">{props.title}</h2>
      <button type="button" onClick={handleRemove}>
        Delete
      </button>
    </div>
  );
};

export default Card;

EDIT ;

Index.js file

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

Solution

  • SOLUTION

    I fixed the issue by changing the add and remove functions that were inside of MainPage.js file You can see the new versions bellow. Hope someday it will help somebody. Use effect was called once all I had to do get the data again after a change...

    New Remove and Add functions

    const onAddToDo = (todoTitle) => {
        console.log(todoTitle + " Added");
        sub(auth.currentUser.email);
      };
      const onRemove = (title) => {
        console.log(title + " Deleted");
        sub(auth.currentUser.email);
      };