I have a react app (a sort of twitter clone) that uses firestore for storing posts and comments on posts. Each post is rendered as an element from an array using array.map(). Each post has a comment button that opens a form to take in a comment and add it to the post. When I enter a comment and submit it, the topmost post is always the one commented on no matter which post contained the comment button that was clicked(docId for the most recently saved firestore document is always submitted by the comment button instead of the docId corresponding to that instance of the component).
The map of the posts (called "howls"):
<div className="timeline">
{sortedHowls &&
sortedHowls.map((howl) => (
<Howl
key={howl.id}
image={howl.image}
text={howl.text}
time={howl.time}
userId={howl.userId}
docId={howl.id}
comments={howl.comments}
likes={howl.likes}
/>
))}
</div>
The Howl Component looks like this:
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useFirestoreConnect } from "react-redux-firebase";
import { firestore } from "../../../firebase-store";
// styles
import "./Howl.scss";
// components
import Avatar from "../Avatar/Avatar";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
// functions
import timeCalc from "./timeCalc";
// icons
import { faStar, faComment } from "#fortawesome/free-solid-svg-icons";
const Howl = ({ docId, userId, text, image, time, comments, likes }) => {
useFirestoreConnect([{ collection: "users" }]);
const [commenting, toggleCommenting] = useState(false);
const [newComment, setNewComment] = useState("");
const [users, setUsers] = useState(null);
const [user, setUser] = useState(null);
const getUsers = useSelector((state) => state.firestore.ordered.users);
useEffect(() => {
if (!users) {
setUsers(getUsers);
} else {
setUser(users.find((doc) => doc.uid === userId));
}
}, [users, user, userId, getUsers]);
const handleLike = () => {
const newLikesTotal = likes + 1;
firestore.collection("howls").doc(docId).update({ likes: newLikesTotal });
};
const handleComment = () => {
toggleCommenting(!commenting);
};
const handleChange = (event) => {
setNewComment(event.currentTarget.value);
};
const submitComment = (event) => {
event.preventDefault();
const { id } = event.currentTarget;
console.log(event.currentTarget);
const resetComment = () => {
toggleCommenting(!commenting);
setNewComment("");
};
if (comments) {
firestore
.collection("howls")
.doc(id)
.update({
comments: [...comments, newComment],
})
.then(() => resetComment());
} else {
firestore
.collection("howls")
.doc(id)
.update({ comments: [newComment] })
.then(() => resetComment());
}
};
return (
<div className="howl">
<div className="avatar-container">
<Avatar
photoURL={user ? user.photoURL : ""}
displayName={user ? user.displayName : ""}
/>
</div>
<div className="name-text-img-container">
<p className="userName">
{user && user.displayName} - {timeCalc(Date.now(), time)}
</p>
<p className="howl-text">{text}</p>
<div className="img-container">
{image ? (
<img src={image} alt="user uploaded" className="img" />
) : null}
</div>
<div className="buttons-container">
<form action="" className="buttons">
<label htmlFor="comment-button">
<FontAwesomeIcon icon={faComment} className="image-icon" />
</label>
<input
id="comment-button"
type="checkbox"
onClick={handleComment}
style={{ display: "none" }}
/>
<label htmlFor="like-button">
<FontAwesomeIcon icon={faStar} className="image-icon" />
</label>
<input
id="like-button"
type="checkbox"
onClick={handleLike}
style={{ display: "none" }}
/>
<label htmlFor="like-button">{likes > 0 && likes}</label>
</form>
</div>
{commenting && (
<div className="comment-form">
<form action="submit" onSubmit={submitComment} id={docId}>
<input
type="text"
name="comment-input"
className="comment-input"
maxLength={128}
onChange={handleChange}
value={newComment}
placeholder="Enter comment"
/>
<div className="buttons">
<button type="submit">Submit</button>
<button onClick={() => toggleCommenting(!commenting)}>
Cancel
</button>
</div>
</form>
</div>
)}
<div className="comments">
{comments
? comments.map((comment, index) => {
return (
<p key={index} className="comment">
{comment}
</p>
);
})
: null}
</div>
</div>
</div>
);
};
export default Howl;
How can I get the comment button to specify the correct document to update?
Link to my full repo.
It turns out that the problem is here:
<form action="" className="buttons">
<label htmlFor="comment-button">
<FontAwesomeIcon icon={faComment} className="image-icon" />
</label>
<input
id="comment-button"
type="checkbox"
onClick={handleComment}
style={{ display: "none" }}
/>
<label htmlFor="like-button">
<FontAwesomeIcon icon={faStar} className="image-icon" />
</label>
<input
id="like-button"
type="checkbox"
onClick={handleLike}
style={{ display: "none" }}
/>
<label htmlFor="like-button">{likes > 0 && likes}</label>
</form>
By using a form and inputs as the buttons instead of using <button /> elements it somehow confused react as to which instance of <Howl /> was opening the comment form and therefore which docId was sent to submitComment. Corrected <Howl /> component:
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useFirestoreConnect } from "react-redux-firebase";
import { firestore } from "../../../firebase-store";
// components
import Avatar from "../Avatar/Avatar";
import CommentInput from "./CommentInput";
import Comment from "./Comment";
import ViewProfile from "../ViewProfile/ViewProfile";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
// functions
import timeCalc from "./timeCalc";
// styles
import "./Howl.scss";
// icons
import { faStar, faComment } from "#fortawesome/free-solid-svg-icons";
const Howl = ({ howl }) => {
useFirestoreConnect([{ collection: "users" }]);
const [commenting, toggleCommenting] = useState(false);
const [newComment, setNewComment] = useState("");
const [users, setUsers] = useState(null);
const [op, setOp] = useState(null);
const [showProfile, setShowProfile] = useState(false);
const { docId, userId, text, likes, comments, time, image } = howl;
const getUsers = useSelector((state) => state.firestore.ordered.users);
const currentUser = useSelector((state) => state.firebase.auth);
// establish user that posted this howl (op = original poster)
useEffect(() => {
users ? setOp(users.find((doc) => doc.uid === userId)) : setUsers(getUsers);
}, [users, op, userId, getUsers]);
const handleLike = () => {
const index = likes.indexOf(currentUser.uid);
let newLikes = [...likes];
if (index > 0) {
newLikes.splice(index, 1);
} else if (index === 0) {
if (likes.length > 1) {
newLikes.splice(index, 1);
} else {
newLikes = [];
}
} else {
newLikes = [...newLikes, currentUser.uid];
}
firestore.collection("howls").doc(docId).update({ likes: newLikes });
};
const handleChange = (event) => {
setNewComment(event.currentTarget.value);
};
const submitComment = (event) => {
event.preventDefault();
const { id } = event.currentTarget;
const { uid, photoURL } = currentUser;
const resetComment = () => {
toggleCommenting(!commenting);
setNewComment("");
};
firestore
.collection("howls")
.doc(id)
.update({
comments: [
...comments,
{ uid: uid, photoURL: photoURL, text: newComment },
],
})
.then(() => resetComment());
};
return (
<div className="howl">
<div className="avatar-container">
<button className="show-profile" onClick={() => setShowProfile(true)}>
<Avatar
photoURL={op ? op.photoURL : ""}
displayName={op ? op.displayName : ""}
/>
</button>
</div>
<div className="name-text-img-container">
<p className="userName">
{op && op.displayName} - {timeCalc(Date.now(), time)}
</p>
<p className="howl-text">{text}</p>
<div className="img-container">
{image && <img src={image} alt="user uploaded" className="img" />}
</div>
<div className="buttons-container">
<div className="buttons">
<button className="comment-button">
<FontAwesomeIcon
icon={faComment}
className="image-icon"
onClick={() => toggleCommenting(!commenting)}
/>
</button>
<button className="like-button" onClick={handleLike}>
<FontAwesomeIcon
icon={faStar}
className={
currentUser && likes.includes(currentUser.uid)
? "image-icon liked"
: "image-icon"
}
/>
</button>
<p>{likes.length > 0 && likes.length}</p>
</div>
</div>
{commenting && (
<CommentInput
submitComment={submitComment}
docId={docId}
toggleCommenting={toggleCommenting}
commenting={commenting}
handleChange={handleChange}
newComment={newComment}
/>
)}
{showProfile && (
<ViewProfile
user={op}
close={() => setShowProfile(false)}
update={false}
/>
)}
<div className="comments">
{comments &&
comments.map((comment, index) => {
return <Comment key={`comment${index}`} comment={comment} />;
})}
</div>
</div>
</div>
);
};
export default Howl;
Related
Whenever I'm clicking on a card to go to the product details page it is automatically going to the bottom of the next page without even scrolling it here is the sample of my code
import React from "react";
import { Link } from "react-router-dom";
import { Rating } from "#material-ui/lab";
const ProductCard = ({ product }) => {
const options = {
value: product.ratings,
readOnly: true,
precision: 0.5,
};
return (
<Link className="productCard" to={`/product/${product._id}`}>
<img src={product.images[0].url} alt={product.name} />
<p>{product.name}</p>
<div>
<Rating {...options} />
<span className="productCardSpan">
{""}({product.numOfReviews} Reviews)
</span>
</div>
<span>{`₹${product.price}/-`}</span>
</Link>
);
};
export default ProductCard;
product details page is starting from here
import React, { useEffect, useState } from "react";
import Carousel from "react-material-ui-carousel";
import "./ProductDetails.css";
import { useSelector, useDispatch } from "react-redux";
import {
clearErrors,
getProductDetails,
newReview,
} from "../../actions/productAction";
import ReviewCard from "./ReviewCard.js";
import Loader from "../layout/Loader/Loader";
import { useAlert } from "react-alert";
import MetaData from "../layout/MetaData";
import { addItemsToCart } from "../../actions/cartAction";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
} from "#material-ui/core";
import { Rating } from "#material-ui/lab";
import { NEW_REVIEW_RESET } from "../../constants/productConstants";
const ProductDetails = ({ match }) => {
const dispatch = useDispatch();
const alert = useAlert();
const { product, loading, error } = useSelector(
(state) => state.productDetails
);
const { success, error: reviewError } = useSelector(
(state) => state.newReview
);
const options = {
size: "large",
value: product.ratings,
readOnly: true,
precision: 0.5,
};
const [quantity, setQuantity] = useState(1);
const [open, setOpen] = useState(false);
const [rating, setRating] = useState(0);
const [comment, setComment] = useState("");
const increaseQuantity = () => {
if (product.Stock <= quantity) return;
const qty = quantity + 1;
setQuantity(qty);
};
const decreaseQuantity = () => {
if (1 >= quantity) return;
const qty = quantity - 1;
setQuantity(qty);
};
const addToCartHandler = () => {
dispatch(addItemsToCart(match.params.id, quantity));
alert.success("Item Added To Cart");
};
const submitReviewToggle = () => {
open ? setOpen(false) : setOpen(true);
};
const reviewSubmitHandler = () => {
const myForm = new FormData();
myForm.set("rating", rating);
myForm.set("comment", comment);
myForm.set("productId", match.params.id);
dispatch(newReview(myForm));
setOpen(false);
};
useEffect(() => {
if (error) {
alert.error(error);
dispatch(clearErrors());
}
if (reviewError) {
alert.error(reviewError);
dispatch(clearErrors());
}
if (success) {
alert.success("Review Submitted Successfully");
dispatch({ type: NEW_REVIEW_RESET });
}
dispatch(getProductDetails(match.params.id));
}, [dispatch, match.params.id, error, alert, reviewError, success]);
console.log(product.images);
return (
<>
{loading ? (
<Loader />
) : (
<>
<MetaData title={`${product.name} -- ECOMMERCE`} />
<div className="ProductDetails">
<div>
{product.images &&
product.images.map((item, i) => (
<img
className="CarouselImage"
key={i}
src={item.url}
alt="product"
/>
))}
</div>
<div>
<div className="detailsBlock-1">
<h2>{product.name}</h2>
<p>Product # {product._id}</p>
</div>
<div className="detailsBlock-2">
<Rating {...options} />
<span className="detailsBlock-2-span">
{" "}
({product.numOfReviews} Reviews)
</span>
</div>
<div className="detailsBlock-3">
<h1>{`₹${product.price}`}</h1>
<div className="detailsBlock-3-1">
<div className="detailsBlock-3-1-1">
<button onClick={decreaseQuantity}>-</button>
<input readOnly type="number" value={quantity} />
<button onClick={increaseQuantity}>+</button>
</div>
<button
disabled={product.Stock < 1 ? true : false}
onClick={addToCartHandler}
>
Add to Cart
</button>
</div>
<p>
Status:
<b className={product.Stock < 1 ? "redColor" : "greenColor"}>
{product.Stock < 1 ? "OutOfStock" : "InStock"}
</b>
</p>
</div>
<div className="detailsBlock-4">
Description : <p>{product.description}</p>
</div>
<button onClick={submitReviewToggle} className="submitReview">
Submit Review
</button>
</div>
</div>
<h3 className="reviewsHeading">REVIEWS</h3>
<Dialog
aria-labelledby="simple-dialog-title"
open={open}
onClose={submitReviewToggle}
>
<DialogTitle>Submit Review</DialogTitle>
<DialogContent className="submitDialog">
<Rating
onChange={(e) => setRating(e.target.value)}
value={rating}
size="large"
/>
<textarea
className="submitDialogTextArea"
cols="30"
rows="5"
value={comment}
onChange={(e) => setComment(e.target.value)}
></textarea>
</DialogContent>
<DialogActions>
<Button onClick={submitReviewToggle} color="secondary">
Cancel
</Button>
<Button onClick={reviewSubmitHandler} color="primary">
Submit
</Button>
</DialogActions>
</Dialog>
{product.reviews && product.reviews[0] ? (
<div className="reviews">
{product.reviews &&
product.reviews.map((review) => (
<ReviewCard key={review._id} review={review} />
))}
</div>
) : (
<p className="noReviews">No Reviews Yet</p>
)}
</>
)}
</>
);
};
export default ProductDetails;
I tried linking in it but it won't work and someone told me to use useRef but i don't know how to use it
You are using React router dom. In React Router there is the problem that if we redirect to the new route, it won't automatically take you to the top of the page. Such behavior is normal when you navigate between pages.
Since you are using functional components
Try to use the following window scroll to the top when the component mounts.
useEffect(() => {
window.scrollTo(0, 0)
}, [])
Browser scroll your page.
If you don't want to let browser auto scroll your page,
use History.scrollRestoration method
Prevent automatic page location restoration
if (history.scrollRestoration) {
history.scrollRestoration = 'manual';
}
Read more at MDN History.scrollRestoration
I keep on getting this error when trying to update a product in my project:
react_devtools_backend.js:4012 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
Index.js:
import React from 'react';
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
ProductList:
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useParams, useNavigate, useLocation } from 'react-router-dom';
import {
createProduct,
deleteProduct,
listProducts,
} from '../actions/productActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import {
PRODUCT_CREATE_RESET,
PRODUCT_DELETE_RESET,
} from '../constants/productConstants';
export default function ProductListScreen(props) {
const navigate = useNavigate();
const { pageNumber = 1 } = useParams();
const { pathname } = useLocation();
const sellerMode = pathname.indexOf('/seller') >= 0;
const productList = useSelector((state) => state.productList);
const { loading, error, products, page, pages } = productList;
const productCreate = useSelector((state) => state.productCreate);
const {
loading: loadingCreate,
error: errorCreate,
success: successCreate,
product: createdProduct,
} = productCreate;
const productDelete = useSelector((state) => state.productDelete);
const {
loading: loadingDelete,
error: errorDelete,
success: successDelete,
} = productDelete;
const userSignin = useSelector((state) => state.userSignin);
const { userInfo } = userSignin;
const dispatch = useDispatch();
useEffect(() => {
if (successCreate) {
dispatch({ type: PRODUCT_CREATE_RESET });
navigate(`/product/${createdProduct._id}/edit`);
}
if (successDelete) {
dispatch({ type: PRODUCT_DELETE_RESET });
}
dispatch(
listProducts({ seller: sellerMode ? userInfo._id : '', pageNumber })
);
}, [
createdProduct,
dispatch,
navigate,
sellerMode,
successCreate,
successDelete,
userInfo._id,
pageNumber,
]);
const deleteHandler = (product) => {
if (window.confirm('Are you sure to delete?')) {
dispatch(deleteProduct(product._id));
}
};
const createHandler = () => {
dispatch(createProduct());
};
return (
<div>
<div className="row">
<h1>Products</h1>
<button type="button" className="primary" onClick={createHandler}>
Create Product
</button>
</div>
{loadingDelete && <LoadingBox></LoadingBox>}
{errorDelete && <MessageBox variant="danger">{errorDelete}</MessageBox>}
{loadingCreate && <LoadingBox></LoadingBox>}
{errorCreate && <MessageBox variant="danger">{errorCreate}</MessageBox>}
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<>
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>PRICE</th>
<th>CATEGORY</th>
<th>BRAND</th>
<th>ACTIONS</th>
</tr>
</thead>
<tbody>
{products.map((product) => (
<tr key={product._id}>
<td>{product._id}</td>
<td>{product.name}</td>
<td>{product.price}</td>
<td>{product.category}</td>
<td>{product.brand}</td>
<td>
<button
type="button"
className="small"
onClick={() => navigate(`/product/${product._id}/edit`)}
>
Edit
</button>
<button
type="button"
className="small"
onClick={() => deleteHandler(product)}
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
<div className="row center pagination">
{[...Array(pages).keys()].map((x) => (
<Link
className={x + 1 === page ? 'active' : ''}
key={x + 1}
to={`/productlist/pageNumber/${x + 1}`}
>
{x + 1}
</Link>
))}
</div>
</>
)}
</div>
);
}
Product Edit Page:
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Axios from 'axios';
import { useNavigate, useParams } from 'react-router-dom';
import { detailsProduct, updateProduct } from '../actions/productActions';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import { PRODUCT_UPDATE_RESET } from '../constants/productConstants';
export default function ProductEditScreen(props) {
const navigate = useNavigate();
const params = useParams();
const { id: productId } = params;
const [name, setName] = useState('');
const [price, setPrice] = useState('');
const [image, setImage] = useState('');
const [category, setCategory] = useState('');
const [countInStock, setCountInStock] = useState('');
const [brand, setBrand] = useState('');
const [description, setDescription] = useState('');
const productDetails = useSelector((state) => state.productDetails);
const { loading, error, product } = productDetails;
const productUpdate = useSelector((state) => state.productUpdate);
const {
loading: loadingUpdate,
error: errorUpdate,
success: successUpdate,
} = productUpdate;
const dispatch = useDispatch();
useEffect(() => {
if (successUpdate) {
navigate('/productlist');
}
if (!product || product._id !== productId || successUpdate) {
dispatch({ type: PRODUCT_UPDATE_RESET });
dispatch(detailsProduct(productId));
} else {
setName(product.name);
setPrice(product.price);
setImage(product.image);
setCategory(product.category);
setCountInStock(product.countInStock);
setBrand(product.brand);
setDescription(product.description);
}
}, [product, dispatch, productId, successUpdate, navigate]);
const submitHandler = (e) => {
e.preventDefault();
// TODO: dispatch update product
dispatch(
updateProduct({
_id: productId,
name,
price,
image,
category,
brand,
countInStock,
description,
})
);
};
const [loadingUpload, setLoadingUpload] = useState(false);
const [errorUpload, setErrorUpload] = useState('');
const userSignin = useSelector((state) => state.userSignin);
const { userInfo } = userSignin;
const uploadFileHandler = async (e) => {
const file = e.target.files[0];
const bodyFormData = new FormData();
bodyFormData.append('image', file);
setLoadingUpload(true);
try {
const { data } = await Axios.post('/api/uploads', bodyFormData, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${userInfo.token}`,
},
});
setImage(data);
setLoadingUpload(false);
} catch (error) {
setErrorUpload(error.message);
setLoadingUpload(false);
}
};
return (
<div>
<form className="form" onSubmit={submitHandler}>
<div>
<h1>Edit Product {productId}</h1>
</div>
{loadingUpdate && <LoadingBox></LoadingBox>}
{errorUpdate && <MessageBox variant="danger">{errorUpdate}</MessageBox>}
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<>
<div>
<label htmlFor="name">Name</label>
<input
id="name"
type="text"
placeholder="Enter name"
value={name}
onChange={(e) => setName(e.target.value)}
></input>
</div>
<div>
<label htmlFor="price">Price</label>
<input
id="price"
type="text"
placeholder="Enter price"
value={price}
onChange={(e) => setPrice(e.target.value)}
></input>
</div>
<div>
<label htmlFor="image">Image</label>
<input
id="image"
type="text"
placeholder="Enter image"
value={image}
onChange={(e) => setImage(e.target.value)}
></input>
</div>
<div>
<label htmlFor="imageFile">Image File</label>
<input
type="file"
id="imageFile"
label="Choose Image"
onChange={uploadFileHandler}
></input>
{loadingUpload && <LoadingBox></LoadingBox>}
{errorUpload && (
<MessageBox variant="danger">{errorUpload}</MessageBox>
)}
</div>
<div>
<label htmlFor="category">Category</label>
<input
id="category"
type="text"
placeholder="Enter category"
value={category}
onChange={(e) => setCategory(e.target.value)}
></input>
</div>
<div>
<label htmlFor="brand">Brand</label>
<input
id="brand"
type="text"
placeholder="Enter brand"
value={brand}
onChange={(e) => setBrand(e.target.value)}
></input>
</div>
<div>
<label htmlFor="countInStock">Count In Stock</label>
<input
id="countInStock"
type="text"
placeholder="Enter countInStock"
value={countInStock}
onChange={(e) => setCountInStock(e.target.value)}
></input>
</div>
<div>
<label htmlFor="description">Description</label>
<textarea
id="description"
rows="3"
type="text"
placeholder="Enter description"
value={description}
onChange={(e) => setDescription(e.target.value)}
></textarea>
</div>
<div>
<label></label>
<button className="primary" type="submit">
Update
</button>
</div>
</>
)}
</form>
</div>
);
}
what could be the problem?
The code should redirect me to a new page in which i can create a new product and add it to my database
In my React component
import React, { useEffect, useState } from "react";
import { useParams } from "react-router";
import { NavLink } from "react-router-dom";
import "./styles/editIntern.sass";
const EditIntern = () => {
const { id } = useParams();
const [intern, setIntern] = useState([]);
const [name, inputName] = useState("");
const [email, inputEmail] = useState("");
const [start, inputStart] = useState("");
const [end, inputEnd] = useState("");
const [errorNameEmpty, isErrorNameEmpty] = useState(false);
const [errorEmailValid, iserrorEmailValid] = useState(false);
const [errorStartEmpty, isErrorStartEmpty] = useState(false);
const [errorEndEmpty, isErrorEndEmpty] = useState(false);
const validEmail = new RegExp(
/(\w+([-+.']\w+)*#\w+([-.]\w+)*\.\w+([-.]\w+)*)/gm
);
const onFormSubmit = (e) => {
e.preventDefault();
let startDate = new Date(start).getTime();
let endDate = new Date(end).getTime();
if (startDate > endDate) {
console.log("Start > end");
console.log(startDate);
console.log(endDate);
} else {
console.log("Ok");
console.log(startDate);
console.log(endDate);
}
};
useEffect(() => {
const fetchIntern = async () => {
const response = await fetch(`http://localhost:3001/interns/${id}`);
const intern = await response.json();
setIntern(intern);
};
fetchIntern();
console.log(`I want to get intern with id: ${id}!`);
}, [id]);
return (
<div className="container">
<img className="Logo" src="../logo.svg" alt="logo" />
<section className="EditIntern">
<NavLink to="/">
<button className="EditIntern_back">
<img
className="EditIntern_back-img"
src="../button_back_icon.svg"
alt="button_back"
/>{" "}
Back to list
</button>
</NavLink>
<form className="EditIntern_form">
<h4 className="EditIntern_form-title">Edit</h4>
<label className="EditIntern_form-label EditIntern_form-label_name">
Full name *
</label>
<input
className="EditIntern_form-input EditIntern_form-input_name"
type="text"
name="name"
value={name}
onChange={(e) => {
if (e.target.value === "") {
isErrorNameEmpty(true);
} else {
isErrorNameEmpty(false);
}
inputName(e.target.value);
}}
/>
{errorNameEmpty ? (
<span className="EditIntern_form-error EditIntern_form-error_name">
Name can't be empty
</span>
) : (
<></>
)}
<label className="EditIntern_form-label EditIntern_form-label_email">
Email address *
</label>
<input
className="EditIntern_form-input EditIntern_form-input_email"
type="text"
name="email"
value={email}
onChange={(e) => {
if (e.target.value === "") {
iserrorEmailValid(true);
} else if (!validEmail.test(e.target.value)) {
iserrorEmailValid(true);
} else {
iserrorEmailValid(false);
}
inputEmail(e.target.value);
}}
/>
{errorEmailValid ? (
<span className="EditIntern_form-error EditIntern_form-error_email">
Example: email#gmail.com
</span>
) : (
<></>
)}
<label className="EditIntern_form-label EditIntern_form-label_start">
Internship start *
</label>
<input
className="EditIntern_form-input EditIntern_form-input_start"
type="date"
name="email"
value={start}
onChange={(e) => {
if (!isNaN(e.target.valueAsNumber))
inputStart(e.target.valueAsNumber);
if (e.target.value === "") {
isErrorStartEmpty(true);
} else {
isErrorStartEmpty(false);
}
}}
/>
{errorStartEmpty ? (
<span className="EditIntern_form-error EditIntern_form-error_start">
Start date can't be empty
</span>
) : (
<></>
)}
<label className="EditIntern_form-label EditIntern_form-label_end">
Internship end *
</label>
<input
className="EditIntern_form-input EditIntern_form-input_end"
type="date"
name="email"
value={end}
onChange={(e) => {
if (!isNaN(e.target.valueAsNumber))
inputEnd(e.target.valueAsNumber);
if (e.target.value === "") {
isErrorEndEmpty(true);
} else {
isErrorEndEmpty(false);
}
}}
/>
{errorEndEmpty ? (
<span className="EditIntern_form-error EditIntern_form-error_end">
End date can't be empty
</span>
) : (
<></>
)}
<input
className="EditIntern_form-submit"
type="submit"
value="Submit"
onClick={onFormSubmit}
/>
</form>
</section>
</div>
);
};
export default EditIntern;
I need inputs to be filled with values from the intern array when this component is called (intern.name, intern.email ...) Now with useState the inputs are empty by default. I need by default with data from intern but with the ability to erase and fill in as you like.
As I already wrote, intern is an array with data that is rendered when this component is opened, it has all the data that needs to be placed by default.
The problem if i see correctly is in that you don't getting data from array intern you just getting it from name, email, etc.
So on start set that values like intern.name to name etc.
after that you could save it inside array using
setIntern({name: name, ...intern})
and contiue with that using other parametrs
I have changed the code below to !_product && fetchProduct(productId), which has caused the error because this returns the fetchProduct function and being executed as the cleanup function. I rewrote it to the code below and the error still persists. The app was created using create-react-app
import { faMinus, faPlus, faTrash } from '#fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { addQuantity, deleteProductFromCart, fetchProduct, substractQuantity } from '../../store/actions';
import { checkPriceForDiscount } from '../../utils';
import Loading from './Loading';
const visible = (cart, productId) => {
let { quantity } = cart.products.find(p => p.id === productId)
return quantity ? "" : "fade-out"
}
const ProductPreview = ({ product, price }) => {
if (price.discounted) {
return (
<div className="wrapper">
<div className="box">
<div className="ribbon text-center">
-{price.discountInPercentage}% for <br />{price.matches} item Discount
</div>
<img className="preview" src={product.image} />
</div>
</div>
)
}
return (
<div className="wrapper">
<div className="box">
<img className="preview" src={product.image} />
</div>
</div>
)
}
const Product = ({ productId, cart, carts, products, fetchProduct, addQuantity, substractQuantity, deleteProductFromCart }) => {
const [price, setPrice] = useState(null)
const [product, setProduct] = useState(null)
useEffect(() => {
let _product = products.find(p => p.id === productId)
if (!_product) fetchProduct(productId)
}, [])
useEffect(() => {
let _product = products.find(p => p.id === productId)
if (_product) setProduct(_product)
}, [products])
useEffect(() => {
if (product) {
let _price = checkPriceForDiscount(carts, product)
if (_price) setPrice(_price)
}
}, [product, cart])
(!price || !product) && <Loading />
return (
<div className={`product ${visible(cart, productId)}`}>
<div className="title-wrapper">
<div className="title">
{product.title} ({product.category})
</div>
</div>
<ProductPreview {...{ product, price }} />
<div className="text">
<div className="description">
{product.description}
</div>
<div className="price">
$ {price.rawPrice}
</div>
<div className="shop-actions">
<FontAwesomeIcon id='substractQuantityIcon' className='pointer' icon={faMinus} onClick={() => substractQuantity(cart.id, productId)} />
{cart.products.find(p => p.productId === productId).quantity}
<FontAwesomeIcon id='addQuantityIcon' className='pointer' icon={faPlus} onClick={() => addQuantity(cart.id, productId)} />
<button className='pointer grow_on_hover' onClick={() => deleteProductFromCart(cart.id, productId)}>
<FontAwesomeIcon icon={faTrash} style={{ cursor: "pointer" }} /> Delete
</button>
</div>
</div>
</div>
);
};
const mapStateToProps = state => {
return {
carts: state.carts,
products: state.products,
};
};
export default connect(
mapStateToProps,
{ addQuantity, substractQuantity, deleteProductFromCart, fetchProduct }
)(Product);
What the code looks like rendering the button to show the form
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { deleteSong, getSongs, updateSong } from '../../store/song';
import ReactAudioPlayer from 'react-audio-player';
import { useHistory } from 'react-router';
import SongForm from '../AddSongForm';
import EditSongForm from '../EditSongForm';
const SongList = () => {
const [addShowForm, setAddShowForm] = useState(false);
const [editShowForm, setEditShowForm] = useState(false);
const history = useHistory()
const dispatch = useDispatch();
const songsObj = useSelector((state) => state.songState.entries);
const songs = Object.values(songsObj)
const user = useSelector((state) => state.session.user);
const CurrentUserId = user?.id
const remove = (e) => {
dispatch(deleteSong(e.target.id));
}
const addFormCheck = (e) => {
if (addShowForm) setAddShowForm(false)
if (!addShowForm) setAddShowForm(true)
}
const editFormCheck = (e) => {
if (editShowForm) setEditShowForm(false)
if (!editShowForm) setEditShowForm(true)
}
useEffect(() => {
dispatch(getSongs());
}, [dispatch]);
return (
<div>
<div>
<button onClick={addFormCheck}>add a song</button>
{addShowForm ?
<SongForm />
: null}
</div>
<h1>Song List</h1>
<ol>
{songs.map(({ id, songName, songLink, userId }) => (
<div className='songdetails' key={id}>
<p key={id}>songName={songName}</p>
<ReactAudioPlayer
src={songLink}
autoPlay
controls
key={songLink}
/>
{userId === CurrentUserId ?
<>
<div>
<button id={id} onClick={remove}>remove</button>
</div>
<div>
<button id={id} onClick={editFormCheck}>edit</button>
{editShowForm ?
<EditSongForm props={id} />
: null}
</div>
</>
: null}
</div>
))}
</ol>
</div>
);
};
export default SongList;
The actual form
import { useState } from "react";
import { useDispatch } from "react-redux";
import { updateSong } from "../../store/song";
import { useSelector } from "react-redux";
const EditSongForm = ({ props }) => {
console.log(props)
const dispatch = useDispatch();
const [songName, setSongName] = useState("");
const [songLink, setSongLink] = useState("");
const [errors, setErrors] = useState([]);
const reset = () => {
setSongName("");
setSongLink("");
// setAlbumName('');
// setArtistName('')
};
const user = useSelector((state) => state.session.user);
const userId = user?.id
const handleSubmit = async (e) => {
e.preventDefault();
const updatedSongDetails = {
id: props,
songName,
songLink,
userId
};
let updatedSong = await dispatch(updateSong(updatedSongDetails))
.catch(async (res) => {
const data = await res.json()
if (data && data.errors) setErrors(data.errors)
})
reset();
};
return (
<div className="inputBox">
<h1>Update A Song</h1>
<ul>
{errors.map((error, idx) => <li className='errors' key={idx}>{error}</li>)}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setSongName(e.target.value)}
value={songName}
placeholder="Song Name"
name="Song Name"
/>
<input
type="text"
onChange={(e) => setSongLink(e.target.value)}
value={songLink}
placeholder="Song Link"
name="Audio File"
/>
<button type="submit">Submit</button>
</form>
</div>
);
};
export default EditSongForm;
Right now when I have a list of songs and click the button for the edit form to appear it applies to the entire list if I have more than one song uploaded. I'm not sure how to make it specific enough so only one form opens at a time.
The solution was to create a component for specific song details and then render that in the .map.
<h1>Song List</h1>
<ol>
{songs.map(({ id, songName, songLink, userId }) => (
<div>
<SpecificSong id={id} songName={songName} songLink={songLink} userId={userId} />
</div>
))}
</ol>