Search code examples
javascriptreactjsfirebasereact-propsarray.prototype.map

How can I render data, which I receive from two different inputs, in the UI that I am rendering, using React?


I am creating a Todo List using React and Firebase. At the moment, I am able to add a todo, and have it render in the UI, which is a list that I import and map through. The problem, is that when I try and add an additional input, I am not able to render the second input, and only the first input gets rendered. Taking this into consideration I decided to console.log the two inputs, and I saw that the data was available in the console. I then previously created a question on Stackoverflow, thinking that my problem was the props I was expecting in the child component. But now, I think my issue lies with how my data is being passed down, to be mapped and then rendered. For example, my todo is being passed as an argument, but I also need my title to be passed as an argument when I map through my list elemnt. So my question is more about how can I get both pieces of data to render in my UI.

This is the parent component. AddLink.js.

import { useState, useEffect } from "react";
import classes from "./addlink.module.css";
    
import firebase from "firebase/app";
import initFirebase from "../../config";
import "firebase/firestore";

import Todo from "../Todo/Todo";

import { v4 as uuidv4 } from "uuid";

initFirebase();
const db = firebase.firestore();

function AddLink(props) {
  const [todos, setTodos] = useState([]);
  const [inputLinks, setInputLinks] = useState("");
  const [inputHeaders, setInputHeaders] = useState("");

  useEffect(() => {
    db.collection("links")
      .orderBy("timestamp", "desc")
      .onSnapshot((snapshot) => {
        setTodos(
          snapshot.docs.map((doc) => ({
            id: doc.id,
            todo: doc.data().todo,
          }))
        );
      });
  }, []);

  const addTodo = (event) => {
    event.preventDefault();

    db.collection("links").add({
      id: uuidv4(),
      todo: inputLinks,
      title: inputHeaders,
      timestamp: firebase.firestore.FieldValue.serverTimestamp(),
    });
    console.log(inputLinks);
    console.log(inputHeaders);
    setInputLinks("");
    setInputHeaders("");
  };

  return (
    <div className={classes.addlink}>
      <form>
        <div className={classes.adminlink}>
          <input
            type="text"
            value={inputLinks}
            onChange={(event) => setInputLinks(event.target.value)}
          />
          <input
            type="text"
            value={inputHeaders}
            onChange={(event) => setInputHeaders(event.target.value)}
          />
          <button
            className={classes.adminbutton}
            type="submit"
            onClick={addTodo}
          >
            Add new link
          </button>
        </div>
      </form>
      {todos.map((todo, title) => (
        <Todo id={todo.id} todo={todo} title={todo.title} key={todo.id} />
      ))}
      
    </div>
  );
}

export default AddLink;

And the child component Todo.js.

import React from "react";
import { AiOutlinePicture } from "react-icons/ai";
import { AiOutlineStar } from "react-icons/ai";
import { GoGraph } from "react-icons/go";
import DeleteForeverIcon from "@material-ui/icons/DeleteForever";

import classes from "./todo.module.css";

import firebase from "firebase/app";
import initFirebase from "../../config";
import "firebase/firestore";

initFirebase();
const db = firebase.firestore();

function Todo(props) {
  const deleteHandler = (event) => {
    db.collection("links").doc(props.todo.id).delete();
  };

  return (
    <li className={classes.adminsection}>
      <div className={classes.linkCards}>
        <h3>{props.todo.title}</h3>
        <p>{props.todo.todo}</p>
        <div>
          <AiOutlinePicture />
          <AiOutlineStar />
          <GoGraph />
          <DeleteForeverIcon onClick={deleteHandler} />
        </div>
      </div>
    </li>
  );
}

export default Todo;

Any help would be greatly appreciated.


Solution

  • I think i saw that code also once before. You are adding the data in a wrong way and also reading it from the map the wrong way. Here is an example how it should work:

    import { useState, useEffect } from "react";
    import classes from "./addlink.module.css";
    
    import firebase from "firebase/app";
    import initFirebase from "../../config";
    import "firebase/firestore";
    
    import Todo from "../Todo/Todo";
    
    import { v4 as uuidv4 } from "uuid";
    
    initFirebase();
    const db = firebase.firestore();
    
    function AddLink(props) {
      const [todos, setTodos] = useState([]);
      const [inputLinks, setInputLinks] = useState("");
      const [inputHeaders, setInputHeaders] = useState("");
    
      useEffect(() => {
        db.collection("links")
          .orderBy("timestamp", "desc")
          .onSnapshot((snapshot) => {
            setTodos(
              snapshot.docs.map((doc) => ({
                id: doc.id,
                todo: doc.data(),
              }))
            );
          });
      }, []);
    
      const addTodo = (event) => {
        event.preventDefault();
    
        db.collection("links").add({
          id: uuidv4(),
          todo: inputLinks,
          title: inputHeaders,
          timestamp: firebase.firestore.FieldValue.serverTimestamp(),
        });
        console.log(inputLinks);
        console.log(inputHeaders);
        setInputLinks("");
        setInputHeaders("");
      };
    
      return (
        <div className={classes.addlink}>
          <form>
            <div className={classes.adminlink}>
              <input
                type="text"
                value={inputLinks}
                onChange={(event) => setInputLinks(event.target.value)}
              />
              <input
                type="text"
                value={inputHeaders}
                onChange={(event) => setInputHeaders(event.target.value)}
              />
              <button
                className={classes.adminbutton}
                type="submit"
                onClick={addTodo}
              >
                Add new link
              </button>
            </div>
          </form>
          {todos.map((i) => (
            <Todo id={i.id} todo={i.todo} title={i.todo.title} key={i.id} />
          ))}
        </div>
      );
    }
    
    export default AddLink;
    

    The mistakes:

    • here the todo is taking just part of the data:
    snapshot.docs.map((doc) => ({
                id: doc.id,
                todo: doc.data().todo,
              }))
    
    • here you need to consider that you saved a single object and you need to read it as one
    {todos.map((todo, title) => (
            <Todo id={todo.id} todo={todo} title={todo.title} key={todo.id} />
          ))}