I want to add a list of topics into the firebase database. Each user in the database will be able to add a list of topics that are unique to them.
It adds the data correctly but when I refresh it and try to add another item in the list, the list in the database is erased, and overwritten. How can I fix this?
constructor(props){
super(props);
this.state = {
list:[],
};
}
onSubmitL = e => {
e.preventDefault();
const db = firebase.firestore();
var change = this.state.list;
//this.state.list.push(this.state.temp);
var textbox = document.getElementById("list");
var temp = textbox.value;
if (temp.length == 0) {
console.log("input empty!");
} else {
this.state.list.push(temp);
}
console.log("current " + this.state.list);
db.collection("users").where("email", "==", firebase.auth().currentUser.email)
.get()
.then(snapshots => {
snapshots.forEach(doc => {
const docData = doc.data();
doc.ref.update({
list: this.state.list,
});
})
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
this.state.item="";
};
updateList = e => {
};
render () {
const { currentUser } = this.state
return (
<div>
<form onSubmit={this.onSubmitL}>
<input
name="topics"
id="list"
onChange={this.onChange}
type='list'
placeholder="list"
onChange={this.updateList}
/>
<button type="submit">Apply Changes</button>
</form>
<div>
</div>
</div>
);
}
I dont know if this is the only problem but it should be
snapshots.docs.forEach(doc => { ...
not
snapshots.forEach(doc => { ...
you might be able to try using a ... doc(doc.id)set()with { merge: true }
db.collection("users").where("email", "==",
firebase.auth().currentUser.email)
.get()
.then(snapshots => {
snapshots.docs.forEach(doc => {
const docData = doc.data();
doc.ref.update({
list: this.state.list,
});
})
})
see https://firebase.google.com/docs/firestore/manage-data/add-data?authuser=0
Related
So this is my app which i have created in react and store data in firestore, i have a form in which ingredients is an array, i can dynamically add the input feilds and when i submit the form it gets submiited and the data gets stored in firebase. My problem is when i click the add feild button instead of one feild two feilds are simultaneously created and i am unable to understand how to do that so if anyone can explain what to do thanks .
code :
function App() {
const [recipes, setRecipes] = useState([])
const [form, setForm] = useState({
ingredients: [],
})
const [popupActive, setPopupActive] = useState(false)
const recipesCollectionRef = collection(db, "recipes")
useEffect(() => {
onSnapshot(recipesCollectionRef, snapshot => {
setRecipes(snapshot.docs.map(doc => {
return {
id: doc.id,
viewing: false,
...doc.data()
}
}))
})
}, [])
const handleView = id => {
const recipesClone = [...recipes]
recipesClone.forEach(recipe => {
if (recipe.id === id) {
recipe.viewing = !recipe.viewing
} else {
recipe.viewing = false
}
})
setRecipes(recipesClone)
}
const handleSubmit = e => {
e.preventDefault()
if (
!form.ingredients ||
) {
alert("Please fill out all fields")
return
}
addDoc(recipesCollectionRef, form)
setForm({
ingredients: [],
})
setPopupActive(false)
}
const handleIngredient = (e, i) => {
const ingredientsClone = [...form.ingredients]
ingredientsClone[i] = e.target.value
setForm({
...form,
ingredients: ingredientsClone
})
}
const handleIngredientCount = () => {
setForm({
...form,
ingredients: [...form.ingredients, ""]
})
{ recipe.viewing && <div>
<h4>Ingredients</h4>
<ul>
{ recipe.ingredients.map((ingredient, i) => (
<li key={i}>{ ingredient }</li>
))}
</ul>
As far as I have understood. Just do like below,
const handleIngredientCount = () => {
setForm({
...form,
ingredients: [...form.ingredients, "", ""],
})
}
You will be created with two input fields simultaneously instead of one when you click the add ingredient button.
I am trying to delete multiple items on click of checkbox using firestore. But, onSnapshot method of firestore is causing issue with the state.
After running the code I can click on checkbox and delete the items, the items get deleted too but I get an error page, "TyperError: this.setState is not a function" in onCollectionUpdate method.
After refreshing the page I can see the items deleted.
Here's my code:
class App extends React.Component {
constructor(props) {
super(props);
this.ref = firebase.firestore().collection('laptops');
this.unsubscribe = null;
this.state = { laptops: [], checkedBoxes: [] };
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.deleteProducts = this.deleteProducts.bind(this);
}
toggleCheckbox = (e, laptop) => {
if (e.target.checked) {
let arr = this.state.checkedBoxes;
arr.push(laptop.key);
this.setState = { checkedBoxes: arr };
} else {
let items = this.state.checkedBoxes.splice(this.state.checkedBoxes.indexOf(laptop.key), 1);
this.setState = {
checkedBoxes: items
}
}
}
deleteProducts = () => {
const ids = this.state.checkedBoxes;
ids.forEach((id) => {
const delRef = firebase.firestore().collection('laptops').doc(id);
delRef.delete()
.then(() => { console.log("deleted a laptop") })
.catch(err => console.log("There is some error in updating!"));
})
}
onCollectionUpdate = (querySnapshot) => {
const laptops = [];
querySnapshot.forEach((doc) => {
const { name, price, specifications, image } = doc.data();
laptops.push({
key: doc.id,
name,
price,
specifications,
image
});
});
this.setState({ laptops });
console.log(laptops)
}
componentDidMount = () => {
this.unsubscribe = this.ref.onSnapshot(this.onCollectionUpdate);
}
getLaptops = () => {
const foundLaptops = this.state.laptops.map((laptop) => {
return (
<div key={laptop.key}>
<Container>
<Card>
<input type="checkbox" className="selectsingle" value="{laptop.key}" checked={this.state.checkedBoxes.find((p) => p.key === laptop.key)} onChange={(e) => this.toggleCheckbox(e, laptop)} />
...carddata
</Card>
</Container>
</div>
);
});
return foundLaptops;
}
render = () => {
return (
<div>
<button type="button" onClick={this.deleteProducts}>Delete Selected Product(s)</button>
<div className="row">
{this.getLaptops()}
</div>
</div>
);
}
}
export default App;
In the toggleCheckbox function you set the this.setState to a object.
You will need to replace that with this.setState({ checkedBoxes: items})
So you use the function instead of setting it to a object
You probably just forgot to bind the onCollectionUpdate so this referes not where you expectit to refer to.
Can you pls also change the this.setState bug you have there as #David mentioned also:
toggleCheckbox = (e, laptop) => {
if (e.target.checked) {
let arr = this.state.checkedBoxes;
arr.push(laptop.key);
this.setState({ checkedBoxes: arr });
} else {
let items = this.state.checkedBoxes.splice(this.state.checkedBoxes.indexOf(laptop.key), 1);
this.setState({
checkedBoxes: items
})
}
}
If you already did that pls update your question with the latest code.
I am trying to set the state of a variable that is initially populated from an API call...
getInServiceVenues = () =>{
this.setState({ loading: true });
let bodyInService = [];
let iterationInService = 0;
let RequestingNetworkOperatorList = [];
axios.post( '/propertymanagement/listVenues', bodyInService,
{
headers: {}
})
.then( response => {
if (this._isMounted) {
this.setState({loading: false});
this.setState({venues: []});
const venuesInService = response.data.InServiceVenueList;
let venueIdInService = null;
let updatedVenuesInService = [];
bodyInService.push(venuesInService);
bodyInService.forEach(val=>{
venueIdInService = Object.keys(bodyInService[0]);
//console.log(venueId);
})
if(this.state.venuesInService!==[]){
this.setState({venuesInService:[]});
}
venueIdInService.forEach(val=>{
updatedVenuesInService = bodyInService.map(venueInService => {
return {
...venueInService[venueIdInService[iterationInService]],
iterationInService,
RequestingNetworkOperatorList
}
});
this.setState({
venuesInService:[...this.state.venuesInService, updatedVenuesInService]
});
iterationInService = iterationInService + 1;
})
}} )
.catch(error => {
console.log(error);
this.setState({error: true});
});
}
On the click of a button I call this function...
postSelectedHandler = (venues) => {
this.setState({newVenue: true}, () => {
console.log(this.state.newVenue);
});
this.getInServiceVenues();
var event = new Event('input', { bubbles: true });
this.search_venue.dispatchEvent(event);
this.filterList(event);
}
I can only see the updated state if I were to change an input box's value which is why I have the event variable in my function (trying to do an onChange automatically). Any help would be appreciated.
Edited with the render method attached...
render () {
//console.log(this.state.allVenues);
const { isBoxVisible } = this.state;
const {loading } = this.state;
let inServiceVenues = <Alert variant="danger">Something went wrong!</Alert>;
let preAuthVenues = <Alert variant="danger">Something went wrong!</Alert>;
let waitingAuthVenues = <Alert variant="danger">Something went wrong!</Alert>;
let availableVenues = <Alert variant="danger">Something went wrong!</Alert>;
if (!this.state.error) {
inServiceVenues = this.state.filterVenuesInService.map(venueInService => {
return <Venue
key={venueInService[0].iteration}
city={venueInService[0].City}
VenueName={venueInService[0].VenueName}
NetworkOperatorID={venueInService[0].NetworkOperatorID}
ConnectedKeyPools={venueInService[0].connectedKeyPoolList}
clicked={() => this.postSelectedHandler(venueInService.id)} />;
});
preAuthVenues = this.state.filterVenuesPreAuth.map(venuePreAuth => {
return <PreAuthVenues
key={venuePreAuth[0].iteration}
city={venuePreAuth[0].City}
VenueName={venuePreAuth[0].VenueName}
NetworkOperatorID={venuePreAuth[0].NetworkOperatorID}
ConnectedKeyPools={venuePreAuth[0].connectedKeyPoolList}
clicked={() => this.postSelectedHandler(venuePreAuth.id)} />;
});
waitingAuthVenues = this.state.filterVenuesWaiting.map(venueWaitingAuth => {
//console.log(venueWaitingAuth[0].RequestingNetworkOperatorList[0]);
let connectedKeyPoolListNewLine;
if(venueWaitingAuth[0].connectedKeyPoolList!=undefined){
connectedKeyPoolListNewLine = JSON.stringify(venueWaitingAuth[0].connectedKeyPoolList, "<p>").replace(/[[\]"']+/g,'').replace(/,+/g, '\n');
//console.log(connectedKeyPoolListNewLine.replace(/\n,+/g, ''));
}else{
connectedKeyPoolListNewLine = '';
}
return <WaitingAuthVenues
key={venueWaitingAuth[0].iterationWaitingAuth}
city={venueWaitingAuth[0].City}
VenueName={venueWaitingAuth[0].VenueName}
VenueID={venueWaitingAuth[0].awaitingAuthVenueID}
connectedKeyPoolList={connectedKeyPoolListNewLine}
RequestedOperatorList={Object.keys(venueWaitingAuth[0].RequestingNetworkOperatorList)}
AwaitingAuthorizationKeyPool={venueWaitingAuth[0].AwaitingAuthorizationFromKeyPoolOwnerKeyPoolList}
filteredVenuesInService={inServiceVenues}
clicked={() => this.postSelectedHandler(venueWaitingAuth.id)} />;
});
availableVenues = this.state.filterVenuesAvailable.map(venue => {
let connectedKeyPoolListNewLine;
if(venue[0].connectedKeyPoolList!=undefined){
connectedKeyPoolListNewLine = JSON.stringify(venue[0].connectedKeyPoolList, "<p>").replace(/[[\]"']+/g,'').replace(/,+/g, '\n');
//console.log(connectedKeyPoolListNewLine.replace(/\n,+/g, ''));
}else{
connectedKeyPoolListNewLine = '';
}
//console.log(connectedKeyPoolList);
return <AvailableVenues
key={venue[0].iteration}
city={venue[0].City}
VenueName={venue[0].VenueName}
connectedKeyPoolList={connectedKeyPoolListNewLine}
id={venue[0].availableVenueID}
clicked={() => this.postSelectedHandler(availableVenues.id)}
submitted={this.forceUpdateHandler}
getNewData={this.getAvailableVenues}/>;
});
//console.log(this.state.venues[0]);
}
I'm using React to build a website. On one of the pages, users can see all users' profiles and select tags to filter them. In addition to the tags selected, I also keep the list of users displayed in the state of the component for another feature. Therefore, when the user clicks or un-clicks a tag, not only the list of tags (called "filters") but also the currently displayed users' profiles (called "currentprofilelist") will be changed. Below is my code to achieve this feature: "updateTasks" is called when the user clicks a tag, which calls "onlyFilters" to change the "filters" field in the state. Then "onlyFilters" returns the new filters, which is passed to filtersUpdateList to modify the "currentprofilelist" in the state. I got the error: "Unhandled Rejection (TypeError): this.onlyFilters(...).then is not a function" and am wondering how I can fix it. I tried adding "return" before it and it still did not work. Thanks for your patience!
const filters = [
......
]
state = {
......
filters: [],
currentprofilelist: []
}
componentDidMount() {
const { users } = this.props;
this.setState({
currentprofilelist: users
})
}
......
onlyFilters = (e) => {
if (e.target.className === "unselected-button") {
const helper = this.state.filters;
helper.push(e.target.id);
this.setState({ filters : helper });
e.target.className = "selected-button";
return helper;
}
else {
const newfilters = this.state.filters.filter(tag => tag !== e.target.id);
this.setState({ filters : newfilters });
e.target.className = "unselected-button";
return newfilters;
}
}
filtersUpdateList = (newfilters) => {
const { users } = this.props;
var newprofilelist = [];
var i = 0;
while (i < users.length) {
if (newfilters.every(t => users[i].profile.areas_of_interest && users[i].profile.areas_of_interest.includes(t))) {
newprofilelist.push(users[i].id);
}
}
this.setState({currentprofilelist: newprofilelist});
}
updateFilters = async (e) => {
e.preventDefault();
this.onlyFilters(e)
.then((newfilters) => {
this.filtersUpdateList(newfilters);
})
}
......
render() {
const { auth, users, profile } = this.props;
return (
......
<Container fluid style={{ marginLeft:"70px", display: "flex", alignItems: "center", zIndex: "-1"}}>
<div className="filter-container">
<li>Filter by interests!</li>
<div className="form-inline">
{filters.map(filter => {
return (
<div className="unselected-button" id={filter} onClick={this.updateFilters}>
{filter}
</div>
);
})}
</div>
</div>
......
</Container>
......
);
}
}
const mapStateToProps = (state) => {
return {
users: state.firestore.ordered.users,
auth: state.firebase.auth,
profile: state.firebase.profile
}
}
......
Your onlyFilters method should be async function to use its result with promise, so try to declare it like:
onlyFilters = async () => {....}
or
async onlyFilters() {....}
My ArticleList component is successfully getting & displaying the user's list of articles from firestore when I first load the app. The user can click a "Remove Article" button, which successfully removes the article from the subcollection in firestore, but it causes an error in the rendering of the react component, which seems to still be trying to render the article that was just removed and is now null. Is there something else I can do to make my react component continuously listen to the firestore data? If possible, I'd like to keep this a functional component and use hooks rather than making it a class, but I'm still learning how to use react hooks and therefore struggling a bit.
ArticleList component:
const ArticleList = (props) => {
const firestore = useFirestore();
const userId = props.auth.uid;
useFirestoreConnect([
{
collection: 'users',
doc: userId,
subcollections: [{collection: 'articles'}],
storeAs: userId + '::articles'
}
]);
const myArticles = useSelector(state => state.firestore.data[`${userId}::articles`]);
const dispatch = useDispatch();
const removeArticle = useCallback(
articleId => dispatch(removeArticleFromFirebase({ firestore }, articleId)),
[firestore]
);
if (props.auth.uid) {
return(
<div>
<h3>My Articles</h3>
<p>Currently signed in: {props.auth.email}</p>
<br/>
{myArticles ? (
Object.keys(myArticles).map(articleId => {
let article = myArticles[articleId];
let articleInformation = '';
if (articleId === props.currentPaperId) {
articleInformation =
<div>
<p>{article.year}</p>
<p>{article.description}</p>
<a target="_blank" href={article.downloadUrl}><button className='waves-effect waves-light btn-small'>See article</button></a>
<button className='waves-effect waves-light btn-small' onClick={() => {removeArticle(articleId);}}>Remove from My Articles</button>
</div>;
}
let authorName = '';
if (article.author) {
authorName = ` by ${article.author}`;
}
if (article) {
return <span key={articleId}>
<li onClick={() => {dispatch(selectArticle(articleId));}}>
<em>{article.title}</em>{authorName}
</li>{articleInformation}
</span>;
} else {
return null;
}
})
) : (
<h4>No articles yet</h4>
)
}
</div>
);
} else {
return null;
}
};
const mapStateToProps = (state) => {
return {
currentPaperId: state.currentPaperId,
auth: state.firebase.auth
};
};
export default compose(connect(mapStateToProps))(ArticleList);
And the removeArticleFromFirebase action:
export const removeArticleFromFirebase = ({ firestore }, id) => {
return (dispatch, getState) => {
const userId = getState().firebase.auth.uid;
firestore
.collection('users')
.doc(userId)
.collection('articles')
.doc(id)
.delete()
.then(() => {
console.log('Deleted article from firestore: ', id);
dispatch({ type: 'REMOVE_ARTICLE', id });
})
.catch(err => {
console.log('Error: ', err);
});
};
}
I've tried adding useState and useEffect in the ArticleList as follows (and tried having the component's return statement map through myArticlesState instead of myArticles), but no success:
const [myArticlesState, setMyArticlesState] = useState(myArticles);
useEffect(() => {
setMyArticlesState(myArticles);
}, [myArticles]);
Note: I do not currently have this article list in overall app state/redux store/props at all. This is something I was thinking of trying next, but I decided to post my question first in case I can just use hooks in this component. No other components/parts of the app need access to this particular list.
Console errors:
error image 1
error image 2
Github repo: https://github.com/jpremmel/yarp2.0
It's kind of difficult to see what's going on but it appears as though you are trying to use a property on an object that does not exist. Therefore, checking for those properties should help resolve this.
Can you try the follow code as your ArticleList?
const ArticleList = (props) => {
const firestore = useFirestore();
const userId = props.auth.uid;
useFirestoreConnect([{
collection: 'users',
doc: userId,
subcollections: [{ collection: 'articles' }],
storeAs: userId + '::articles'
}]);
const myArticles = useSelector(state => state.firestore.data[`${userId}::articles`]);
const dispatch = useDispatch();
const removeArticle = useCallback(articleId => dispatch(removeArticleFromFirebase({ firestore }, articleId)), [firestore]);
if (props.auth.uid) {
return (
<div>
<h3>My Articles</h3>
<p>Currently signed in: {props.auth.email}</p>
<br />
{myArticles ? (
Object.keys(myArticles).map(articleId => {
let article = myArticles[articleId];
let articleInformation = '';
if (article) {
if (
articleId === props.currentPaperId &&
article.hasOwnProperty('year') &&
article.hasOwnProperty('description') &&
article.hasOwnProperty('downloadUrl')
) {
articleInformation =
<div>
<p>{article.year}</p>
<p>{article.description}</p>
<a target="_blank" href={article.downloadUrl}><button className='waves-effect waves-light btn-small'>See article</button></a>
<button className='waves-effect waves-light btn-small' onClick={() => { removeArticle(articleId); }}>Remove from My Articles</button>
</div>;
}
let authorName = '';
if (article.hasOwnProperty('author') && article.author) {
authorName = ` by ${article.author}`;
}
if (article.hasOwnProperty('title') && article.title) {
return <span key={articleId}>
<li onClick={() => { dispatch(selectArticle(articleId)); }}>
<em>{article.title}</em>{authorName}
</li>{articleInformation}
</span>;
} else {
return null;
}
}
})
) : (
<h4>No articles yet</h4>
)
}
</div>
);
} else {
return null;
}
};
const mapStateToProps = (state) => {
return {
currentPaperId: state.currentPaperId,
auth: state.firebase.auth
};
};
export default compose(connect(mapStateToProps))(ArticleList);
Can you show us the error? I think it's about the state not being an array after you delete your data just initialize your state with an empty array like this :
Const= [articlesdata,setArticlesData]=useState([])
And leave the useEffect as it is