How can I get my input to render in a paragraph, using React?

Viewed 80

I am working on a Todo list, and I have created the functionality for the todo to be added. My list, which renders the todo is located in a sepearate component from where I created the addTodo function. Everything seems to be fine, however, when I click to add a todo, the input that I added doesn't render in the paragraph that I created within the Todo component. I also consoled the data that I am adding in the input, and it shows up in the console. I believe my problem lies with how I am setting my props in the paragraph elemement in my Todo component. This is my code, if it helps.

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 [input, setInput] = 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: input,
      timestamp: firebase.firestore.FieldValue.serverTimestamp(),
    });
    console.log(input);
    setInput("");
  };

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

export default AddLink;
 

And the 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>Todo</h3>
        <p>{props.value}</p>
        <div>
          <AiOutlinePicture />
          <AiOutlineStar />
          <GoGraph />
          <DeleteForeverIcon onClick={deleteHandler} />
        </div>
      </div>
    </li>
  );
}

export default Todo;
1 Answers

From what I can see your todo objects have id, todo, and timestamp properties.

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

  db.collection("links").add({
    id: uuidv4(),
    todo: input,
    timestamp: firebase.firestore.FieldValue.serverTimestamp(),
  });
  setInput("");
};

And when the links collection updates you map it all into a todo property in state.

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

You map it and pass the entire todo property as a prop.

 {todos.map((todo, id) => (
   <Todo todo={todo} key={id} />
 ))}

And finally in Todo you access a value prop.

<p>{props.value}</p>

This isn't a defined value.

It seems you should access a props.todo.todo prop.

<p>{props.todo.todo}</p>

I could be mistaken on the level of nesting and what is unpacked by the onSnapshot subscription, so it could possibly be just props.todo.

<p>{props.todo}</p>

But given that in the deleteHandler you appear to reference correctly props.todo.id I am certain you need to access props.todo.todo in the paragraph.

Word of Caution

When mapping arrays if you are mutating the array (inserting in the middle, deleting elements, etc...) then don't use the array index as the React key as once you mutate the array the keys will shift to different elements. Use the doc.id since that will be an intrinsic value to each element.

{todos.map((todo) => (
  <Todo todo={todo} key={todo.id} />
))}

See Lists & Keys

Related