I am building a chat application using firebase and react. The problem facing is that the input data is not getting added to the firebase firestore collection. The collection is named messages.
The output is like the input is getting displayed and disappeared as it is not saved in the database.
The code is given below:
import React, { useRef, useState } from 'react';
import './App.css';
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
import 'firebase/analytics';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useCollectionData } from 'react-firebase-hooks/firestore';
firebase.initializeApp({
//firebase_credentials
})
const auth = firebase.auth();
const firestore = firebase.firestore();
function App() {
const [user] = useAuthState(auth);
return (
<div className="App">
<header>
<h1>SuperChat 💬</h1>
<SignOut />
</header>
<section>
{user ? <ChatRoom /> : <SignIn />}
</section>
</div>
);
}
function SignIn() {
const signInWithGoogle = () => {
const provider = new firebase.auth.GoogleAuthProvider();
auth.signInWithPopup(provider);
}
return (
<>
<button className="sign-in" onClick={signInWithGoogle}>Sign in with Google</button>
<p>Don't wait, messages are gonna disappear in a while</p>
</>
)
}
function SignOut() {
return auth.currentUser && (
<button className="sign-out" onClick={() => auth.signOut()}>Sign Out</button>
)
}
function ChatRoom() {
const dummy = useRef();
const messagesRef = firestore.collection('messages');
const query = messagesRef.orderBy('createdAt').limit(25);
const [messages] = useCollectionData(query, { idField: 'id' });
const [formValue, setFormValue] = useState('');
const sendMessage = async (e) => {
e.preventDefault();
const { uid, photoURL } = auth.currentUser;
await messagesRef.add({
text: formValue,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
uid,
photoURL
})
setFormValue('');
dummy.current.scrollIntoView({ behavior: 'smooth' });
}
return (<>
<main>
{messages && messages.map(msg => <ChatMessage key={msg.id} message={msg} />)}
<span ref={dummy}></span>
</main>
<form onSubmit={sendMessage}>
<input value={formValue} onChange={(e) => setFormValue(e.target.value)} placeholder="say something nice" />
<button type="submit" disabled={!formValue}>send</button>
</form>
</>)
}
function ChatMessage(props) {
const { text, uid, photoURL } = props.message;
const messageClass = uid === auth.currentUser.uid ? 'sent' : 'received';
return (<>
<div className={`message ${messageClass}`}>
<img src={photoURL || 'https://feedback.seekingalpha.com/s/cache/ff/f2/fff2493e0063ac43f7161be10e0d7fff.png'} />
<p>{text}</p>
</div>
</>)
}
export default App
The issue is that you are not properly setting the object to be added as a document in Firestore in the messagesRef.add() method. If you check this documentation you can see that the add() method should have a structure like:
db.collection("collectionName").add({
field1: "someValue1",
field2: "someValue2",
...
fieldN: "someValueN"
})
So what you should do in your code is the following:
await messagesRef.add({
text: formValue,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
photoURL : "someURLvalue"
})
NOTE: I took out the uid from the example above because using add() the ID is automatically generated.
Related
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
I'm doing an e-commerce app in which you rent tuxedos but when I go to the payments page it goes blank. It happened when I installed Stripe API on my app and it became buggy in the specific page. In this version of React, I tried to put on the payment page but it goes blank. Can you guys help me solve this problem please?
Here's my code on App.js:
import './App.css';
import Header from './Header.js';
import Home from './Home.js';
import Checkout from './Checkout.js';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Login from './Login';
import { useEffect } from 'react';
import { auth } from './firebase';
import { useStateValue } from './StateProvider';
import Payment from './Payment';
import { loadStripe } from '#stripe/stripe-js';
import { Elements } from '#stripe/react-stripe-js';
const promise = loadStripe('some stripe api here');
function App() {
const [{}, dispatch] =useStateValue();
useEffect(() => {
//Only run once the app component logs
auth.onAuthStateChanged(authUser => {
console.log('User is signed in', authUser)
if (authUser) {
dispatch({
type:'SET_USER',
user: authUser
})
} else {
dispatch({
type:'SET_USER',
user: null
})
}
})
}, [])
return (
//BEM
<Router>
<div className="app">
<Routes>
<Route path="/login" element={[<Login />]}/>
<Route path="/checkout" element={[<Header />, <Checkout />]}/>
<Route path="/payment" element={[<Header />, <Elements stripe={promise} />, <Payment />]}/>
<Route path="/" element={[<Header />, <Home />]}/>
</Routes>
</div>
</Router>
);
}
export default App;
Now here's my code on the Payment page (Payment.js):
import { CardElement, useElements, useStripe } from '#stripe/react-stripe-js';
import React, { useEffect, useState } from 'react';
import CurrencyFormat from 'react-currency-format';
import { Link, useNavigate } from 'react-router-dom';
import CheckoutProduct from './CheckoutProduct';
import './Payment.css';
import { useStateValue } from './StateProvider';
import { getCartTotal } from './reducer';
import axios from 'axios';
function Payment() {
const [{cart, user}, dispatch] = useStateValue();
const navigate = useNavigate();
const stripe = useStripe();
const elements = useElements();
const [succeeded, setSucceeded] = useState(false);
const [processing, setProcessing] = useState("");
const [error, setError] = useState(null);
const [disabled, setDisabled] = useState(true);
const [clientSecret, setClientSecret] = useState(true);
useEffect(() => {
const getClientSecret = async() => {
const response = await axios({
method: 'post',
url: `/payments/create?total=${getCartTotal(cart) * 100}`
});
setClientSecret(response.data.clientSecret)
}
getClientSecret();
}, [cart])
const handleSubmit = async(event) => {
event.preventDefault();
setProcessing(true);
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method : {
card: elements.getElement(CardElement)
}
}).then(({paymentIntent}) => {
setSucceeded(true);
setError(null)
setProcessing(false)
navigate('/orders', {replace:true});
})
}
const handleChange = event => {
setDisabled(event.empty);
setError(event.error ? event.error.message : '');
}
return (
<div className='payment'>
<div className='payment_container'>
<h1> Checkout (<Link to='/checkout'> {cart?.length} items </Link>) </h1>
{/* Payment section - Delivery address */}
<div className='payment_section'>
<div className='payment_title'>
<h3> Delivery Address </h3>
</div>
<div className='payment_address'>
<p> {user?.email} </p>
<p> 123 Elvis Lane </p>
<p> Austin, Texas </p>
</div>
</div>
{/* Payment section - Review items */}
<div className='payment_section'>
<div className='payment_title'>
<h3> Review items and delivery </h3>
<div className='payment_items'>
{cart.map(item => (
<CheckoutProduct
id = {item.id}
title = {item.title}
image = {item.image}
price = {item.price}
rating = {item.rating}
/>
))}
</div>
</div>
</div>
{/* Payment section - Payment method */}
<div className='payment_section'>
<div className='payment_title'>
<h3> Payment Method </h3>
<div className='payment_details'>
{/* Stripe API */}
<form onSubmit={handleSubmit}>
<CardElement onChange={handleChange} />
<div className='payment_priceContainer'>
<CurrencyFormat
renderText={(value) => (
<>
<h3> Order Total: {value} </h3>
</>
)}
decimalScale={2}
value= {getCartTotal(cart)}
displayType={"text"}
thousandSeparator={true}
prefix={"$"}
/>
<button disabled={processing || disabled || succeeded}>
<span> {processing ? <p> Processing </p> : "Buy Now"} </span>
</button>
</div>
{error && <div>{error}</div>}
</form>
</div>
</div>
</div>
</div>
</div>
)
}
export default Payment
Is this an error on App.js or is it in Payment.js? The page should display the info and the payment form.
Edit: I found out it was in the Payment.js code somewhere around here:
const navigate = useNavigate();
const stripe = useStripe();
const elements = useElements();
const [succeeded, setSucceeded] = useState(false);
const [processing, setProcessing] = useState("");
const [error, setError] = useState(null);
const [disabled, setDisabled] = useState(true);
const [clientSecret, setClientSecret] = useState(true);
useEffect(() => {
const getClientSecret = async() => {
const response = await axios({
method: 'post',
url: `/payments/create?total=${getCartTotal(cart) * 100}`
});
setClientSecret(response.data.clientSecret)
}
getClientSecret();
}, [cart])
const handleSubmit = async(event) => {
event.preventDefault();
setProcessing(true);
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method : {
card: elements.getElement(CardElement)
}
}).then(({paymentIntent}) => {
setSucceeded(true);
setError(null)
setProcessing(false)
navigate('/orders', {replace:true});
})
}
const handleChange = event => {
setDisabled(event.empty);
setError(event.error ? event.error.message : '');
Can you guys help me fix this please? It seems that in this section is where the error is occurring.
Edit 2:
Here's of how it should look like:
Here's what actually happens:
Edit 3: Here's what the console gives me as an error, maymbe it is in the elements tag that causes the problem.
It looks like you need to wrap your checkout page in an Elements provider:
To use the Payment Element component, wrap your checkout page component in an Elements provider. Call loadStripe with your publishable key, and pass the returned Promise to the Elements provider. Also pass the client secret from the previous step as options to the Elements provider.
The sample code Stripe provides shows how to properly structure your app:
import React from 'react';
import ReactDOM from 'react-dom';
import {Elements} from '#stripe/react-stripe-js';
import {loadStripe} from '#stripe/stripe-js';
import CheckoutForm from './CheckoutForm';
// Make sure to call `loadStripe` outside of a component’s render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('pk_test_123');
function App() {
const options = {
// passing the client secret obtained in step 2
clientSecret: '{{CLIENT_SECRET}}',
// Fully customizable with appearance API.
appearance: {/*...*/},
};
return (
<Elements stripe={stripePromise} options={options}>
<CheckoutForm />
</Elements>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
import React from 'react';
import {PaymentElement} from '#stripe/react-stripe-js';
const CheckoutForm = () => {
return (
<form>
<PaymentElement />
<button>Submit</button>
</form>
);
};
export default CheckoutForm;
I have one component with a form that creates posts and then I have another component that displays these posts in a feed. How do I get the feed to re-render when a new post is created? What's the best way to do that?
Re-render the parent component without changing props?
Update props?
Some other way?
Create.js
import React, { useState } from "react";
export default function Create () {
const [form, setForm] = useState({
post: "",
});
// These methods will update the state properties.
function updateForm(value) {
return setForm((prev) => {
return { ...prev, ...value };
});
}
async function onSubmit(e) {
e.preventDefault();
// When a post request is sent to the create url, we'll add a new post to the database.
const newPost = { ...form };
await fetch("http://localhost:3000/post/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newPost),
})
.catch(error => {
window.alert(error);
return;
});
setForm({ post: "" });
}
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="post">Post</label>
<input
type="text"
className="form-control"
id="post"
value={form.post}
onChange={(e) => updateForm( { post: e.target.value })}
/>
</div>
<div className="form-group">
<input
type="submit"
value="Create post"
className="btn btn-primary"
/>
</div>
</form>
</div>
);
}
Feed.js
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
const Post = (props) => (
<div>
{props.post.post}
<Link className="btn btn-link" to={`/edit/${props.post._id}`}>Edit</Link>
<button className="btn btn-link" onClick={() => { props.deletePost(props.post._id);
}}
>
Delete
</button>
</div>
);
export default function PostList() {
const [posts, setPosts] = useState([]);
// This method fetches the posts from the database.
useEffect(() => {
async function getPosts() {
const response = await fetch(`http://localhost:3000/post/`);
if (!response.ok) {
const message = `An error occured: ${response.statusText}`;
window.alert(message);
return;
}
const posts = await response.json();
setPosts(posts);
}
getPosts();
return;
}, [posts.length]);
// This method will delete a post
async function deletePost(id) {
await fetch(`http://localhost:3000/${id}`, {
method: "DELETE"
});
const newPosts = posts.filter((el) => el._id !== id);
setPosts(newPosts);
}
// This method will map out the posts on the table
function postList() {
return posts.map((post) => {
return (
<Post
post={post}
deletePost={() => deletePost(post._id)}
key={post._id}
/>
);
});
}
// This following section will display the the posts.
return (
<div>
<h3>Post List</h3>
<div>{postList()}</div>
</div>
);
}
Home.js
import React from "react";
import Feed from "./feed";
import Create from "./create";
const Home = () => {
return (
<div className="app">
<div>
<Create />
<Feed />
</div>
</div>
);
};
export default Home;
There are numerous ways to do so. The one you should start with is the most straightforward: pass your current posts and setPosts into both component from a parent component:
const Parent = () => {
const [posts, setPosts] = useState([]);
return <>
<Create posts={posts} setPosts={setPosts} />
<PostList posts={posts} setPosts={setPosts} />
</>
}
There are better ways to do so, but they are more involved. I suggest you to start with this one to understand how React handles data better.
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
import React, { useState } from 'react'
import Display from './components/Display';
const App = () => {
const [input,setInput] = useState("");
const getData = async () => {
const myAPI = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${input}&units=metric&appid=60dfee3eb8199cac3e55af5339fd0761`);
const response = await myAPI.json();
console.log(response); //want to use response as a prop in Display component
}
return(
<div className="container">
<h1>Weather Report</h1>
<Display title={"City Name :"} /> //here
<Display title={"Temperature :"} /> //here
<Display title={"Description :"} /> //here
<input type={input} onChange={e => setInput(e.target.value)} className="input"/>
<button className="btn-style" onClick={getData}>Fetch</button>
</div>
);
}
export default App;
I don't know if I understand you correctly but if I'm right you want to access data returned from your function that is fetching from API, if so you can try this way
import React, { useState, useEffect } from 'react'
import Display from './components/Display';
import axios from 'axios';
const App = () => {
const [input,setInput] = useState("");
const [state, setState] = useState({loading: true, fetchedData: null});
useEffect(() => {
getData();
}, [setState]);
async function getData() {
setState({ loading: true });
const apiUrl = 'http://api.openweathermap.org/data/2.5/weather?q=${input}&units=metric&appid=60dfee3eb8199cac3e55af5339fd0761';
await axios.get(apiUrl).then((repos) => {
const rData = repos.data;
setState({ loading: false, fetchedData: rData });
});
}
return(
state.loading ? <CircularProgress /> : (
<List className={classes.root}>
{ state.fetchedData.map((row) => (
<div className="container">
<h1>Weather Report</h1>
<Display title={"City Name :" + row.cityName } /> //here
<Display title={"Temperature :" + row.temperature} /> //here
<Display title={"Description :" + row.description} /> //here
</div>
)) }
</List>
)
);
}