useEffect runs Mutiple times and upadting the views - reactjs

I have A Component (the GetSingleVideoID Component) that needs to be rendered every time the id changes
You can check the code on Github the repo Link
the Issue is that the route to get the single video I have put the views of the video to increment when ever the route hits. and then the use Effect keep incrementing the views when I am already on the Component
The getVideoById Route
// #route GET api/v1/video/:id
// #desc GET single video
// #access Private
router.get('/:id', auth, async (req, res) => {
const { id } = req.params;
try {
const videos = await Video.find().populate('channel', ['image', 'title']);
const video = await Video.findById(id).populate('channel', [
'image',
'title',
'subscribers',
'usersToNotify',
]);
if (!video) {
return res.status(404).json({ msg: 'Video not found!' });
}
video.views += 1;
const catVid = {};
video.categoryVids = [];
videos
.filter(
(vid) =>
vid.category === video.category &&
vid._id.toString() !== video._id.toString()
)
.forEach((v) => {
catVid.video = v._id;
catVid.videoTitle = v.title;
catVid.channelTitle = v.channel.title;
catVid.thumb = v.thumbnail;
catVid.views = v.views;
catVid.createdAt = v.createdAt;
video.categoryVids = [...video.categoryVids, catVid];
});
await video.save();
res.status(200).json(video);
} catch (err) {
if (err.kind == 'ObjectId') {
return res.status(400).json({ msg: 'Video not found!' });
}
console.error(err.message);
res.status(500).send('Server Error');
}
});
When I get rid of the func from the dependencies the Subscribe and Notify doesn't work perfectly anymore (like update when i click) I would have to refresh for the changes to work.
import React, { useState, useEffect, useCallback } from 'react';
import moment from 'moment';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { useVideoGlobalContext } from '../actions/video';
import { useAuthGlobalContext } from '../actions/auth';
import { useChannelGlobalContext } from '../actions/channel';
import { timeSince, intToString } from '../utils/utilities';
import Section from '../components/Section';
const SingleVideo = () => {
const {
singleVideo,
loading,
getVideoByID,
commentVideo,
likeVideo,
deleteComment,
likes,
unlikes,
unlikeVideo,
deleteVideo,
} = useVideoGlobalContext();
const { user, isAuthenticated, userChannel } = useAuthGlobalContext();
const { id } = useParams();
const {
notifyChannel,
msg,
newChannel,
unnotifyChannel,
subscribeChannel,
unsubscribeChannel,
} = useChannelGlobalContext();
const [text, setText] = useState('');
const [subscribed, setSubscribed] = useState(false);
const [sub, setSub] = useState(false);
const [liked, setLiked] = useState(false);
const [like, setLike] = useState(false);
const [disliked, setDisliked] = useState(false);
const [dislike, setDislike] = useState(false);
const [notified, setNotified] = useState(false);
const [notify, setNotify] = useState(false);
const navigate = useNavigate();
useEffect(() => {
if (singleVideo) {
const isSubscribed = singleVideo.channel.subscribers.some(
(cha) => cha.user == user._id
);
setSub(subscribed);
setSubscribed(isSubscribed);
}
}, [singleVideo, subscribeChannel, unsubscribeChannel, newChannel]);
useEffect(() => {
if (singleVideo) {
const isLiked = singleVideo.likes.some((like) => like.user == user._id);
setLike(liked);
setLiked(isLiked);
}
}, [singleVideo, likeVideo, unlikeVideo, newChannel]);
useEffect(() => {
if (singleVideo) {
const isLiked = singleVideo.unlikes.some((like) => like.user == user._id);
setDislike(liked);
setDisliked(isLiked);
}
}, [singleVideo, likeVideo, unlikeVideo, newChannel]);
useEffect(() => {
if (singleVideo) {
const isNotified = singleVideo.channel.usersToNotify.some(
(cha) => cha.user == user._id
);
setNotify(notified);
setNotified(isNotified);
}
}, [singleVideo, notifyChannel, unnotifyChannel, msg]);
// // console.log(data);
useEffect(() => {
getVideoByID(id);
}, [id]);
// useEffect(() => {
// getVideoByID(id);
// }, [getVideoByID, id]);
const onSubscribe = () => {
setSub(!sub);
if (sub && subscribed) {
unsubscribeChannel(singleVideo.channel._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// alert(subMsg.msg);
} else {
subscribeChannel(singleVideo.channel._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// alert(subMsg.msg);
}
};
const onNotification = () => {
setNotify(!notify);
if (notify && notified) {
unnotifyChannel(singleVideo.channel._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// console.log(msg);
} else {
notifyChannel(singleVideo.channel._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// console.log(msg);
}
};
const likeAVideo = () => {
setLike(!liked);
if (like && liked) {
alert('You already like this');
} else {
likeVideo(singleVideo._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// console.log(msg);
}
};
const commentAVideo = (e) => {
e.preventDefault();
commentVideo(singleVideo._id, text);
setText('');
};
const dislikeAVideo = () => {
setDislike(!disliked);
if (dislike && disliked) {
alert('You already unlike this');
} else {
unlikeVideo(singleVideo._id);
// if (msg.msg) {
// alert(msg.msg);
// }
// console.log(msg);
}
};
const onDelete = (vid) => {
deleteVideo(vid);
navigate('/');
};
return (
<Section nameClass='video-page'>
{singleVideo === null ? (
<h1>Loading....</h1>
) : (
<div class='v-container'>
<div class='video-player'>
<div class='vid-container'>
<video
src={`http://localhost:5000/${singleVideo.videoPath}`}
controls
></video>
{isAuthenticated &&
userChannel &&
userChannel._id === singleVideo.channel._id ? (
<div>
<Link to={`/video/edit/${singleVideo._id}`}>
<button>
<i class='fa-solid fa-pen-to-square'></i>
</button>
</Link>
<button onClick={() => onDelete(singleVideo._id)}>
<i class='fa-solid fa-trash-can'></i>
</button>
</div>
) : (
''
)}
</div>
<div class='vid-info'>
<div class='tags mb-2 primary'>
{singleVideo.tags.map((tag) => (
<span>#{tag} </span>
))}
</div>
<h3>
{singleVideo.title}{' '}
<span class='badge-cate'>{singleVideo.category}</span>
</h3>
<div class='views-date'>
<span class='views'>
{intToString(singleVideo.views)} views
</span>
<span class='date'>
{moment(singleVideo.createdAt).format('MMM Do YYYY')}
</span>
</div>
<div class='desc'>{singleVideo.description}</div>
<div class='channel-card'>
<div class='channel-info'>
<img
src={
singleVideo.channel.image
? `http://localhost:5000/${singleVideo.channel.image}`
: '/img/no-image.png'
}
alt=''
/>
<div class='name-subs'>
<h4>{singleVideo.channel.title}</h4>
<span class='subs'>
{singleVideo.channel.subscribers.length} Subscribers
</span>
</div>
</div>
<div class='btns'>
<button onClick={likeAVideo} class='thumbs'>
<i
class={`fa-solid fa-thumbs-up ${liked ? 'primary' : ''}`}
></i>
</button>
<span>{singleVideo.likes.length}</span>
<button onClick={dislikeAVideo} class='thumbs'>
<i
class={`fa-solid fa-thumbs-down ${
disliked ? 'primary' : ''
}`}
></i>
</button>
<span>{singleVideo.unlikes.length}</span>
<button
onClick={onSubscribe}
className={`sub ${subscribed ? 'disabled' : ''}`}
>
Subscribe
</button>
<button onClick={onNotification} class='bell'>
<i
className={`fa-solid fa-bell ${
notified ? 'primary' : ''
}`}
></i>
</button>
</div>
</div>
<div class='comments'>
<h4>{singleVideo.comments.length} Comments</h4>
<form class='form' onSubmit={commentAVideo}>
<div class='form-control'>
<input
type='text'
name='text'
placeholder='Add a comment...'
class='input'
value={text}
onChange={(e) => setText(e.target.value)}
/>
</div>
<button class='btn'>Add</button>
</form>
<div class='comment-container'>
{singleVideo.comments.length > 0 ? (
singleVideo.comments.map((comment) => (
<div class='comment'>
<img
src={
comment.userImage
? `http://localhost:5000/${comment.userImage}`
: '/img/photo-me.jpeg'
}
alt=''
/>
{isAuthenticated && user._id === comment.user ? (
<button
onClick={() =>
deleteComment(singleVideo._id, comment._id)
}
class='delete'
>
<i class='fa-solid fa-trash-can'></i>
</button>
) : (
''
)}
<div class='info'>
<div>
<h5>{comment.userName}</h5>
<span>
{(timeSince(comment.date) ||
timeSince(comment.date) !== undefined) &&
timeSince(comment.date)}
</span>
</div>
<p>{comment.text}</p>
</div>
</div>
))
) : (
<h3 className='text-center'>No Comments</h3>
)}
</div>
</div>
</div>
</div>
<div class='category-videos'>
<h3>
More Videos in <span class='primary'>{singleVideo.category}</span>
</h3>
<div class='category-vids'>
{singleVideo.categoryVids.length > 0 ? (
singleVideo.categoryVids.map((video) => (
<div class='cat-vid'>
<img
src={
video.thumb
? `http://localhost:5000/${video.thumb}`
: '/img/no-image.png'
}
alt=''
/>
<div class='info'>
<h4>{video.videoTitle}</h4>
<span>
{video.channelTitle} <i class='fa-solid fa-check'></i>{' '}
</span>
<div>
<span class='views'>
{intToString(video.views)} views
</span>
<span class='time'>{timeSince(video.createdAt)}</span>
</div>
</div>
</div>
))
) : (
<h4 className='text-center'>
No Videos for {singleVideo.category}...
</h4>
)}
</div>
</div>
</div>
)}
</Section>
);
};
export default SingleVideo;
on the useEffect I have put the getVideoByID function on the dependencies as you can see on the code above.

Related

how to add data in localstorage in react

I am arslan Chaudhry. currently, I am working on an eCommerce site where I will store data in local storage because I don't have too much backend knowledge. how I can add functionality like delete and update on another folder.
my code is given below.
Books.js
import React from "react";
import "./components/book.css";
import Carousel from "react-multi-carousel";
import "react-multi-carousel/lib/styles.css";
import { FaShoppingCart } from "react-icons/fa";
import { useEffect } from "react";
import { useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { createContext } from "react";
const Books = () => {
let arr=()=>{
let dat=localStorage.getItem("products")
if (dat) {
return JSON.parse(dat)
}
else{
return []
}
}
const [booksData, setbooksData] = useState([]);
const [productData, setproductData] = useState(arr());
let nav = useNavigate();
// slider
const responsive = {
superLargeDesktop: {
breakpoint: { max: 4000, min: 3000 },
items: 5,
},
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 3,
},
tablet: {
breakpoint: { max: 1024, min: 464 },
items: 2,
},
mobile: {
breakpoint: { max: 464, min: 0 },
items: 1,
},
};
let croser = useRef("");
let loding = useRef("");
const getJason = async () => {
try {
let fea = await fetch(
"https://script.google.com/macros/s/AKfycbxFCG7S-kjncQZwvcMnqq4wXoBAX8ecH1zkY2bLP7EE-YHlnKbiJ3RUuHtWLe6sIK30Kw/exec"
);
let acData = await fea.json();
let itemsData = acData.shop.filter((element) => {
if (element.name) {
return element;
}
});
setbooksData(itemsData);
if (itemsData) {
croser.current.style.filter = "blur(0px)";
loding.current.style.display = "none";
}
} catch (error) {
croser.current.style.filter = "blur(0px)";
loding.current.style.display = "none";
}
};
// get product data from api
useEffect(() => {
getJason();
}, []);
// go to cart button
const goto = () => {
nav("/Cart");
};
// local data
let addNow=(e)=>{
let data=productData.find((element)=>{return element.id === e.id });
let cart;
if (data) {
cart=productData.map((element)=>{
return element.id === e.id ? {...element, quantity:element.quantity+1}:element
})
}
else{
cart=[...productData,{...e, quantity:1}]
}
setproductData(cart);
};
useEffect(() => {
localStorage.setItem("products",JSON.stringify(productData))
}, [productData])
console.log(productData);
return (
<>
<div className="row " style={{ marginTop: "10px" }}>
<div className="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div className="section-headline text-center">
<h2>Books Shop</h2>
</div>
</div>
</div>
<div className="lodingBooks" ref={loding}>
<div class="spinner-border" role="status"></div>
<h4>Please wait....</h4>
</div>
<div ref={croser}>
<div className=" shadow go_to_cart" onClick={goto}>
<i class="bi bi-cart-check text-white"></i>
</div>
<Carousel responsive={responsive} className="container">
{booksData.map((element) => {
return (
<>
<div class="container page-wrapper">
<div class="page-inner">
<div class="row">
<div class="el-wrapper">
<div class="box-up">
<img class="img" src={element.images} alt="" />
<div class="img-info">
<div class="info-inner">
<span class="p-name text-info">
{element.name}
</span>
<span class="p-company ">Author:CHAUDHRY</span>
</div>
<div class="a-size ">
About:This is a complete book on javascript
<span class="size"></span>
</div>
</div>
</div>
<input
type="text"
value={1}
style={{ display: "none" }}
/>
<div class="box-down">
<div class="h-bg">
<div class="h-bg-inner"></div>
</div>
<a class="cart">
<span class="price">{element.price + "$"}</span>
<span
class="add-to-cart btn btn-sm"
style={{ backgroundColor: "#3EC1D5" }}
onClick={()=>{addNow(element)}}
>
<span class="txt">
ADD TO CART <FaShoppingCart />
</span>
</span>
</a>
</div>
</div>
</div>
</div>
</div>
</>
);
})}
</Carousel>
</div>
</>
);
};
export default Books;
and here is my cart file. where i want to perform the action like update and delete.
Cart.js
import React from "react";
import "./components/cart.css";
import { useEffect } from "react";
const Cart = () => {
let data = localStorage.getItem("products");
let javaData = JSON.parse(data);
let removeData = (e) => {
};
useEffect(() => {
localStorage.clear()
}, [])
return (
<>
<div class="container mt-5 mb-5">
<div class="d-flex justify-content-center row">
<div class="col-md-8">
<div class="p-2 shoingTitle">
<h4>Shop Now</h4>
<span class="text-danger">Remove all</span>
</div>
{javaData ? (
javaData.map((item) => {
return (
<>
<div class="d-flex flex-row justify-content-between align-items-center p-2 bg-white mt-4 px-3 rounded">
<div class="mr-1 imageandpara">
<img class="rounded" src={item.images} width="70" />
<span class="font-weight-bold">{item.name}</span>
</div>
<div class="d-flex flex-column align-items-center product-details">
<div class="d-flex flex-row product-desc"></div>
</div>
<div class="d-flex flex-row align-items-center qty">
<i class="minusSign shadow">
<i class="bi bi-dash"></i>
</i>
<span class="text-grey quantityNumber">
{item.quantity}
</span>
<i class="minusSign shadow">
<i class="bi bi-plus"></i>
</i>
</div>
<div>
<span class="text-grey productAmount">{`${
item.quantity * item.price
}$`}</span>
</div>
<div
class="d-flex align-items-center text-dark "
style={{
cursor: "pointer",
fontWeight: "900",
fontSize: "15px",
}}
onClick={() => {
removeData(item);
}}
>
<i class="bi bi-x text-danger"></i>
</div>
</div>
</>
);
})
) : (
<h3 style={{ textAlign: "center" }}>Cart is empety</h3>
)}
<div class="d-flex flex-row align-items-center mt-3 p-2 bg-white rounded">
<input
type="text"
class="form-control gift-card "
placeholder="discount code/gift card"
/>
<button
class="btn btn-sm ml-3 shadow"
type="button"
style={{
outline: "#3EC1D5",
backgroundColor: "#3EC1D5",
color: "white",
}}
>
Apply
</button>
</div>
<div class="totalItems">
Total Items: <strong>12</strong>
</div>
<span class="TotalPrice">
Total price: <strong>12$</strong>
</span>
<div class="d-flex flex-row align-items-center mt-3 p-2 bg-white rounded">
<button
class="btn btn-block btn-sm ml-2 pay-button shadow"
type="button"
style={{ backgroundColor: "#3EC1D5" }}
>
Proceed to Pay
</button>
</div>
</div>
</div>
</div>
</>
);
};
export default Cart;
Try this for add:
let removeData = (e) => {
localStorage.setItem("name of the item") // e.target.name
};
There's alot going on in your site :)
I think it will be responsible to create a context, that will serve the cart to all other components.
Things to note here, (Found some little improvements)
Run the function in the useState hook, don't just leave it there like you did
When using Array.filter you need to return a boolean iorder for it too filter your array properly.
This is the code I brought up hope it helps you out.
CartContext.js file.
import React, { createContext, useContext, useEffect, useState } from "react";
export const CartContext = createContext();
function Cart({ children }) {
const arr = useCallback(() => {
let dat = localStorage.getItem("products");
if (dat) {
return JSON.parse(dat);
} else {
return [];
}
}, []);
const [productData, setproductData] = useState(() => arr());
const getJason = async () => {
try {
let fea = await fetch(
"https://script.google.com/macros/s/AKfycbxFCG7S-kjncQZwvcMnqq4wXoBAX8ecH1zkY2bLP7EE-YHlnKbiJ3RUuHtWLe6sIK30Kw/exec"
);
let acData = await fea.json();
// filter callback function should return a boolean. That is either true or false in order to make it work.
// SO i think this function isn't going to function properly
let itemsData = acData.shop.filter((element) => {
if (element.name) {
return element;
}
});
setbooksData(itemsData);
if (itemsData) {
croser.current.style.filter = "blur(0px)";
loding.current.style.display = "none";
}
} catch (error) {
croser.current.style.filter = "blur(0px)";
loding.current.style.display = "none";
}
};
// get product data from api
useEffect(() => {
getJason();
}, []);
const addProduct = (e) => {
// check if product id available on cart
const findProduct = productData.find((element) => {
return element.id === e.id;
});
// add first quantity if not available
if (!findProduct)
return setproductData([...productData, { ...e, quantity: 1 }]);
// increase quantity by 1
const newCart = productData.map((element) => {
return {
...element,
quantity: element.id === e.id ? element.quantity + 1 : element.quantity,
};
});
setproductData(newCart);
};
const removeProduct = (e) => {
// check if product id available on cart
const findProductQuantity = productData.find((element) => {
return element.id === e.id && element.quantity >= 1;
});
// add first quantity if not available
if (!findProduct)
// Your ui should prevent this
return;
// decrease quantity by 1
const reducedQuantityCart = productData.map((element) => {
return {
...element,
quantity: element.id === e.id ? element.quantity - 1 : element.quantity,
};
});
// filter out all products with quantity less than 1 (quantity : 0)
const newCart = productData.filter((element) => {
return element.quantity >= 1;
});
setproductData(newCart);
};
const deleteProduct = (e) => {
// check if product id available on cart
const findProduct = productData.find((element) => {
return element.id === e.id;
});
// add first quantity if not available
if (!findProduct)
// Your ui should prevent this
return;
const productIndex = productData.findIndex((element) => {
return element.id === e.id;
});
// splice (delete) product from the productData array
const newCart = [productData].splice(productIndex, 1);
setproductData(newCart);
};
const value = {
productData,
addProduct,
removeProduct,
deleteProduct,
};
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}
In here you create a context, create all your function you will use to update your cart, and pass them to your Context provider
// create a hook can you use anywhere in the app
export const useCart = () => {
const context = useContext(CartContext);
if (!context) {
throw new Error("Must use useCart in CartContext Child");
}
const { productData, addProduct, removeProduct, deleteProduct } = context;
return { productData, addProduct, removeProduct, deleteProduct };
};
For this you create a custom hook that you can use inside any component provided it is a child of the Cart context. So make sure you wrap it all around your app
// Use Case
const SomeComponent = () => {
const { productData, addProduct, removeProduct } = useCart();
return (
<div>
<p> Number of Products: {productData.length}</p>
<div>
{productData?.map((product) => (
<div>
<p>{product?.name}</p>
<button onClick={() => addProduct(product)}>add</button>
<button onClick={() => removeProduct(product)}>
subtract/reduce
</button>
<button onClick={() => deleteProduct(product)}>delete</button>
</div>
))}
</div>
</div>
);
};
Use case Scenario od how this code will work. Hope you find this helful

React parent component needs child function to return data

I think I need a call back function, but do not understand the proper syntax given a parent component calling a child function.
Here is the stripped down parent component followed by the function FilesUpload.
I need the File.Name from child returned and setState({fileName}) in parent component.
Hopefully painfully obvious to someone who knows how to do this.
Thank you in advance for solution.
Rob
#davidsz - any ideas?
...
//Stripped down ParentComponent.jsx
import React, { Component } from 'react'
import FilesUpload from "../Services/FilesUpload";
class ParentComponent extends Component {
constructor(props) {
super(props)
this.state = {
fileName: null
}
this.changefileNameHandler = this.changefileNameHandler.bind(this);
}
changefileNameHandler= (event) => {
this.setState({fileName: event.target.value});
}
componentDidMount(){
}
render() {
return (
<div>
<td>this.state.fileName </td>
<FilesUpload onUpdate={this.changefileNameHandler}/>
</div>
)
}
}
export default ParentComponent
//functional service FilesUpload.js
import React, { useState, useEffect, useRef } from "react";
import UploadService from "../Services/FileUploadService";
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
const UploadFiles = () => {
const [selectedFiles, setSelectedFiles] = useState(undefined);
const [progressInfos, setProgressInfos] = useState({ val: [] });
const [message, setMessage] = useState([]);
const [fileInfos, setFileInfos] = useState([]);
const progressInfosRef = useRef(null)
useEffect(() => {
UploadService.getFiles().then((response) => {
setFileInfos(response.data);
});
}, []);
const selectFiles = (event) => {
setSelectedFiles(event.target.files);
setProgressInfos({ val: [] });
};
const upload = (idx, file) => {
let _progressInfos = [...progressInfosRef.current.val];
return UploadService.upload(file, (event) => {
_progressInfos[idx].percentage = Math.round(
(100 * event.loaded) / event.total
);
setProgressInfos({ val: _progressInfos });
})
.then(() => {
toast.info(file.name + " Uploaded")
setMessage((prevMessage) => ([
...prevMessage,
"Uploaded the file successfully: " + file.name,
]));
})
.catch(() => {
_progressInfos[idx].percentage = 0;
setProgressInfos({ val: _progressInfos });
setMessage((prevMessage) => ([
...prevMessage,
"Could not upload the file: " + file.name,
]));
});
};
const uploadFiles = () => {
const files = Array.from(selectedFiles);
let _progressInfos = files.map(file => ({ percentage: 0, fileName: file.name }));
progressInfosRef.current = {
val: _progressInfos,
}
const uploadPromises = files.map((file, i) => upload(i, file));
Promise.all(uploadPromises)
.then(() => UploadService.getFiles())
.then((files) => {
setFileInfos(files.data);
});
setMessage([]);
};
return (
<div>
{progressInfos && progressInfos.val.length > 0 &&
progressInfos.val.map((progressInfo, index) => (
<div className="mb-2" key={index}>
<span>{progressInfo.fileName}</span>
<div className="progress">
<div
className="progress-bar progress-bar-info"
role="progressbar"
aria-valuenow={progressInfo.percentage}
aria-valuemin="0"
aria-valuemax="100"
style={{ width: progressInfo.percentage + "%" }}
>
{progressInfo.percentage}%
</div>
</div>
</div>
))}
<div className="row my-3">
<div className="col-8">
<label className="btn btn-default p-0">
<input type="file" multiple onChange={selectFiles} />
</label>
</div>
<div className="col-4">
<button
className="btn btn-success btn-sm"
disabled={!selectedFiles}
onClick={uploadFiles}
>
Upload
</button>
</div>
</div>
{message.length > 0 && (
<div className="alert alert-secondary" role="alert">
<ul>
{message.map((item, i) => {
return <li key={i}>{item}</li>;
})}
</ul>
</div>
)}
<div className="card">
{/* <div className="card-header">List of Files</div> */}
<ul className="list-group list-group-flush">
{!fileInfos &&
fileInfos.map((file, index) => (
<li className="list-group-item" key={index}>
{/* <a href={file.url}>{file.name}</a> */}
</li>
))}
</ul>
</div>
<ToastContainer position="top-center" autoClose={1000}/>
</div>
);
};
export default UploadFiles;
...
I'm not quite sure I understand your question perfectly, but do you want to pass down the changefileNameHandler function as a prop to your FilesUpload functional component?
In this case you can just add props as a paremeter:
const UploadFiles = (props) => { ...
and call it wherever you need it:
props.onUpdate(event)

Firebase data changes state but does not render component

I am trying to attatch a listener to the chatRoomIds subcollection for a specific user from the User collection.After retriving the chatroomIds for the specific users I have to loop through each to get the specific chatRoom document from the chatRoomIds array in which each element is an object containing chatRoomId that will be used to query for the specific chatRoom.The problem is that the state is working correctly but does not render the Messaage Card component unless I toggle another state in the Message component in the react dev tools on the messages link again after everything loads.
function Message() {
const [currentUser] = useAuthState(auth)
const [{user}] = useStateValue()
const {show, setNotificationPopup, setShow} = useChat();
const [chats, setChats] = useState([])
const [loading, setLoading] = useState(true)
const [chatRoomArraySnap, error] = useCollection(db.collection("Users").doc(user?.uid).collection('ChatRoomIds'))
const chatList = []
const {setLoader, loader} = useLoader();
let chatRoomIds = []
let params = useParams();
const messageId = params.id;
const userObj = {
email: user?.email,
objId: user?.uid,
userName: user?.displayName
};
useEffect(() => {
{
params.id ? setShow(true)
:
setShow(false)
}
}, [])
useEffect(() =>{
// chatList = []
if(user.uid){
db.collection("Users").doc(user.uid).collection('ChatRoomIds').get()
.then(
snapshot => {
snapshot.docs.map((each) => {
// chatRoomIds.push(each.data())
db.collection('ChatRooms').where("chatRoomId", "==", each.data().id).orderBy('dateLastUpdated','desc').onSnapshot(snapshot => {
snapshot.docs.forEach(doc => {
chats.push(doc.data())
console.log(doc.data())
})
// setChats(chatList)
setLoading(false)
// snapshot.docChanges().forEach((change) => {
// if (change.type === "added") {
// console.log("New : ", change.doc.data());
// chatList.push(change.doc.data())
//
// }
// if (change.type === "modified") {
// console.log("Modified : ", change.doc.data());
// setNotificationPopup(change.doc.data())
//
// }
// if (change.type === "removed") {
// console.log("Removed : ", change.doc.data());
// }
// })
})
})
console.log(chatRoomIds)
// chatRoomIds?.map((each) => {
// db.collection('ChatRooms').where("chatRoomId", "==", each.id).orderBy('dateLastUpdated','desc').onSnapshot(snapshot => {
// snapshot.docs.forEach(doc => {
// chatList.push(doc.data())
// console.log(doc.data())
// })
//
// setChats(chatList)
// setLoading(false)
//
//
//
//
//
// // snapshot.docChanges().forEach((change) => {
// // if (change.type === "added") {
// // console.log("New : ", change.doc.data());
// // chatList.push(change.doc.data())
// //
// // }
// // if (change.type === "modified") {
// // console.log("Modified : ", change.doc.data());
// // setNotificationPopup(change.doc.data())
// //
// // }
// // if (change.type === "removed") {
// // console.log("Removed : ", change.doc.data());
// // }
// // })
//
//
// })
// })
}
)
console.log(chatList)
// if (chatRoomArraySnap) {
//
// chatRoomArraySnap.docs.map((each) => {
// chatRoomIds.push(each.data())
// })
// // chatList=[]
// // setChats([])
//
// chatRoomIds.map((each) => {
// db.collection('ChatRooms').where("chatRoomId", "==", each.id).orderBy('dateLastUpdated','desc').onSnapshot(snapshot => {
// snapshot.docs.forEach(doc => {
// chatList.push(doc.data())
// })
//
//
//
//
//
//
// // snapshot.docChanges().forEach((change) => {
// // if (change.type === "added") {
// // console.log("New : ", change.doc.data());
// // chatList.push(change.doc.data())
// //
// // }
// // if (change.type === "modified") {
// // console.log("Modified : ", change.doc.data());
// // setNotificationPopup(change.doc.data())
// //
// // }
// // if (change.type === "removed") {
// // console.log("Removed : ", change.doc.data());
// // }
// // })
//
//
// })
// })
// setChats(chatList)
// console.log(chatList)
//
// }
}
},[])
return (
<>
<Header/>
<div className='container'>
<div className='message '>
<div className='row'>
<div className='col-md-4 col-lg-4 col-sm-12 lg-view'>
<div className=' pt-3 user-list-section'>
<div className='lg-view'>
<h5 className='text-light ml-5 mb-5`'>Messages</h5>
<div className={`d-flex align-items-center`}>
<Search functionHandler={handleSearchChat} props={'#13161A'}/>
<CreateGroupBtn/>
</div>
</div>
<div className='user-list'>
{!loading && chats ? chats.map(chat => {
// console.log(chat.data());
return (<>
<MessageCard key={chat.chatRoomId}
id={chat.chatRoomId} chats={chat}/>
</>
)
}) : <></>}
</div>
</div>
</div>
{
!show ? <>
<div className='sm-view w-100 pl-3 pr-3 '>
<div className=' w-100 d-flex justify-content-center pt-4'>
<div className='flex-grow-1'>
<h4 className='text-light'>Messages</h4>
<p>Talk with your friends</p>
</div>
<div className="search-container flex-grow-1 ">
<div className='search d-flex float-right'>
<CreateGroupBtn/>
</div>
</div>
</div>
<Search functionHandler={handleSearchChat}/>
</div>
<div className='col-md-4 col-lg-4 col-sm-12 sm-view'>
<div className=' pt-3 user-list-section'>
<div className='lg-view'>
<h5 className='text-light ml-5 mb-5`'>Messages</h5>
<div className={`d-flex align-items-center`}>
<Search functionHandler={handleSearchChat} props={'#13161A'}/>
<CreateGroupBtn/>
</div>
</div>
<div className='user-list'>
{!loading && chats !== undefined && chats !== null && chats ? chats.map(chat => {
// console.log(chat.data());
return (<>
<MessageCard key={chat.chatRoomId}
id={chat.chatRoomId} chats={chat}/>
</>
)
}) : <></>}
</div>
</div>
</div>
</>
:
<>
<div className='col-md-8 p-0 col-lg-8 col-sm-12'>
{
messageId && currentUser.email && <MessageWindow/>
}
</div>
</>
}
</div>
</div>
</div>
{!show && <MobileNavbar/>}
</>
);
}
///Working Code
function Message() {
const [currentUser] = useAuthState(auth)
const [{user}] = useStateValue()
const {show, setShow} = useChat();
const [chats, setChats] = useState([])
const [loading, setLoading] = useState(true)
let params = useParams();
const messageId = params.id;
useEffect(() => {
{
params.id ? setShow(true)
:
setShow(false)
}
}, [])
useEffect(() => {
if (user.uid) {
db.collection("Users")
.doc(user.uid)
.collection("ChatRoomIds")
.get()
.then((snapshot) => {
snapshot.docs.map((each) => {
db.collection("ChatRooms")
.where("chatRoomId", "==", each.data().id)
.orderBy("dateLastUpdated", "desc")
.onSnapshot((snapshot) => {
console.log(snapshot.docs.map((doc) => doc.data()))
setChats((chats) =>
chats.concat(snapshot.docs.map((doc) => doc.data()))
);
setLoading(false)
});
});
});
}
}, []);
return (
<>
<Header/>
<div className='container'>
<div className='message '>
<div className='row'>
<div className='col-md-4 col-lg-4 col-sm-12 lg-view'>
<div className=' pt-3 user-list-section'>
<div className='lg-view'>
<h5 className='text-light ml-5 mb-5`'>Messages</h5>
<div className={`d-flex align-items-center`}>
<Search functionHandler={handleSearchChat} props={'#13161A'}/>
<CreateGroupBtn/>
</div>
</div>
<div className='user-list'>
{/*{!loading && chats ? chats.map(chat => {*/}
{/* // console.log(chat.data());*/}
{/* return (<>*/}
{/* <MessageCard key={chat.chatRoomId}*/}
{/* id={chat.chatRoomId} chats={chat}/>*/}
{/* </>*/}
{/* )*/}
{/*}) : <></>}*/}
</div>
</div>
</div>
{
!show ? <>
<div className='sm-view w-100 pl-3 pr-3 '>
<div className=' w-100 d-flex justify-content-center pt-4'>
<div className='flex-grow-1'>
<h4 className='text-light'>Messages</h4>
<p>Talk with your friends</p>
</div>
<div className="search-container flex-grow-1 ">
<div className='search d-flex float-right'>
<CreateGroupBtn/>
</div>
</div>
</div>
<Search functionHandler={handleSearchChat}/>
</div>
<div className='col-md-4 col-lg-4 col-sm-12 sm-view'>
<div className=' pt-3 user-list-section'>
<div className='lg-view'>
<h5 className='text-light ml-5 mb-5`'>Messages</h5>
<div className={`d-flex align-items-center`}>
<Search functionHandler={handleSearchChat} props={'#13161A'}/>
<CreateGroupBtn/>
</div>
</div>
<div className='user-list'>
{!loading && chats ? chats.map(chat => {
return (<>
<MessageCard key={chat.chatRoomId}
id={chat.chatRoomId} chats={chat}/>
</>
)
}) : <></>}
</div>
</div>
</div>
</>
:
<>
<div className='col-md-8 p-0 col-lg-8 col-sm-12'>
{
messageId && currentUser.email && <MessageWindow/>
}
</div>
</>
}
</div>
</div>
</div>
{!show && <MobileNavbar/>}
</>
);
}
export default Message;
You've a lot of commented out code, but what isn't commented out has a glaring issue, state mutation. You are pushing directly into the chats state array. This is why you have to toggle/update some other state in order to trigger the component to rerender and expose the mutation.
You should properly enqueue these chat updates. On the last/innermost snapshot you should use a functional state update to update from the previous state and because you are working in a loop, and concatenate an array of new chat documents.
Array.prototype.concat
The concat() method is used to merge two or more arrays. This method
does not change the existing arrays, but instead returns a new array.
Array.prototype.map
The map() method creates a new array populated with the results of
calling a provided function on every element in the calling array.
.onSnapshot((snapshot) => {
setChats((chats) =>
chats.concat(snapshot.docs.map((doc) => doc.data()))
);
});
Example:
useEffect(() => {
if (user.uid) {
db.collection("Users")
.doc(user.uid)
.collection("ChatRoomIds")
.get()
.then((snapshot) => {
snapshot.docs.map((each) => {
db.collection("ChatRooms")
.where("chatRoomId", "==", each.data().id)
.orderBy("dateLastUpdated", "desc")
.onSnapshot((snapshot) => {
setChats((chats) =>
chats.concat(snapshot.docs.map((doc) => doc.data()))
);
});
});
});
}
}, []);
Update - to resolve duplicates
Still use a functional state update but run the chats array through a filter first to check if there are no new chats with matching chatRoomId properties. If there is a match return false to remove it from the array, otherwise keep it. The filter function returns a new array that you can concatenate to the new chats results.
useEffect(() => {
if (user.uid) {
db.collection("Users")
.doc(user.uid)
.collection("ChatRoomIds")
.get()
.then((snapshot) => {
snapshot.docs.map((each) => {
db.collection("ChatRooms")
.where("chatRoomId", "==", each.data().id)
.orderBy("dateLastUpdated", "desc")
.onSnapshot((snapshot) => {
const newChats = snapshot.docs.map((doc) => doc.data());
setChats((chats) => {
const filtered = chats.filter(chat =>
!newChats.some(newChat =>
newChat.chatRoomId === chat.chatRoomId
)
);
newChats.concat(filtered);
});
});
});
});
}
}, []);

Problem in implementing file Upload in Reactjs

I am Implementing a file upload feature to get resume of job applicants in my Reactjs form.
Now whenever I click on Upload everything works fine but while the file is uploading browser throws an error.
Here is my fileUpload.js.
import React, { useState, useRef } from "react";
import axios, { CancelToken, isCancel } from "axios";
import { LinearProgressWithLabel } from "./ProgressBar";
const FileUpload = () => {
const [uploadPercentage, setUploadPercentage] = useState(0);
const cancelFileUpload = useRef(null);
const uploadFile = ({ target: { files } }) => {
let data = new FormData();
data.append("file", files[0]);
const options = {
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let percent = Math.floor((loaded * 100) / total);
if (percent < 100) {
setUploadPercentage(percent);
}
},
cancelToken: new CancelToken(
cancel => (cancelFileUpload.current = cancel)
)
};
const BASE_URL = "https://api.quantel.in"
axios
.post(
`${BASE_URL}/api/v1/jobs/resume`,
data,
options
)
.then(res => {
console.log(res);
setUploadPercentage(100);
setTimeout(() => {
setUploadPercentage(0);
}, 1000);
})
.catch(err => {
console.log(err);
if (isCancel(err)) {
alert(err.message);
}
setUploadPercentage(0);
});
};
const cancelUpload = () => {
if (cancelFileUpload.current)
cancelFileUpload.current("User has canceled the file upload.");
};
return (
<>
<p>
<input
type="file"
className="form-control-file"
onChange={uploadFile}
/>
</p>
{uploadPercentage > 0 && (
<div className="row mt-3">
<div className="col pt-1">
<LinearProgressWithLabel value={uploadPercentage} />
</div>
<div className="col-auto">
<span
className="text-primary cursor-pointer"
onClick={() => cancelUpload()}
>
Cancel
</span>
</div>
</div>
)}
</>
);
};
export default FileUpload;
When I click on the browse button the browser throws the following error. And I am confused why is it so?
When you check for uploadPercentage > 0 change that to this
{uploadPercentage > 0 ? (
<div className="row mt-3">
<div className="col pt-1">
<LinearProgressWithLabel value={uploadPercentage} />
</div>
<div className="col-auto">
<span
className="text-primary cursor-pointer"
onClick={() => cancelUpload()}
>
Cancel
</span>
</div>
</div>
) : null }
All your code inside { ... } is treated as a function (inside JSX) and in your case when uploadPercentage === 0 it is returning undefined.

How to save key in localStorage to be retrieve later when you go back to your page in ReactJS

I have a page, and I want to save the key to be retrieved later. This key is use to determine the last selected active item in the carousel. What do I need to do using localStorage in ReactJS?
import React, { Fragment, Component } from 'react'
import { truncateString } from '#helpers'
import './styles.css'
class RoutineSidebar extends Component {
handleOnClick = key => {
const { currentSlideKey } = this.props;
const isExerciseDone = this.props.skipExerciseValidation(currentSlideKey);
if(isExerciseDone || key < this.props.currentSlideKey) {
if(this.props.skipExerciseValidation(key === 0 ? 0 : key - 1)) {
this.props.setKey(key);
}
} else {
if(key === this.props.currentSlideKey + 1) {
this.props.openSkipExerModal();
}
}
React.useEffect(() => {
localStorage.setItem('selectedRoutine', key);
}, [key]);
}
checkExerciseStatusSkipped = key => {
const { routineList } = this.props;
return routineList[key].skipped;
};
checkExerciseStatusDone = key => {
const { routineList } = this.props;
return routineList[key].done;
}
checkExercisesSelected = key => {
const { routineList } = this.props;
return routineList[key];
}
render() {
const { exercises, currentSlideKey } = this.props;
const todaysRoutineThumbnails = exercises.map((exercise, key) => {
return (
<div key={key} onClick={() => this.handleOnClick(key)} className={key === currentSlideKey ? 'thumbnail-container selected-exercise' : 'thumbnail-container'}>
<div className="row todays-routine">
<div className="col-sm-6">
{
this.checkExerciseStatusSkipped(key) ? <Fragment><i className="fas fa-times-circle status-indicator-warning" />
<div className="scThumb">
<img className='active-thumbnail img-opaque' alt="todays-routine-thumbnail" src={exercise.thumbnail} />
</div>
</Fragment>
: this.checkExerciseStatusDone(key) ? <Fragment><i className="fas fa-check-circle status-indicator-done" />
<div className="scThumb">
<img className='active-thumbnail img-opaque' alt="todays-routine-thumbnail" src={exercise.thumbnail} />
</div>
</Fragment>
: !this.checkExerciseStatusDone(key) && !this.checkExerciseStatusSkipped(key) && <Fragment><div className="routine-exercise-counter">{key + 1}</div><div className="scThumb">
<img className='active-thumbnail' alt="todays-routine-thumbnail" src={exercise.thumbnail} />
</div>
</Fragment>
}
</div>
<div className="col-sm-6">
<div className="thumbnail-info clearfix">
<p className="thumbnail-title">{truncateString(exercise.exerciseName, 30)}</p>
<p className="thumbnail-description">This is the best exercise for back pain ever made</p>
</div>
</div>
</div>
</div>
)
})
return (
<div className="todays-routine-container">
<h1>{this.props.header}</h1>
{todaysRoutineThumbnails}
</div>
)
}
}
export default RoutineSidebar;
You can't use useEffect hook inside a class-based component.
to use localStorage with React you don't need anything, just use it directly.
I guess if you replaced your useEffect code with this code, it will work fine.
setTimeout(() => {
window.localStorage.setItem("selectedRoutine", key);
}, 0);
Just you need to create one util.js file for this kind of comman usages and paste below code and use these functions by just import.
export const getLocalStorage = (key) => {
const localStorageData = localStorage.getItem(key);
if (localStorageData) {
return JSON.parse(localStorageData);
} else {
return null;
}
}
export const setLocalStorage = (key, value) => {
if (key && value) {
localStorage.setItem(key, JSON.stringify(value));
}
}
Please try this
import React, { Fragment, Component } from 'react'
import { truncateString } from '#helpers'
import './styles.css'
class RoutineSidebar extends Component {
constructor(props){
super(props);
this.setState={
activeCarouselItem: ""
}
}
componentDidMount(){
this.setState({
activeCarouselItem: localStorage.getItem('selectedRoutine')?localStorage.getItem('selectedRoutine'): ""
})
}
handleOnClick = key => {
const { currentSlideKey } = this.props;
const isExerciseDone = this.props.skipExerciseValidation(currentSlideKey);
if(isExerciseDone || key < this.props.currentSlideKey) {
if(this.props.skipExerciseValidation(key === 0 ? 0 : key - 1)) {
this.props.setKey(key);
}
} else {
if(key === this.props.currentSlideKey + 1) {
this.props.openSkipExerModal();
}
}
// React.useEffect(() => {
// localStorage.setItem('selectedRoutine', key);
// }, [key]);
this.setState({activeCarouselItem: key })
}
checkExerciseStatusSkipped = key => {
const { routineList } = this.props;
return routineList[key].skipped;
};
checkExerciseStatusDone = key => {
const { routineList } = this.props;
return routineList[key].done;
}
checkExercisesSelected = key => {
const { routineList } = this.props;
return routineList[key];
}
render() {
const { exercises, currentSlideKey } = this.props;
const todaysRoutineThumbnails = exercises.map((exercise, key) => {
return (
<div key={key} onClick={() => this.handleOnClick(key)} className={key === currentSlideKey ? 'thumbnail-container selected-exercise' : 'thumbnail-container'}>
<div className="row todays-routine">
<div className="col-sm-6">
{
this.checkExerciseStatusSkipped(key) ? <Fragment><i className="fas fa-times-circle status-indicator-warning" />
<div className="scThumb">
<img className='active-thumbnail img-opaque' alt="todays-routine-thumbnail" src={exercise.thumbnail} />
</div>
</Fragment>
: this.checkExerciseStatusDone(key) ? <Fragment><i className="fas fa-check-circle status-indicator-done" />
<div className="scThumb">
<img className='active-thumbnail img-opaque' alt="todays-routine-thumbnail" src={exercise.thumbnail} />
</div>
</Fragment>
: !this.checkExerciseStatusDone(key) && !this.checkExerciseStatusSkipped(key) && <Fragment><div className="routine-exercise-counter">{key + 1}</div><div className="scThumb">
<img className='active-thumbnail' alt="todays-routine-thumbnail" src={exercise.thumbnail} />
</div>
</Fragment>
}
</div>
<div className="col-sm-6">
<div className="thumbnail-info clearfix">
<p className="thumbnail-title">{truncateString(exercise.exerciseName, 30)}</p>
<p className="thumbnail-description">This is the best exercise for back pain ever made</p>
</div>
</div>
</div>
</div>
)
})
return (
<div className="todays-routine-container">
<h1>{this.props.header}</h1>
{todaysRoutineThumbnails}
</div>
)
}
}
export default RoutineSidebar

Resources