How to put image with description with Firebase and next.js? - javascript

i'am using firebase 9 and next.js 13 to make crud app and i want to make "post" with image in it, but seems like the image didn't follow the post id, and how do i display the image in other components ? because i have separate "post" and "blog" components. the image successfully store in firebase and shown at "post" components
Here is my code:
const [post, setPost] = useState({ description: "" });
const [user, loading] = useAuthState(auth);
const [imageUpload, setImageUpload] = useState(null);
const [imageList, setImageList] = useState([]);
const route = useRouter();
const imageListRef = ref(storage, "posts/");
const routeData = route.query;
//Post
const submitPost = async (e) => {
e.preventDefault();
//Make new post
const collectionRef = collection(db, "posts");
await addDoc(collectionRef, {
...post,
timestamp: serverTimestamp(),
user: user.uid,
avatar: user.photoURL,
username: user.displayName,
}).then((document) => {
//Upload Image
if (imageUpload) {
const imageRef = ref(storage, `posts/${imageUpload.name + v4()}`);
uploadBytes(imageRef, imageUpload).then((snaphot) => {
getDownloadURL(snaphot.ref).then((url) => {
setImageList((prev) => [...prev, url]);
});
});
}
});
setPost({ description: "" });
return route.push("/Dashboard/Blog");
}
};
useEffect(() => {
checkUser();
listAll(imageListRef).then((response) => {
response.items.forEach((item) => {
getDownloadURL(item).then((url) => {
setImageList((prev) => [...prev, url]);
});
});
});
}, [user, loading]);
<form onSubmit={submitPost}>
<textarea
value={post.description}
onChange={(e) => setPost({ ...post, description: e.target.value })>
</textarea>
<input
type="file"
onChange={(event) => {
setImageUpload(event.target.files[0]);
}}
className="text-sm "
/>
<button
type="submit"
className="bg-primary-green w-full font-semibold p-2 my-2 rounded-md"
>
Upload
</button>
{imageList.map((url) => {
return <img src={url} />;
})}
</div>
</form>
i tried few solution but still not work, Any help will be really appreciated i'am just a beginer
and here is the Message component that contain "post" that will be display in "Blog" component, i want image that i store in it
export default function Message({ children, avatar, username, description }) {
return (
<div>
<div>
<img src={avatar}/>
<div>
<h2 >{username}</h2>
<p >7:20 PM · Jan 19, 2023</p>
</div>
</div>
<div>
<p>{description}</p>
</div>
{children}
</div>
);
}

In this case, you must use context or global state management libraries like Redux.

Related

Why the state is not updating?

I have created a state using react hook useState() at line 28 which is const [input, setInput] = useState('');. Then I updated the state by calling the setInput() at line 45 in the createTodo function at line 32. But when the function is executing, the state is not updating. There is no error on the console. I also added a console.log() after the state updating statement and the console.log() also executing but state is not updating.
My codes are given below.
App.jsx
import React, { useEffect, useState } from 'react';
import { AiOutlinePlus } from 'react-icons/ai';
import ToDo from './ToDo';
import { db } from './firebase';
import {
addDoc,
collection,
deleteDoc,
doc,
onSnapshot,
query,
updateDoc,
} from 'firebase/firestore';
const style = {
bg: `h-screen w-screen p-4 bg-gradient-to-r from-[#2F80ED] to-[#1CB5E0]`,
container: `bg-slate-100 max-w-[500px] w-full m-auto rounded-md shadow-xl p-4`,
heading: `text-3xl font-bold text-center text-gray-800 p-2`,
form: `flex justify-between`,
input: `border p-2 w-full text-xl`,
button: `border p-4 ml-2 bg-purple-500 text-slate-100`,
count: `text-center p-2`,
};
const App = () => {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState(''); // this is the state
// ************ firebase operations ************
// create
const createTodo = async event => {
event.preventDefault();
if (input === '') {
alert('Please add some text');
return;
}
// crating data to firebase
await addDoc(collection(db, 'todos'), {
text: input,
completed: false,
});
setInput(''); // here state is not updating
};
// read
useEffect(() => {
const q = query(collection(db, 'todos'));
const unsubscribe = onSnapshot(q, querySnapshot => {
let todosArr = [];
querySnapshot.forEach(doc => {
todosArr.push({ ...doc.data(), id: doc.id });
});
setTodos(todosArr);
});
return () => unsubscribe();
}, []);
// update
const toggleComplete = async todo => {
await updateDoc(doc(db, 'todos', todo.id), {
completed: !todo.completed,
});
};
// delete
const todoDelete = async id => {
await deleteDoc(doc(db, 'todos', id));
};
return (
<div className={style.bg}>
<div className={style.container}>
<h2 className={style.heading}>ToDo CRUD with Firebase</h2>
<form onSubmit={createTodo} className={style.form}>
<input
type="text"
className={style.input}
placeholder="ToDo"
onChange={event => setInput(event.target.value)}
/>
<button className={style.button}>
<AiOutlinePlus size={30} />
</button>
</form>
<ul>
{todos.map((todo, index) => (
<ToDo
key={index}
todo={todo}
toggleComplete={toggleComplete}
todoDelete={todoDelete}
/>
))}
</ul>
{todos.length === 0 ? null : (
<p className={style.count}>{`You have ${todos.length} todos`}</p>
)}
</div>
</div>
);
};
export default App;
Please have a look. How can I solve this problem?
You are missing value={input} in your input element
<input
type="text"
className={style.input}
placeholder="ToDo"
value={input} <-- this
onChange={event => setInput(event.target.value)}
/>
<input
type="text"
className={style.input}
placeholder="ToDo"
**value={input}**
onChange={event => setInput(event.target.value)}
/>
https://reactjs.org/docs/forms.html#:~:text=An%20input%20form%20element%20whose,called%20a%20%E2%80%9Ccontrolled%20component%E2%80%9D.&text=Since%20the%20value%20attribute%20is,state%20the%20source%20of%20truth.

I am having a problem when fetching data from MongoDB to client side

I have created a page named Product Detail. On this page, there is a delivered button, I want this button to reduce the quantity of the product by 1 whenever it will be clicked. However, it's working but after clicking the button there is an error coming. I have attached the error screenshot for your convenience. I have tried several times to solve the error but I couldn't find any solution. Is there anyone to help me out with that error, please?
Thanks in advance.
Error screenshots:
https://i.ibb.co/CwmG0d5/image.jpg
1. Product detail code
import React from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import useProductDetail from '../../hooks/useProductDetail';
import './ProductDetail.css';
const ProductDetail = () => {
const { productId } = useParams();
const [product, setProduct] = useProductDetail(productId);
const { quantity } = product;
const navigate = useNavigate();
const navigateToManageInventory = () => {
navigate("/manage");
};
// event handler for delivered button
const handleQuantity = () => {
let newQuantity = quantity - 1;
const newProduct = { ...product, quantity: newQuantity };
setProduct(newProduct);
const url = `http://localhost:5000/product/${productId}`;
console.log(url);
fetch(url, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newProduct),
});
};
return (
<div className="container">
<h3 className="text-center my-4">Product Details</h3>
<div className="product-container mx-auto rounded-3 shadow">
<div className="text-center my-3">
<img width={250} src={product.picture} alt="" />
</div>
<div className="product-info px-4">
<h6 className="text-muted">Product Id: {productId}</h6>
<h3>{product.name}</h3>
<p>
<b>Price:</b> ${product.price}
</p>
<p>
<b>Quantity:</b> {product.quantity}{" "}
<small className="text-muted">pcs</small>
</p>
<p>
<b>Supplier:</b> {product.supplier}
</p>
<p>{product.description}</p>
</div>
<div className="d-flex justify-content-center">
<button
onClick={() => handleQuantity(productId)}
className="w-50 btn btn-primary mt-3"
>
Delivered
</button>
</div>
</div>
<div className="text-center">
<button
onClick={navigateToManageInventory}
className="my-5 btn btn-primary"
>
Manage Inventories
</button>
</div>
</div>
);
};
export default ProductDetail;
2. Product detail (custom hook)
import { useEffect, useState } from 'react';
const useProductDetail = (productId) => {
const [product, setProduct] = useState({});
useEffect(() => {
const url = `http://localhost:5000/product/${productId}`;
fetch(url)
.then(res => res.json())
.then(data => setProduct(data));
}, [productId]);
return [product, setProduct];
};
export default useProductDetail;
3. Server-side code
app.put('/product/:id', async (req, res) => {
const id = req.params.id;
const quantity = req.body;
const options = { upsert: true };
const updateDor = {
_id: id,
name: product.name,
price: product.price,
description: product.description,
quantity: quantity.quantity,
supplier: product.supplier,
picture: product.picture
};
const result = await productCollection.updateOne(query, updateDor, options);
res.send(result);
});

Extra item being added in localStorage?

I am trying to build message component in react. Currently I just saving entered messages in localStorage but whenever I am pushing a message 2 rows get pushed in localStorage which should not happen. I have checked it but not able to get why this happening.
message comp
import React, { useState, useEffect } from "react";
interface IMessage {
user: string;
text: string;
}
export const Message = () => {
const [messages, setMessages] = useState<IMessage[]>([]);
const [message, setMessage] = useState("");
const [user, setUser] = useState("testuser");
useEffect(() => {
const fetchedMessages = JSON.parse(localStorage.getItem("messages") as any);
if (fetchedMessages) {
console.log("fetchedMessages=>", fetchedMessages);
}
}, [messages]);
const handleFormSubmit = (event: any) => {
event.preventDefault();
setMessages((messages) => {
const newData = [...messages, { text: message, user: user }];
let oldStorage =
JSON.parse(localStorage.getItem("messages") as any) || [];
const oldStorageN = [...oldStorage, { text: message, user: user }];
localStorage.removeItem("messages");
localStorage.setItem("messages", JSON.stringify(oldStorageN));
return newData;
});
setMessage("");
};
return (
<>
<div className="MessageContainer">
{messages.map((message) => {
return (
<div key={message.text + message.user}>
<div>
<strong>{message.user}</strong>
<div>{message.text}</div>
</div>
</div>
);
})}
</div>
<div>
<form onSubmit={handleFormSubmit}>
<div>
<input
type="text"
name="message"
onChange={(e) => {
setMessage(e.target.value);
}}
value={message}
/>
</div>
<div>
<input type="submit" value="send" />
</div>
</form>
</div>
</>
);
};
This is code link
https://stackblitz.com/edit/react-starter-typescript-qowlvq?file=components/Message.tsx
Note: on StackBlitz it's working fine, but on my machine it's adding two entries per message.

Is there a way to re-render data after submit-axios post request? ReactJS

So I've recently started on learning React, where I've created a little project for me. Now on backend everythings works etc.
By now everything was going good, but now I've got stuck.
Now about the page: I've got page, where u can see details about single article, and get info about loan for price of article. I've made it on backend that default value of it is 60 months, and if u want different period, u submit other months value eg. 120. So on backend when I hit route http://localhost:8080/api/article/id i get response of data and loan is calculated with 60 months. Now if in body i send eg. {"months": 6} i get different data in response which is expected and working fine.
Now where I've hit a wall: on front end I have no idea how to update data when form is submited. Here you can see my from:
And idea is when u enter eg. 6 to lower part of page is changed:
These last two right clomuns should be changed.
Now I've tried to send with id months to the actions and then refresh page when disptach is triggered but no success - and I know that after refresh months are reseted to default value.
Now these values come from that localhost route and I'm fetching it with a axios call, and displaying content
Here is my Article.js component:
import React, { useEffect, useState, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';
import Form from 'react-validation/build/form';
import Input from 'react-validation/build/input';
import CheckButton from 'react-validation/build/button';
import ArticleService from '../services/article.service';
import { getArticle } from '../actions/articles';
const Article = (props) => {
const form = useRef();
const checkBtn = useRef();
const [content, setContent] = useState([]);
const [dataArr, setDataArr] = useState([]);
const [months, setMonths] = useState([]);
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const onChangeMonths = (e) => {
const months = e.target.value;
setMonths(months);
};
const handleMonths = (e) => {
e.preventDefault();
setLoading(true);
if (checkBtn.current.context._errors.length === 0) {
const id = props.match.params.id;
dispatch(getArticle(id, months))
.then(() => {})
.catch(() => {
setLoading(false);
});
} else {
setLoading(false);
}
};
useEffect(() => {
const fetchPosts = async () => {
const id = props.match.params.id;
const res = await ArticleService.article(id);
setContent(res.data);
const data = res.data.kredit;
const dataArr = [];
dataArr.push({
name: 'kreditNKS-rataNKS',
price: data.kreditNKS.map((item) => {
return item;
}),
rate: data.rataNKS.map((item) => {
return item;
}),
nks: data.stopaNKS.map((item) => {
return item;
}),
banka: {
eks: data.stopaEKS.map((item) => {
return item;
}),
bankname: data.ime.map((item) => {
return item;
}),
type: data.tip.map((item) => {
return item;
}),
},
});
setDataArr(dataArr);
};
fetchPosts();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const a = dataArr;
return (
<div>
<p className='text-dark'>
<Link to='/dashboard'>
<i className='fas fa-arrow-left'></i> Nazad
</Link>
</p>
<div className='container p-3 my-3 bg-dark text-white'>
<strong>Artikal id:{content.id}</strong>
<br></br>
<br></br>
<div className='row'>
<div className='col-sm'>
Opis:
<br></br>
{content.descr}
</div>
<div className='col-sm'>
Cijena
<br></br>
{content.price}
</div>
<div className='col-sm'>
Cijena po metru kvadratnom:
<br></br>
{content.ppm2}/m2
</div>
</div>
</div>
<div className='container'>
<h3>KREDITI ZA CIJENU {content.price}</h3>
<Form onSubmit={handleMonths} ref={form}>
<div className='form-group'>
<label>Vrijeme otplate u mjesecima:</label>
<Input
type='text'
className='form-control w-25'
name='months'
value={months}
onChange={onChangeMonths}
/>
<button
className='btn btn-primary btn-block w-25'
disabled={loading}
>
{loading && (
<span className='spinner-border spinner-border-sm'></span>
)}
<span>Click</span>
</button>
<CheckButton style={{ display: 'none' }} ref={checkBtn} />
<small>
Ako se ne unese vrijeme otplate kredita, kredit se izračunava za
60 mjeseci
</small>
</div>
</Form>
</div>
<div className='container-fluid'>
<br></br>
<h4>Lista kredita</h4>
<div className='row'>
<div className='col-sm'>
<h4>Informacije o banci</h4>
{a &&
a.map((item) =>
item.banka.bankname.map((its, index) => (
<div className='card card-body flex-fill'>
<h2>{its}</h2>
<h6>EKS: {item.banka.eks[index]}%</h6>
<h6>Tip: {item.banka.type[index]} K.S</h6>
</div>
))
)}
</div>
<div className='col-sm'>
<h4>NKS</h4>
{a &&
a.map((item) =>
item.nks.map((s) => (
<div className='card card-body flex-fill'>
<h2>{s}</h2>
</div>
))
)}
</div>
<div className='col-sm'>
<h4>Ukupna cijena kredita</h4>
{a &&
a.map((item) =>
item.price.map((it2) => (
<div className='card card-body flex-fill'>
<h2>{it2} KM</h2>
</div>
))
)}
</div>
<div className='col-sm'>
<h4>Rata</h4>
{a &&
a.map((item) =>
item.rate.map((it2) => (
<div className='card card-body flex-fill'>
<h2>{it2} KM/mj</h2>
</div>
))
)}
</div>
</div>
</div>
</div>
);
};
export default Article;
actions/article.js
import { SET_MESSAGE, RATE_UPDATE, UPDATE_FAIL } from './types';
import ArticleService from '../services/article.service';
export const getArticle = (id, months) => (dispatch) => {
return ArticleService.article(id, months).then(
(response) => {
dispatch({
type: RATE_UPDATE,
});
dispatch({
type: SET_MESSAGE,
payload: response.data.message,
});
return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: UPDATE_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message,
});
return Promise.reject();
}
);
};
services/article.service.js
import axios from 'axios';
//const API_URL = 'https://stanbackapp.azurewebsites.net/api/articles/';
const API_URL = 'http://localhost:8080/api/articles/';
//const dAPI_URL = 'https://stanbackapp.azurewebsites.net/api/article/';
const dAPI_URL = 'http://localhost:8080/api/article/';
const articles = () => {
return axios.get(API_URL);
};
const article = (id, months) => {
return axios.post(dAPI_URL + `${id}`, {
months,
});
};
export default {
articles,
article,
};
I want to frontend behave just like backend: If i don't send anything in the form display data for 60 months.(that is what is doing now), but if i type in 10 and hit click a want to page re-render these two last columns(because if you send months only them are changed) and display that new data.
Also I've tried this in dispatch:
dispatch(getArticle(id, months))
.then((response) => console.log(response))
But console.log is undefiend
Any tips? Thanks!
I think your issue may be that you are mixing state. The first time you fetch data (via useEffect), you are fetching directly via axios. However, when you fetch data in the handleMonths, you are passing the action over to Redux, which operates very differently. The other issue is that the component never retrieves the data from Redux, so any updates are essentially ignored.
My suggestion would be to use the same method for retrieving data the first time as you do subsequent times:
const handleMonths = (e) => {
...
if (checkBtn.current.context._errors.length === 0) {
const id = props.match.params.id;
try {
const res = await ArticleService.article(id, months);
... // Handle the response appropriately.
setContent(res.data);
const data = res.data.kredit;
const dataArr = [];
dataArr.push({
name: 'kreditNKS-rataNKS',
price: data.kreditNKS,
rate: data.rataNKS,
nks: data.stopaNKS,
banka: {
eks: data.stopaEKS,
bankname: data.ime,
type: data.tip,
},
});
setDataArr(dataArr);
} catch (e) {
setLoading(false);
}
} else {
setLoading(false);
}
};
This provides 2 benefits:
You can extract the method for handling the response data to make it the same for both cases.
You remove the need for an external state handler (i.e. Redux) which you may not need.

Firebase getDownloadUrl does not work at first i neen to reload the page

When I try to display my image with the image url i got an error 404 not found and then when i reload the page the image appear. I know it's because of the image dont have time to get upload first but i don't know how to resolve this problem. If you have any idea, this is the code :
I first create a user and create a ref for my image then put my ref in my database then i get my data on my profile page and do a getDownloadUrl method to get my image url with my ref but like i said before i need to reload the page first to don't get the error.
.createUserWithEmailAndPassword(email.value, password.value)
.then(registeredUser => {
const storageRef = app.storage().ref();
const fileRef = storageRef.child(registeredUser.user.uid + "/profile.jpg");
fileRef.put(file);
app.firestore().collection('Users')
.add({
uid: registeredUser.user.uid,
firstName: firstName.value,
lastName: lastName.value,
email: email.value,
companyName: companyName.value,
companyDomain: companyDomain.value,
profileImage: registeredUser.user.uid + "/profile.jpg"
})
let imageRef = userData[0].profileImage
const imageUrl = (async () => {
let imagePath = await app.storage().ref().child(imageRef).getDownloadURL()
setProfileImage(imagePath)
})()
return (
<>
<Nav />
<div className="container">
<h1>Profile</h1>
<div className="profile_container">
<div className="image_container">
<img src={profileImage} alt="profileImage" />
</div>
<div className="info_profile_container">
<h3><strong>First Name:</strong> {userData[0].firstName}</h3>
<h3><strong>Last Name:</strong> {userData[0].lastName}</h3>
<h3><strong>Email:</strong> {userData[0].email}</h3>
<h3><strong>Company Name:</strong> {userData[0].companyName}</h3>
<h3><strong>Company
Your example is incomplete, so I'm going to provide you with pseudo code.
Try use effect to get what you need. This will retrieve the image every time userData changes.
useEffect(() => {
if (userData) imageUrl(); //check whether userData is valid before fetching
}, [userData]) //get the image every time userData props changes.
const imageUrl = async() => {
let imagePath = await app.storage().ref().child(imageRef).getDownloadURL()
setProfileImage(imagePath)
})
const Profile = () => {
const { currentUser } = useContext(AuthContext);
const [userData, setUserData] = useState()
const [profileImage, setProfileImage] = useState()
const users = app.firestore().collection('Users')
useEffect(() => {
users
.where("uid", "==", `${currentUser.uid}`)
.get()
.then((snapshot) => {
const data = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}))
setUserData(data)
console.log(data)
})
}, [userData])
if (!userData) {
return <h1>Loading...</h1>
} else {
let imageRef = userData[0].profileImage
const imageUrl = async () => {
let imagePath = await app.storage().ref().child(imageRef).getDownloadURL()
setProfileImage(imagePath)
}
return (
<>
<Nav />
<div className="container">
<h1>Profile</h1>
<div className="profile_container">
<div className="image_container">
<img src={profileImage} alt="profileImage" />
</div>
<div className="info_profile_container">
<h3><strong>First Name:</strong> {userData[0].firstName}</h3>
<h3><strong>Last Name:</strong> {userData[0].lastName}</h3>
<h3><strong>Email:</strong> {userData[0].email}</h3>
<h3><strong>Company Name:</strong> {userData[0].companyName}</h3>
<h3><strong>Company Domain:</strong> {userData[0].companyDomain}</h3>
</div>
</div>
<button className="danger" onClick={() => app.auth().signOut()}>Sign out</button>
</div>
</>
);
}
};

Categories