I want to update a 'progress' field in the document with autogenerated id (using fire store)every time the progress button is clicked.
I tried in several different ways but nothing seems to be working. I am attaching my method:
useEffect(() => {
var user = firebase.auth().currentUser;
var email = user.email;
var documentID = database.collection("ChosenChallenge").doc().id;
database
.collection("Users")
.doc(email)
.collection("ChosenChallenge")
.doc(documentID)
.update({
Progress: percentRange
})
.then(() => {
console.log('added!');
});
}, )
and the class:
import React, { useState, useEffect } from "react";
import './newprogressbar.css';
import { firebaseAppAuth, database } from "../firebase";
import firebase from "firebase/app";
export const ProgressBarContainer = (props) => {
let [percentRange, setProgress] = useState(0);
const handleUpdate = () => {
setProgress(percentRange < 99 ? percentRange + 7.14285714 : 100);
props.onChange(percentRange + 7.14285714);
}
const Range = (props) => {
return (
<div className="range" style={{width: `${props.percentRange}%`}}/>
);
};
const ProgressBar = (props) => {
return (
<div className="progress-bar">
<Range percentRange={props.percentRange}/>
</div>
);
};
useEffect(() => {
var user = firebase.auth().currentUser;
var email = user.email;
var documentID = database.collection("ChosenChallenge").doc().id;
database
.collection("Users")
.doc(email)
.collection("ChosenChallenge")
.doc(documentID)
.update({
Progress: percentRange
})
.then(() => {
console.log('added!');
});
}, )
return (
<div id="progresscontainer">
<ProgressBar percentRange={percentRange} />
<button id="updatepic" onClick={handleUpdate}>Daily Update</button>
</div>
);
};
Any help high appreciated!
database.collection("ChosenChallenge").doc().id generates a new random document ID every time it's called. It doesn't know which existing document you want.
If you want to update a document, you must know its existing ID. There is no avoiding that. If you don't know the ID, you will need to query the collection to find the document ID based on something you know about the contents of the document.
If you don't know the ID and you don't know anything about the contents of the document to filter a query, you're stuck, and you might need to rethink how you are using Firestore for this app.
Related
I'm new to Firebase. Right now I'm building a Todo List app with React, my code is the following:
...
const todoItem = useRef(null);
const pathRef = collection(firestore, `users/${user.uid}/todos`)
const [todos] = useCollectionData(pathRef);
const handleSubmit = async e => {
e.preventDefault();
const text = todoItem.current.value
if(text) {
try {
const docRef = await addDoc(pathRef, {
text,
completed: false,
createdAt: serverTimestamp(),
});
console.log("This doc's id is: ", docRef.id)
} catch (e) {
console.error("Error adding document: ", e);
}
}
};
...
Below my List component I have a TodoItem component:
...
const onDelete = (id) => deleteDoc(doc(pathRef, '/?????'))
return (
<div key={id} className="...">
<li className="...">{text}</li>
<button onClick={() => onDelete(id)} className="...">Delete</button>
</div>
)
...
For the Delete button to work I need the unique docRef.id associated to that document, but I'm having troubles finding a way to fetch it from Firestore or saving it in my state in such way that remains related to it's corresponding document. I'm probably overcomplicating this too much, so I turn to you lovely coders from stackoverflow. What can I do?
I want to only show the "Load More" button when I have extra documents to show on my React and Firebase website.
Right now, I'm fetching only 2 documents from a firestore and I want to show the "Load More" button when I have more than 2 documents in my firestore. If I only have 2 or fewer than 2 documents in my firestore, I don't want to show the "Load More" button.
And I want to hide the "Load More" button after fetching all the documents that I have on the firestore.
Anyone, please help me with this!
useCollection Hook:
import { useEffect, useRef, useState } from "react"
// firebase import
import {
collection,
getDocs,
limit,
onSnapshot,
orderBy,
query,
startAfter,
where,
} from "firebase/firestore"
import { db } from "../firebase/config"
export const useCollection = (c, _q, _l, _o) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
// if we don't use a ref --> infinite loop in useEffect
// _query is an array and is "different" on every function call
const q = useRef(_q).current
const o = useRef(_o).current
useEffect(() => {
let ref = collection(db, c)
if (q) {
ref = query(ref, where(...q))
}
if (o) {
ref = query(ref, orderBy(...o))
}
if (_l) {
ref = query(ref, limit(_l))
}
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
// unsubscribe on unmount
return unsubscribe
}, [])
const fetchMore = async (doc) => {
setIsLoading(true)
const q = query(
collection(db, c),
orderBy(...o),
startAfter(doc.createdAt),
limit(_l)
)
const newDocuments = await getDocs(q)
updateState(newDocuments)
setIsLoading(false)
}
const updateState = (docs) => {
if (!docs.empty) {
const tempPosts = []
docs.forEach((document) => {
tempPosts.push({
id: document.id,
...document.data(),
})
})
setDocuments([...documents, ...tempPosts])
}
}
return { documents, fetchMore, error, isLoading }
}
SolutionComments.js (In this file I'm showing the "Load More Comments" button)
import React, { useState } from "react"
import { useParams } from "react-router-dom"
import { useCollection } from "../../hooks/useCollection"
import Comment from "./Comment"
import CommentForm from "./CommentForm"
const SolutionComments = () => {
const [activeComment, setActiveComment] = useState(null)
const { id } = useParams()
const { documents, fetchMore, isLoading } = useCollection(
`solutions/${id}/comments`,
null,
2,
["createdAt", "desc"]
)
const fetchMoreComments = () => {
fetchMore(documents[documents.length - 1])
}
return (
<div className="mt-10">
<CommentForm docID={id} />
<div>
{documents &&
documents.map((comment) => (
<Comment
key={comment.id}
comment={comment}
replies={comment.replies}
activeComment={activeComment}
setActiveComment={setActiveComment}
/>
))}
</div>
{documents.length > 2 && (
<button onClick={fetchMoreComments} className="text-white bg-purple-500">
{!isLoading ? "Load More Comments!" : "Loading..."}
</button>
)}
</div>
)
}
export default SolutionComments
Firestore does not have a mechanism for automatically telling you how many documents are in a query. You will need to manage that aggregate count yourself. Then you could fetch that aggregate count and use that to determine whether to show a load more button or not.
However this is harder than it sounds. And most modern apps we don't use load more buttons rather we use infinite scroll.
To be honest, it has never been efficient or smart to show a result set count in an app. The fact that people have done it in the past doesn't mean that it is the right thing to do today. It might have made sense when you had small databases, typically running off of a desktop database. But in a cloud-based solution with millions of documents and millions of users, and complex queries, knowing how many documents are in a result set is a very hard problem to solve.
I encountered a problem in my chat app.
I works when I post message doc to the messages col but then I'm trying do getDocs back and render them I get an empty array.
I looked through FB docs, and I didn't notice any mistakes on my part. I also read an article where I was advised to use the react-firebase library with useCollectionData with which I had the same result.
const [messages, loading] = useCollectionData(
firestore.collection('messages').orderBy('createdAt')
)
I tried different approaches but nothing seems to work.
import React, { useState, useEffect } from 'react'
import { auth, db, app } from '../../firebase.config'
import { useAuthState } from 'react-firebase-hooks/auth'
import { useCollectionData } from 'react-firebase-hooks/firestore'
import { docs, onSnapshot, query, where, addDoc, collection, serverTimestamp, orderBy, getDocs } from 'firebase/firestore'
import Message from '../Message/Message'
import Spinner from '../Spinner/Spinner'
import './chat.css'
const Chat = () => {
const [user, loading, error] = useAuthState(auth)
const [value, setValue] = useState('')
const [msgs, setMsgs] = useState([])
console.log('msgs>>>>', msgs)
useEffect(() => {
const fetchMsg = async () => {
const messagesRef = collection(db, 'messages')
const q = query(
messagesRef,
orderBy('timestamp', 'desc')
)
const querySnap = await getDocs(q)
let listings = []
querySnap.forEach((doc) => {
return listings.push({
id: doc.id,
data: doc.data(),
})
})
setMsgs(listings)
}
fetchMsg()
}, [])
const sendMessage = async (e) => {
e.preventDefault();
const docRef = await addDoc(collection(db, 'messages'), {
uid: user.uid,
displayName: user.displayName,
photoURL: user.photoURL,
text: value,
createdAt: serverTimestamp()
})
console.log(docRef)
setValue('')
}
if (loading) {
return <Spinner />
}
return (
<>
<div className='ch-wind'>
{msgs.map((msg) => (
<Message key={msg.id} msg={msg} style={{ backgroundColor: user.uid === msg.uid ? '#A32cc4' : '#a1045a' }} />
))}
</div>
<form className="ch-form" onSubmit={sendMessage}>
<textarea
value={value}
className='ch-form-text'
onChange={e => setValue(e.target.value)}
placeholder='Enter your message here'
/>
<button
className='ch-form-btn'
>
Send
</button>
</form>
</>
)
}
export default Chat
By using useEffect() hook, I would assume that you want to get the data realtime. Firestore has a realtime listeners that you can use. You can listen to a document with the onSnapshot() method. An initial call using the callback you provide creates a document snapshot immediately with the current contents of the single document. Then, each time the contents change, another call updates the document snapshot. See code below:
useEffect(() => {
const messagesRef = query(collection(db, 'messages'), orderBy('timestamp', 'desc'));
onSnapshot(messagesRef, (snapshot) => {
// Maps the documents and sets them to the `msgs` state.
setMsgs(snapshot.docs.map(doc => ({
id: doc.id,
data: doc.data()
})))
})
}, [])
Also, as pointed out by #CDoe, you should use the same Fieldname which you set from the addDoc method as you can see on the above code.
Then on the rendering, something like this:
{msgs.map((msg) => (
// By setting the `doc.data()` to the object `data`, you should access it by `msg.data.<object_key>`
<Message key={msg.id} msg={msg.data.text} style={{ backgroundColor: user.uid === msg.data.uid ? '#A32cc4' : '#a1045a' }} />
))}
I leave some comments on the code to better understand it.
For more information on realtime updates, you may check out this documentation.
In the query, you're trying to orderBy timestamp. That's not a field you're creating in sendMessage.
When a value you're ordering by doesn't exist on the document, it won't return.
Maybe you meant to orderyBy the createdAt value.
const q = query(
messagesRef,
orderBy('createdAt', 'desc')
)
this is my react code here I am getting react poll using API but when I start working on handalchange For POST API request I need (PollId,userId and answer) I am getting userId through { const userId = isAutheticated() && isAutheticated().user._id; } but I do not understand how can I get PollId from my all polls, please help...!
import React, { useState, useEffect } from "react";
import Poll from "react-polls";
import "../../styles.css";
import { isAutheticated } from "../../auth/helper/index";
import { getPolls, postPoll } from "../helper/coreapicalls";
import { useParams } from "react-router-dom";
const MainPoll = () => {
const userId = isAutheticated() && isAutheticated().user._id;
const pollId = useParams();
const id = pollId._Id;
console.log(id);
const [polls, setPoll] = useState([]);
const [error, seterror] = useState(false);
// Setting answers to state to reload the component with each vote
const [pollAnswers, setPollAnswers] = useState([]);
useEffect(() => {
loadPoll();
}, []);
const loadPoll = () => {
getPolls().then((data) => {
if (data.error) {
seterror(data.error);
} else {
setPoll(data);
console.log(data);
}
});
};
// Handling user vote
// Increments the votes count of answer when the user votes
const handalchange = () => {
postPoll();
console.log("hello");
};
return (
<div className="">
<div className="container my-5">
<h1 className="blog_heading my-3">Poll's of the Day</h1>
<div className="row">
{polls.reverse().map((poll, index) => (
<div className="col-lg-4 col-12 poll_border" key={index}>
<Poll
noStorage
question={poll.question}
answers={Object.keys(poll.options).map((key) => {
return {
option: key,
votes: poll.options[key].length,
};
})}
onVote={handalchange}
className="mb-2"
/>
</div>
))}
</div>
</div>
</div>
);
};
export default MainPoll;
my frontend image -
Here I have 5 polls , so I can not get PollId from useParams ...! so how can I get..?
Your component seems to represent list of polls, not any specific poll. So if you have an array of polls instead of one poll, than you have multiple ids instead of the single one.
You can get them by mapping your polls array like that:
const pollIds = polls.map((poll) => poll.id); // or any other prop that stores id
I'm working with Firebase - Cloud Firestore and at the moment I would like to paginate all the records available. I already have a list of records and what is left is some pagination for this. I'm new with Cloud Firestore, so any clarity is appreciated.
I checked the Firestore documentation (https://firebase.google.com/docs/firestore/query-data/query-cursors#paginate_a_query) and examples with ReactJS, but there is not much available.
I understand that eg:.startAt(0), .limit(10), but the question is how to paginate properly with this component called at the render method.
import React, { Component } from 'react';
import Pagination from "react-js-pagination";
import firestore from "./Firebase";
export default class DataList extends Component {
constructor(props) {
super(props);
this.state = {
dbItems: [],
currentPage: 1,
itemsPerPage: 3,
totalItemCount: 1,
activePage: 15
}
this.handlePageChange = this.handlePageChange.bind(this);
}
handlePageChange(pageNumber) {
console.log(`active page is ${pageNumber}`);
this.setState({ activePage: pageNumber });
}
async getItems() {
const { currentPage, itemsPerPage } = this.state;
const startAt = currentPage * itemsPerPage - itemsPerPage;
const usersQuery = firestore.collection('Users').orderBy("email").startAt(startAt).limit(itemsPerPage)
const snapshot = await usersQuery.get()
const items = snapshot.docs.map(doc => doc.data())
return this.setState({
dbItems: items,
totalItemCount: firestore.collection('Users').get().then(res => console.log(res.size))
})
}
componentDidMount() {
this.getItems()
}
componentDidUpdate(prevProps, prevState) {
const isDifferentPage = this.state.currentPage !== prevState.currentPage
if (isDifferentPage) this.getItems()
}
render() {
return (
<div>
{this.state.dbItems.map((users, index) => {
return (
<p key={index}>
<b>First Name:</b> {users.firstname} <br />
<b>Email:</b> {users.email}
</p>
)
})
}
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={this.state.itemsPerPage}
totalItemsCount={this.state.totalItemCount}
pageRangeDisplayed={this.state.itemsPerPage}
onChange={this.handlePageChange}
/>
</div>
)
}
}
Thank you for the help!
Pagination can be achieved using startAt()
// Get Items.
async fetchUsers = () => {
// State.
const {users, usersPerPage} = this.state
// Last Visible.
const lastVisible = users && users.docs[users.docs.length - 1]
// Query.
const query = firestore.collection('Users')
.orderBy('email')
.startAfter(lastVisible)
.limit(usersPerPage)
// Users.
const users = await query.get()
// ..
return this.setState({users})
}
// Did Mount.
componentDidMount() {
this.fetchUsers()
}
// Did Update.
componentDidUpdate(prevProps, prevState) {
const isDifferentPage = this.state.currentPage !== prevState.currentPage
if (isDifferentPage) this.fetchUsers()
}
Anyone new to Firestore and Firestore Pagination with ReactJS that would be kinda confusing to understand how Pagination will work or when to trigger call to next set of documents in firestore. anyone struggle like this try my example to make some ideas and process ahead.(Im using React-Bootstrap to render UI Elements)
01 - Install Package react-infinite-scroll-component
First Install this package yarn add react-infinite-scroll-component
02 - Include Package
Include it to your file by 'import InfiniteScroll from 'react-infinite-scroll-component';' importing it
03 - Init State
initiate state with empty list array
this.state = {
list: [],
};
04 - Create Function to get first set of data and initiate it with component did mount
//component did mount will fetch first data from firestore
componentDidMount(){
this.getUsers()
}
getUsers(){
let set = this
//initiate first set
var first = set.ref.collection("users").limit(12);
first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
//initiate local list
const list = [];
documentSnapshots.forEach(function(doc) {
//im fetching only name and avatar url you can get any data
//from your firestore as you like
const { name, avatar_full_url } = doc.data();
//pushing it to local array
list.push({ key: doc.id, name, avatar_full_url });
});
//set state with updated array of data
//also save last fetched data in state
set.setState({ list, last: lastVisible });
});
}
05 - Create function to get balance data set
fetchMoreData = () => {
let set = this
//get last state we added from getUsers()
let last = this.state.last
var next = set.ref.collection("users").startAfter(last).limit(12);
next.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
const list = [];
documentSnapshots.forEach(function(doc) {
//im fetching only name and avatar url you can get any data
//from your firestore as you like
const { name, avatar_full_url } = doc.data();
list.push({ key: doc.id, name, avatar_full_url });
});
//set state with updated array of data
//also save last fetched data in state
let updated_list = set.state.list.concat(list);
set.setState({ list: updated_list, last: lastVisible });
});
};
06 - Render UI
<InfiniteScroll
dataLength={this.state.list.length}
next={this.fetchMoreData}
hasMore={true}
loader={<span className="text-secondary">loading</span>}>
<Row className="mt-3">
{ this.state.list.map((single, index) => (
<Col lg={4} key={ index }>
<div>
<Image src={ single.avatar_full_url }roundedCircle width="100" />
<h2>{ single.name }</h2>
</div>
</Col>
))}
</Row>
</InfiniteScroll>
Check this example this could help anyone who trying previous / next pagination
//initial state
const [list, setList] = useState([]);
const [page, setPage] = useState(1);
//loading initial data
useEffect(() => {
const fetchData = async () => {
await firebase.firestore().collection('users')
.orderBy('created', 'desc') //order using firestore timestamp
.limit(5) //change limit value as your need
.onSnapshot(function(querySnapshot) {
var items = [];
querySnapshot.forEach(function(doc) {
items.push({ key: doc.id, ...doc.data() });
});
setList(items);
})
};
fetchData();
}, []);
After loading initial data use following function for next button trigger
//next button function
const showNext = ({ item }) => {
if(list.length === 0) {
//use this to show hide buttons if there is no records
} else {
const fetchNextData = async () => {
await firebase.firestore().collection('users')
.orderBy('created', 'desc') //order using firestore timestamp
.limit(5) //change limit value as your need
.startAfter(item.created) //we pass props item's first created timestamp to do start after you can change as per your wish
.onSnapshot(function(querySnapshot) {
const items = [];
querySnapshot.forEach(function(doc) {
items.push({ key: doc.id, ...doc.data() });
});
setList(items);
setPage(page + 1) //in case you like to show current page number you can use this
})
};
fetchNextData();
}
};
Then Previous button function
//previous button function
const showPrevious = ({item}) => {
const fetchPreviousData = async () => {
await firebase.firestore().collection('users')
.orderBy('created', 'desc')
.endBefore(item.created) //this is important when we go back
.limitToLast(5) //this is important when we go back
.onSnapshot(function(querySnapshot) {
const items = [];
querySnapshot.forEach(function(doc) {
items.push({ key: doc.id, ...doc.data() });
});
setList(items);
setPage(page - 1)
})
};
fetchPreviousData();
};
at the end create list view & two buttons like this
{
//list doc's here this will come inside return (place this code inside table)
list.map((doc) => (
<tr key={doc.key}>
<td>{ doc.name }</td>
<td>{ doc.age }</td>
<td>{ doc.note }</td>
</tr>
))
}
{
//show previous button only when we have items
//pass first item to showPrevious function
page === 1 ? '' :
<Button onClick={() => showPrevious({ item: list[0] }) }>Previous</Button>
}
{
//show next button only when we have items
//pass last item to showNext function
list.length < 5 ? '' :
<Button onClick={() => showNext({ item: list[list.length - 1] })}>Next</Button>
}
That's it check my code comments where you can change as per your need. this is what happens when you paginate using Firebase FireStore. you can use create custom hook to reuse these component as per your need.
Hope this could help someone so i made a gist check it here
here AddTable and AddForm is adding table and add form to fill data in table...
import React, { useEffect, useState } from "react";
import Button from "react-bootstrap/Button";
import Pagination from "react-bootstrap/Pagination";
import AddTable from "../management/AddTable";
import AddForm from "../management/AddSuperAdminForm";
import {
where,
getDocs,
collection,
query,
orderBy,
startAfter,
limit,
endBefore,
limitToLast,
} from "firebase/firestore";
import { db_firestore } from "../../../firebase.config";
const SuperAdmin = () => {
const [tableDataArray, setTableDataArray] = useState();
const [show, setShow] = useState(false);
const [editId, setEditId] = useState("");
const [oldUid, setOldUid] = useState("");
const [lastVisible, setLastVisible] = useState();
const [prevVisible, setPrevVisible] = useState();
const handleClose = () => {
setShow(false);
setEditId("");
};
const handleShow = () => {
setShow(true);
setEditId("");
};
let tempdata;
let pageSize = 3;
let q = query(
collection(db_firestore, "users"),
where("role", "==", "superadmin"),
orderBy("timestamps", "desc"),
limit(pageSize)
);
function nextPage(lastVisible) {
q = query(
collection(db_firestore, "users"),
where("role", "==", "superadmin"),
orderBy("timestamps", "desc"),
startAfter(lastVisible),
limit(pageSize)
);
}
function prevPage(firstVisible) {
q = query(
collection(db_firestore, "users"),
where("role", "==", "superadmin"),
orderBy("timestamps", "desc"),
endBefore(firstVisible),
limitToLast(pageSize + 1)
);
}
const newfun = async () => {
const querySnapshot = await getDocs(q);
tempdata = [];
// Get the last visible document
setLastVisible(querySnapshot.docs[querySnapshot.docs.length - 1]);
// Get the prev visible document
setPrevVisible(querySnapshot.docs[0]);
querySnapshot.forEach((doc) => {
const { name, email, uid } = doc.data();
tempdata.push([name, email, uid, doc.id]);
});
console.log("SuperAdmin...");
setTableDataArray(tempdata);
};
useEffect(() => {
newfun();
// setInterval(() => { // if you want to get new update after some secound
// newfun();
// }, 10000);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div>
<Button
className="d-block mx-auto my-2"
variant="primary"
onClick={handleShow}
>
Add SuperAdmin
</Button>
{/* -----> AddTable <------
Index will generate Automatic In Table.
Always keep action end of the table.
*/}
{tableDataArray ? (
<AddTable
tableHeaders={["Name", "Email", "uid", "Action"]}
tableData={tableDataArray}
fetchNew={newfun}
setEditId={setEditId}
setShow={setShow}
setOldUid={setOldUid}
/>
) : (
""
)}
<AddForm
fetchNew={newfun}
show={show}
setShow={setShow}
handleClose={handleClose}
editId={editId}
oldUid={oldUid}
/>
<Pagination className="float-end">
<Pagination.Item
className="shadow-none"
size="lg"
onClick={() => {
prevPage(prevVisible);
newfun();
}}
>
Previous
</Pagination.Item>
<Pagination.Item
className="shadow-none"
size="lg"
onClick={() => {
nextPage(lastVisible);
newfun();
}}
>
Next
</Pagination.Item>
</Pagination>
</div>
);
};
export default SuperAdmin;
Use startAt() or startAfter() for that
firestore
.collection("Users")
.startAt(0)
.limit(10)
.get()