task.name not showing in React Firebase app - reactjs

I'm working on a to-do list app in React that connects to a Firestore Database and I'm able to send the data correctly to Firebase, but my {task.name} is not displaying. The list numbers and the button are loading, but not {task.name}. The tasks are added to Firebase with the handleAdd function and the tasks are supposed to be loaded with the useEffect function. Here is the code for the main to-do.js file:
import React, { useEffect } from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { db } from "./utils/firebase";
import { collection, addDoc, Timestamp, query, orderBy, onSnapshot } from "firebase/firestore";
export default function Todo() {
const [name, setName] = useState("");
const [isDoubleClicked, setDoubleClicked] = useState(false);
const [tasks, setTasks] = useState([]);
function handleChange(event) {
event.preventDefault();
setName(event.target.value);
}
const handleAdd = async (e, id) => {
e.preventDefault();
try {
await addDoc(collection(db, 'tasks'), {
name: name,
id: uuidv4(),
completed: false,
created: Timestamp.now(),
})
} catch (err) {
alert(err)
}
}
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created', 'name'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
console.log(tasks);
}, [])
function handleClick(event) {
if (event.detail === 2) {
console.log("double click");
setDoubleClicked(current => !current);
event.currentTarget.classList.toggle('double-clicked');
}
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off, click on "X" to delete an item, and drag items to reorder.</p>
</div>
<input
type="text"
value={name}
onChange={handleChange}
/>
<button
type="submit"
onClick={handleAdd}
>
Add
</button>
</div>
<ol>
{tasks.map((task => (
<li
id={task.id}
key={task.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.name} <button>x</button>
</li>
)))}
</ol>
</div>
);
};

as far as I can tell you are setting task the wrong way, instated of mapping to data stay consistent with the original uploading logic, to avoid such cases you can type your state for the sake of consistency.
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created', 'name'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
name: doc.data().name,
id: doc.data().id,
completed: doc.data().completed,
created: doc.data().created,
})))

I figured this out. I had to change {task.name} to {task.data.name}. I also updated the useEffect function. Here is the code:
import React, { useEffect } from "react";
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { db } from "./utils/firebase";
import { collection, addDoc, Timestamp, query, orderBy, onSnapshot } from "firebase/firestore";
export default function Todo() {
const [name, setName] = useState("");
const [isDoubleClicked, setDoubleClicked] = useState(false);
const [tasks, setTasks] = useState([]);
function handleChange(event) {
event.preventDefault();
setName(event.target.value);
}
const handleAdd = async (e, id) => {
e.preventDefault();
try {
await addDoc(collection(db, 'tasks'), {
name: name,
id: uuidv4(),
completed: false,
created: Timestamp.now(),
})
} catch (err) {
alert(err)
}
}
useEffect(() => {
const q = query(collection(db, 'tasks'), orderBy('created'))
onSnapshot(q, (querySnapshot) => {
setTasks(querySnapshot.docs.map(doc => ({
data: doc.data()
})))
})
console.log(tasks);
}, [])
function handleClick(event) {
if (event.detail === 2) {
console.log("double click");
setDoubleClicked(current => !current);
event.currentTarget.classList.toggle('double-clicked');
}
}
return (
<div>
<div>
<div>
<h1>To Do List App</h1>
<p>Double click to mark an item off, click on "X" to delete an item, and drag items to reorder.</p>
</div>
<input
type="text"
value={name}
onChange={handleChange}
/>
<button
type="submit"
onClick={handleAdd}
>
Add
</button>
</div>
<ol>
{tasks.map((task => (
<li
id={task.data.id}
key={task.data.id}
completed={task.data.completed}
onClick={handleClick}
>
{task.data.name} <button>x</button>
</li>
)))}
</ol>
</div>
);
};

Related

How to get id from clicked doc firebase react?

I am trying to get the id from a doc when I click a delete button. so the user can delete the post.
I got far enough to show the button if the user is the uploader.
but, I don't know how to get the id from the clicked document.
I tried this:
{auth.currentUser.uid === uploaderId ? (
<div
onClick={(e) => {
if (auth.currentUser.uid === uploaderId) {
e.stopPropagation();
deleteDoc(doc(db, "posts", id));
}
}}
>
<div>
<button>🗑️</button>
</div>
</div>
) : (
<div>
</div>
)}
but that doesn't work and gives me the error:
Uncaught TypeError: Cannot read properties of undefined (reading 'indexOf')
so to summarize how do I get the id from the document
full code:
import React, { useEffect, useRef, useState } from 'react';
import './App.css';
import { initializeApp } from "firebase/app";
import { getFirestore, collection, orderBy,
query, Timestamp, addDoc, limitToLast,
deleteDoc, doc, } from "firebase/firestore";
import { confirmPasswordReset, getAuth, GoogleAuthProvider, signInWithPopup, } from "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";
import { useCollectionData } from "react-firebase-hooks/firestore";
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
function App() {
const [user] = useAuthState(auth);
return (
<div className="App">
<Header/>
{/* <Picker/> */}
<section>
<SignOut/>
{/* <ShowProfile/> */}
<Upload/>
{user ? <Feed /> : <SignIn />}
</section>
</div>
);
}
function Header() {
return (
<h1 className='headerTxt'>SHED</h1>
)
}
// function Picker() {
// }
function SignIn() {
const signInWithGoogle = () =>{
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
}
return (
<button className='SignIn' onClick={signInWithGoogle}>sign in with google</button>
)
}
function SignOut() {
return auth.currentUser && (
<button onClick={() => auth.signOut()}>Sign out</button>
)
}
function Feed() {
const postsRef = collection(db, 'posts');
const qwery = query(postsRef,orderBy('createdAt'), limitToLast(25), );
const [posts] = useCollectionData(qwery, {idField: "id"});
return (
<>
<main>
{posts && posts.map(msg => <Post key={msg.id} post={msg} />)}
</main>
</>
)
}
function Post(props) {
const {text, photoURL, displayName, uid, uploaderId, id} = props.post;
return (
<div className={"post"}>
<div className='msg'>
<div className='top'>
<img src={photoURL} alt="" />
<sub>{displayName}</sub>
</div>
<hr />
<p>{text}</p>
{auth.currentUser.uid === uploaderId ? (
<div
onClick={(e) => {
if (auth.currentUser.uid === uploaderId) {
e.stopPropagation();
deleteDoc(doc(db, "posts", id));
}
}}
>
<div>
<button>🗑️</button>
</div>
</div>
) : (
<div>
</div>
)}
{/* <button>❤️</button> */}
</div>
</div>
)
}
// function ShowProfile() {
// return(
// <div>
// <button onClick={queryMine}>my posts</button>
// </div>
// )
// }
function Upload() {
const postsRef = collection(db, 'posts');
const [formValue, setFormValue] = useState('');
const sendpost = async(e) =>{
e.preventDefault();
const {uid, photoURL, displayName} = auth.currentUser;
if (formValue !== "" && formValue !== " ") {
await addDoc(postsRef, {
text: formValue,
createdAt: Timestamp.now(),
displayName,
uploaderId: uid,
photoURL
})
setFormValue('')
}
}
return auth.currentUser &&(
<form onSubmit={sendpost}>
<textarea name="" id="" cols="30" rows="10"
value={formValue} onChange={(e) => setFormValue(e.target.value)}
></textarea>
<button type='submit'>➜</button>
</form>
)
}
export default App;```
Get the data (doc id and doc data) using querySnapshot and merge them into one object. Save an array of objects to a state and pass what you need as properties. For example:
...
const querySnapshot = await getDocs(collection(db, `users/${email}/todos`))
const todos = []
querySnapshot.forEach(doc => {
todos.push({
id: doc.id,
text: doc.data().text,
isCompleted: doc.data().isCompleted,
})
})
return todos
Here is a simple example of the Todo App, but the logic is the same. In the Todo component, I get the doc id as props. Full code:
import { collection, getDocs } from 'firebase/firestore'
import { useEffect, useState } from 'react'
import { db } from '../../app/firebase'
import Todo from '../Todo'
import './style.css'
const TodoList = () => {
const [todos, setTodos] = useState()
useEffect(() => {
const getData = async () => {
const querySnapshot = await getDocs(collection(db, 'users/kvv.prof#gmail.com/todos'))
const todos = []
querySnapshot.forEach(doc => {
todos.push({
id: doc.id,
text: doc.data().text,
isCompleted: doc.data().isCompleted,
})
})
return setTodos(todos)
}
getData()
}, [])
return (
<section className="todo-list">
{todos?.map(todo => (
<Todo key={todo.id} id={todo.id} text={todo.text} isCompleted={todo.isCompleted} />
))}
</section>
)
}
export default TodoList
If you use react-firebase-hooks, then you need to use this

Set a default content from database in react-draft-wysiwyg editor

I am creating a blog website in which I am embedding react-draft-wysiwyg editor. I am facing problem when the user has to update the blog. When I click the update button the content is gone. I looked into many solutions but I couldn't make it work.
This is my code
import axios from "axios";
import React, { useContext, useEffect, useState } from "react";
import { useLocation } from "react-router";
import { Link } from "react-router-dom";
import { Context } from "../../context/Context";
import "./singlePost.css";
import { EditorState, ContentState, convertFromHTML } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import { convertToHTML } from 'draft-convert';
import DOMPurify from 'dompurify';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import Parser from 'html-react-parser';
export default function SinglePost() {
const location = useLocation();
const path = location.pathname.split("/")[2];
const [post, setPost] = useState({});
const PF = "http://localhost:5000/images/";
const { user } = useContext(Context);
const [title, setTitle] = useState("");
const [desc, setDesc] = useState("");
const [updateMode, setUpdateMode] = useState(false);
useEffect(() => {
const getPost = async () => {
const res = await axios.get("/posts/" + path);
setPost(res.data);
setTitle(res.data.title);
setDesc(res.data.desc);
};
getPost();
}, [path]);
const handleDelete = async () => {
try {
await axios.delete(`/posts/${post._id}`, {
data: { username: user.username },
});
window.location.replace("/");
} catch (err) {}
};
// updating post
const handleUpdate = async () => {
try {
await axios.put(`/posts/${post._id}`, {
username: user.username,
title,
desc,
});
setUpdateMode(false)
} catch (err) {}
};
const [editorState, setEditorState] = useState(
() => EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(desc)
)
),
);
const [convertedContent, setConvertedContent] = useState(null);
const handleEditorChange = (state) => {
setEditorState(state);
convertContentToHTML();
}
const convertContentToHTML = () => {
let currentContentAsHTML = convertToHTML(editorState.getCurrentContent());
setConvertedContent(currentContentAsHTML);
setDesc(currentContentAsHTML);
}
const createMarkup = (html) => {
return {
__html: DOMPurify.sanitize(html)
}
}
return (
<div className="singlePost">
<div className="singlePostWrapper">
{post.photo && (
<img src={PF + post.photo} alt="" className="singlePostImg" />
)}
{updateMode ? (
<input
type="text"
value={title}
className="singlePostTitleInput"
autoFocus
onChange={(e) => setTitle(e.target.value)}
/>
) : (
<h1 className="singlePostTitle">
{title}
{post.username === user?.username && (
<div className="singlePostEdit">
<i
className="singlePostIcon far fa-edit"
onClick={() => setUpdateMode(true)}
></i>
<i
className="singlePostIcon far fa-trash-alt"
onClick={handleDelete}
></i>
</div>
)}
</h1>
)}
<div className="singlePostInfo">
<span className="singlePostAuthor">
Author:
<Link to={`/?user=${post.username}`} className="link">
<b> {post.username}</b>
</Link>
</span>
<span className="singlePostDate">
{new Date(post.createdAt).toDateString()}
</span>
</div>
{updateMode ? (
// <textarea
// className="singlePostDescInput"
// value={desc}
// onChange={(e) => setDesc(e.target.value)}
// />
<Editor
contentState={desc}
editorState={editorState}
onEditorStateChange={handleEditorChange}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>
) : (
<p className="singlePostDesc">{Parser(desc)}</p>
)}
{updateMode && (
<button className="singlePostButton" onClick={handleUpdate}>
Update
</button>
)}
</div>
</div>
);
}
I want to display desc which is saved in MongoDB database when the user clicks on update button.
The following part is what I tried to do but didn't work.
const [editorState, setEditorState] = useState(
() => EditorState.createWithContent(
ContentState.createFromBlockArray(
convertFromHTML(desc)
)
),
);
I am getting warning in this:
react.development.js:220 Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the r component.
Please help

How to filter or search for services in another component (React)

NOTE: My Page Card component is working correctly. How can I filter the card page component in the Search component?
I'm new to react, and I don't quite understand how I can accomplish this task.
In the Search component I put it in a fixed way, as I can't filter a component using another.
The Code is summarized for ease.
Card Page
import React, {
useEffect,
useState
} from "react";
import classes from "./boxService.module.css";
import axios from "axios";
function BoxService() {
const [test, SetTest] = useState([]);
useEffect(() => {
axios
.get("http://localhost:8080/api/test")
.then((response) => {
SetTest(response.data);
})
.catch(() => {
console.log("Error!");
});
}, []);
return ({
test.map((test, key) => {
<div className={classes.box}
return (
<Grid item xs = {2} key={key} >
<div className={test.name} < div >
<p className={test.description}</p>
</Grid>
);
})}
);
}
export default BoxService;
Seach Page
import React, {
useState,
useEffect
} from "react";
import axios from "axios";
function Search() {
const [searchTerm, setSearchTerm] = useState("");
const [test, SetTest] = useState([]);
//Chamada API
useEffect(() => {
axios
.get("http://localhost:8080/api/test")
.then((response) => {
SetTest(response.data);
})
.catch(() => {
console.log("Error");
});
}, []);
return (
<div>
<input type = "text"
placeholder = "Search..."
onChange = {
(event) => {
setSearchTerm(event.target.value);
}
}/>
{
test.filter((val) => {
if (searchTerm === "") {
return val;
} else if (
val.nome.toLowerCase().includes(searchTerm.toLowerCase())
) {return val;}
}).map((val, key) => {
return ( <div className = "user"
key = {key} >
<p> {val.name} </p> </div>
);
})
} </div>
);
}
export default Search;
Here is an example of how it should/could look like:
import React from "react";
function SearchBox({ setSearchTerm, searchTerm }) {
const handleFilter = (e) => {
setSearchTerm(e.target.value);
};
return (
<>
filter
<input type="search" onChange={handleFilter} value={searchTerm} />
</>
);
}
export default function App() {
const [searchTerm, setSearchTerm] = React.useState("");
const [filteredResults, setFilteredResults] = React.useState([]);
const [results, setResults] = React.useState([]);
React.useEffect(() => {
const fetchdata = async () => {
const randomList = await fetch(`https://randomuser.me/api/?results=50`);
const data = await randomList.json();
const { results } = data;
setResults(results);
};
fetchdata();
}, []);
React.useEffect(() => {
const filterResults = results.filter((item) =>
item.name.last.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredResults(filterResults);
}, [searchTerm, results]);
return (
<div className="App">
<SearchBox setSearchTerm={setSearchTerm} searchTerm={searchTerm} />
<div>
<ul>
{filteredResults.map(({ name }, idx) => {
return (
<li key={idx}>
{name.first} {name.last}
</li>
);
})}
</ul>
</div>
</div>
);
}

Can't delete an item using useContext and useReducer

Please help guys. So I've started to learn about useReducer and useContext and I'm stuck at deleting an object. I've tried to log the result in the console and it prints the expected array that I want. However, when I return the array from the reducer, and whenever I delete an object, it clears the array and the button is left. So I've got this 2 js files below.
useContext & useReducer
import { createContext, useReducer } from "react";
const initialState = {
items: [],
};
const reducer = (state, action) => {
switch (action.type) {
case "ADD_ITEM":
return { ...state, items: [action.payload, ...state.items] };
case "DEL_ITEM":
return {
...state,
items: [state.items.filter((item) => item.id !== action.payload)],
};
default:
return state;
}
};
export const ItemContext = createContext(initialState);
export const ItemProvider = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);
const addItem = (item) => {
dispatch({ type: "ADD_ITEM", payload: item });
};
const delItem = (id) => {
dispatch({ type: "DEL_ITEM", payload: id });
};
return (
<ItemContext.Provider value={{ items: state.items, addItem, delItem }}>
{props.children}
</ItemContext.Provider>
);
};
import React, { useContext, useState } from "react";
import { ItemContext } from "../Context/ItemContext";
const Test2 = () => {
const { items } = useContext(ItemContext);
const { addItem } = useContext(ItemContext);
const { delItem } = useContext(ItemContext);
const [name, setName] = useState("");
const [price, setPrice] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
const newItem = {
id: items.length + 1,
name: name,
price: price,
};
addItem(newItem);
setName("");
setPrice("");
console.log(newItem);
};
const handleDelete = (id) => {
delItem(id);
};
return (
<div>
<div>
<form>
<label>Product Name:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<label>Product Price:</label>
<input
type="text"
value={price}
onChange={(e) => setPrice(e.target.value)}
/>
<button onClick={handleSubmit}>Submit</button>
</form>
</div>
<div>
{items.map((item) => (
<div key={item.id}>
<li>
<p>{item.name}</p>
<p>{item.price}</p>
</li>
<button onClick={() => handleDelete(item.id)}>X</button>
</div>
))}
</div>
</div>
);
};
export default Test2;
The Array.filter() already returns a new array, so when you're putting it into [], you are creating an array which contains an array as your items as the first element.
case "DEL_ITEM":
return {
...state,
items: state.items.filter((item) => item.id !== action.payload),
};
is therefore correct.

Redux action won't fire in component

I'm trying to run an action function in a component that creates an axios request on the backend. This function (addList) works in another component, but when I try to use it in another component, it doesn't work.
Here is the component where addList doesn't work. addList gets called in the onRowAdd function.
import React, { useState } from "react";
import MaterialTable from "material-table";
import { addList } from '../../actions/profile';
import PropTypes from "prop-types";
import { connect } from "react-redux";
const ListTable = (list, {addList}) => {
const l = list;
const [data, setData] = useState(l.list.list);
console.log(l.list.list);
list = l.list.list;
var listname = l.list.name;
const handlebClick = (e) => {
e.preventDefault();
console.log({list});
};
return (
<div className="list-table">
<button onClick={e => handlebClick(e)}>Apply Sequence</button>
<MaterialTable
title={listname}
columns={[
{ title: "User Name", field: "username" },
{
title: "Link",
field: "link",
initialEditValue: ""
},
{ title: "Name", field: "name", initialEditValue: "" },
{ title: "Source", field: "source", initialEditValue: "" },
{ title: "Date Added", field: "dateAdded", type: "date" },
{ title: "Notes", field: "notes", initialEditValue: "" }
]}
data={l.list.list}
editable={{
onRowAdd: newData =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const d = data;
d.push(newData);
setData({ d });
console.log(data);
addList(data, l.list.name, true);
resolve();
//put request to change list
}
resolve();
}, 1000);
}),
onRowUpdate: (newData, oldData) =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
const d = data;
//console.log(d, oldData);
const index = d.indexOf(oldData);
d[index] = newData;
setData({ d });
resolve();
//put request to change list
}
resolve();
}, 1000);
}),
onRowDelete: oldData =>
new Promise((resolve, reject) => {
setTimeout(() => {
{
let d = data;
console.log(d);
if (d.length > 0) {
const index = d.indexOf(oldData);
d.splice(index, 1);
setData({ d });
}
resolve();
//put request to change list
}
resolve();
}, 1000);
})
}}
/>
</div>
);
};
ListTable.propTypes = {
addList: PropTypes.func.isRequired
};
export default connect(
null,
{ addList }
)(ListTable);
Here's the component where addList works. It's called in the submit buttons onclick.
import React, { Fragment, useState } from "react";
import { Link, withRouter } from "react-router-dom";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { addList } from "../../actions/profile";
function ListItem({ listitem, index }) {
return <div className="listitem">Username: {listitem.username}</div>;
}
function ListForm({ addListItem }) {
const [value, setValue] = useState("");
const handleSubmit = e => {
e.preventDefault();
if (!value) return;
addListItem(value);
setValue("");
};
return (
<form onSubmit={handleSubmit}>
<input
className="input"
type="text"
placeholder="* Username"
name="username"
value={value}
onChange={e => setValue(e.target.value)}
required
/>
</form>
);
}
const AddList = ({ addList }) => {
const [list, setList] = useState([]);
const addListItem = username => {
const newListItems = [...list, { username }];
setList(newListItems);
};
return (
<Fragment>
<h1 className="large text-primary">Add A List</h1>
<p className="lead">
<i className="fas fa-code-branch" /> Add a list of your followers'
usernames
</p>
<div className="form">
<div className="form-group" />
<div className="list">
{list.map((listitem, index) => (
<ListItem key={index} index={index} listitem={listitem} />
))}
<ListForm addListItem={addListItem} />
</div>
<input
type="submit"
className="btn btn-primary my-1"
onClick={e => {
e.preventDefault();
console.log(list);
const listname = prompt(
"Please enter a name for the list",
"List Name"
);
addList([{}], listname, false);
}}
/>
<Link className="btn btn-light my-1" to="/dashboard">
Go Back
</Link>
</div>
</Fragment>
);
};
AddList.propTypes = {
addList: PropTypes.func.isRequired
};
export default connect(
null,
{ addList }
)(withRouter(AddList));
The error is Uncaught TypeError: addList is not a function

Resources