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.
Related
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
i have a reusable contact form that works perfectly when used in the index.js file.
However when i use it from a component in the page folder i am having a 404 not found error message because it uses this route 3000/ourServices/conciergerie/api/contact/ instead of 3000/api/contact.
How do i ensure the it will always fetch the correct route? please see how i fetch the api below :
async function handleSubmit() {
const data = {
firstName,
email,
phone,
message,
};
const res = await fetch("api/contact", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
data: data,
token: "test",
}),
});
alert("Message sent! Thank you\nWe will be in touch with you soon!");
}
pages/ourServices/conciergerie
import Image from "next/image";
import { AiOutlinePlus, AiOutlineMinus } from "react-icons/ai";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { Contact } from "../../components/contact/Contact";
import es from "../../locales/es-ES/conciergerie.json";
import en from "../../locales/en-US/conciergerie.json";
import Icon1 from "/public/1.svg";
import Icon2 from "/public/2.svg";
import Icon3 from "/public/3.svg";
const Conciergerie = () => {
let { locale } = useRouter();
let t = locale === "es-ES" ? es : en;
// const { t } = useTranslation(locale, "conciergerie");
let myIcons = [Icon1, Icon2, Icon3];
const scrollToConciergerie = () => {
window.scrollTo({
top: 300,
behavior: "smooth",
});
};
const myLoader = ({ src, width, quality }) => {
return `${src}?w=${width}&q=${quality || 75}`;
};
const [showform, setshowform] = useState(false);
useEffect(() => {
window.addEventListener("load", scrollToConciergerie);
return () => {
window.removeEventListener("load", scrollToConciergerie);
};
});
const showContactForm = () => {
return <Contact />;
};
const contentData = t.conciergerieData;
return (
<div className="section" onLoad={scrollToConciergerie}>
<div className="container">
<div className="text-center">
<h1 className=" my-4 text-capitalize" id="conciergerie">
{t.conciergerieHeader}
</h1>
</div>
<h3 className="text-capitalize concierge-subheading mt-3">
{t.conciergerieTitle}
</h3>
<p className="lead concierge-subheading-text">{t.conciergerieText}</p>
</div>
<div className="container">
<div className="row text-center mt-5">
{contentData?.map((item, index) => {
return (
<div className="col-md-4" key={index}>
<span className="fa-stack fa-4x">
<Image
layout="responsive"
src={myIcons[index]}
alt="icons"
className="svg-inline--fa fa-solid fa-stack-1x fa-inverse img-fluid"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="house"
role="img"
objectFit="cover"
height={300}
width={300}
//loader={myLoader}
/>
</span>
<h4 className="my-3 text-hogar2 text-uppercase">
{item.title}
</h4>
<ul>
{item.text.map((text) => {
return (
<li key={text.id} className="list-unstyled">
<p className="m-0 text-muted text-list">
{text.content}
</p>
</li>
);
})}
</ul>
{item.id === "algomas" &&
(!showform ? (
<AiOutlinePlus
role="button"
onClick={() => {
setshowform(!showform);
}}
className="fs-2"
fill="#5ab4ab"
/>
) : (
<AiOutlineMinus
role="button"
onClick={() => {
setshowform(!showform);
}}
className="fs-2"
fill="#5ab4ab"
/>
))}
{item.id === "else" &&
(!showform ? (
<AiOutlinePlus
role="button"
onClick={() => {
setshowform(!showform);
}}
className="fs-2"
fill="#5ab4ab"
/>
) : (
<AiOutlineMinus
role="button"
onClick={() => {
setshowform(!showform);
}}
className="fs-2"
fill="#5ab4ab"
/>
))}
</div>
);
})}
</div>
{showform && showContactForm()}
</div>
</div>
);
};
export default Conciergerie;
can someone help me please?
The reason this problem is happening has to do with absolute and relative paths.
fetch("api/contact")
Is a relative path. The fetch function figures out the path of the current file, ie 3000/ourServices/conciergerie, and adds api/contact to it
On the other hand, if you add a "/" before the path :
fetch("/api/contact")
Fetch figures out the root path of the project, then adds the path you added, ie :
3000/api/contact
TL;DR: Change fetch("api/contact") to fetch("/api/contact").
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.
In my Next.js app, the component is getting re-rendered when I change the browser tab and then get back to the tab in which the app is already opened. e.g. app is open tab 1 and when I switch to tab 2 and then come back to tab 1.
Actually, I have a page on which listing of records appears, so when I do local filter using text match it is working fine. But when I change the tab and get back to the app tab, it resets the listing again.
When I filter the location with text then it does the filter.
But when I switch the tab it resets the result.
I am using useSwr for data fetching and display listing. Here below is code of component:
import useSWR from 'swr'
import Link from 'next/link'
import Httpservice from '#/services/Httpservice'
import { useState, useEffect, useCallback } from 'react'
import NavBar from '#/components/NavBar'
import Alert from 'react-bootstrap/Alert'
import Router, { useRouter } from 'next/router'
import NoDataFound from '#/components/NoDataFound'
import nextConfig from 'next.config'
import { useTranslation, useLanguageQuery, LanguageSwitcher } from 'next-export-i18n'
export default function Locations({...props}) {
const router = useRouter()
const { t } = useTranslation()
const [queryLanguage] = useLanguageQuery()
const httpService = new Httpservice
const pageLimit = nextConfig.PAGE_LIMIT
const [loading,setLoading] = useState(true)
const [pageIndex, setPageIndex] = useState(1)
const [locations, setLocations] = useState([])
const [searchText, setSearchText] = useState('')
const [locationId, setLocationId] = useState(null)
const [isExpanding, setIsExpending] = useState(null)
const [loadMoreBtn, setLoadMoreBtn] = useState(true)
const [locationName, setLocationName] = useState(null)
const [errorMessage, setErrorMessage] = useState(null)
const [tempLocations, setTempLocations] = useState([])
const [deleteMessage, setDeleteMessage] = useState(null)
const [successMessage, setSuccessMessage] = useState(null)
const [displayConfirmationModal, setDisplayConfirmationModal] = useState(false)
const showDeleteModal = (locationName, locationId) => {
setLocationName(locationName)
setLocationId(locationId)
setSuccessMessage(null)
setErrorMessage(null)
setDeleteMessage(`Are you sure you want to delete the '${locationName}'?`)
setDisplayConfirmationModal(true)
}
const hideConfirmationModal = () => {
setDisplayConfirmationModal(false)
}
const locationsFetcher = async() => {
try{
await httpService.get(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`).then((response) => {
if(response.status == 200 && response.data) {
let data = response.data.results
setLocations([...new Set([...locations,...data])])
setTempLocations([...new Set([...locations,...data])])
if(response.data.next == undefined && response.data.results.length == 0) {
setLoadMoreBtn(false)
}
setLoading(false)
setIsExpending(null)
return data
} else {
setLoading(false)
setIsExpending(null)
const error = new Error('An error occurred while fetching the data.')
error.info = response.json()
error.status = response.status
throw error
}
}).catch((error) => {
setLoading(false)
setIsExpending(null)
})
} catch (error) {
setLoading(false)
setIsExpending(null)
}
}
const {data, error} = useSWR(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`, locationsFetcher,{
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
if (error.status === 404) return
if (retryCount >= 10) return
setTimeout(() => revalidate({ retryCount }), 5000)
}
})
const loadMore = () => {
setPageIndex(pageIndex + 1)
setIsExpending(true)
}
const handleSearch = (e) => {
let searchKey = e.target.value
setSearchText(e.target.value)
if(searchKey.length > 0) {
console.log(tempLocations)
let foundValue = tempLocations.filter(location => location.name.toLowerCase().includes(searchText.toLowerCase()))
if(foundValue) {
setLoadMoreBtn(false)
setLocations(foundValue)
} else {
setLoadMoreBtn(true)
setLocations(tempLocations)
}
} else {
setLoadMoreBtn(true)
setLocations(tempLocations)
}
}
return (
<>
<NavBar />
<div className="app-wrapper">
<div className="app-content pt-3 p-md-3 p-lg-4">
<div className="container-xl">
<div className="row gy-4 mb-2">
<div className="col-12 col-lg-8">
<h1 className="page-head-title"> {t('locations')} </h1>
</div>
</div>
<div className="summary_col">
<div className="row gy-4">
<div className="col-12 col-lg-12">
<div className="dotted float-end">
<a href="javascript:void(0)">
<img src="/images/icons/dotted.png" width="16" height="4" alt="" />
</a>
</div>
</div>
</div>
<div className="row gy-4 mt-2">
<div className="col-6 col-lg-3 col-md-4">
<div className="input-group search_col">
<div className="form-outline ">
<input type="search" className="form-control" placeholder={t('search')} value={searchText} onChange={handleSearch} />
</div>
<button type="button" className="btn">
<img src="/images/icons/search.png" width="19" height="19" alt="" />
</button>
</div>
</div>
<div className="col-6 col-lg-9 col-md-8 ">
<Link href={{ pathname: '/settings/locations/add', query: (router.query.lang) ? 'lang='+router.query.lang : null }}>
<a className="btn btn-primary float-end">{t('location_page.add_location')}</a>
</Link>
</div>
</div>
<div className="row gy-4 mt-2">
<div className="col-12 col-lg-12">
<div className="vehicles_col table-responsive">
<table className="table" width="100%" cellPadding="0" cellSpacing="0">
<thead>
<tr>
<th>{t('location_page.name')}</th>
<th>{t('location_page.company')}</th>
<th>{t('location_page.contact')}</th>
<th>{t('location_page.email')}</th>
<th>{t('location_page.phone')}</th>
<th>{t('location_page.address')}</th>
<th>{t('detail')}</th>
</tr>
</thead>
<tbody>
{error && <tr><td><p>{t('error_in_loading')}</p></td></tr>}
{(loading) ? <tr><td colSpan="6"><p>{t('loading')}</p></td></tr> :
(locations && locations.length > 0) ? (locations.map((location, index) => (
<tr index={index} key={index}>
<td>{location.name}</td>
<td>
<a href="javascript:void(0)">
{(location.links && location.links.Company) ? location.links.Company : '-'}
</a>
</td>
<td>{location.contact}</td>
<td>{location.email}</td>
<td>{location.phone}</td>
<td>
{(location.address1) ? location.address1 : ''}
{(location.address2) ? ','+location.address2 : ''}
{(location.address3) ? ','+location.address3 : ''}
<br />
{(location.city) ? location.city : ''}
{(location.state) ? ','+location.state : ''}
{(location.country) ? ','+location.country : ''}
{(location.zip) ? ','+location.zip : ''}
</td>
<td>
<Link href={{ pathname: '/settings/locations/edit/'+ location.UUID, query: (router.query.lang) ? 'lang='+router.query.lang : null }}>
{t('view')}
</Link>
</td>
</tr>
))) : (<tr><td><NoDataFound /></td></tr>)}
</tbody>
</table>
<div className="click_btn">
{(loadMoreBtn) ? (isExpanding) ? t('loading') : <a href="javascript:void(0)" onClick={() => loadMore()}>
<span>{t('expand_table')}</span>
</a> : t('no_more_data_avail')}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}
By default useSWR will automatically revalidate data when you re-focus a page or switch between tabs. This is what's causing the re-renders.
You can disable this behaviour through the options object in your useSWR call, by setting the revalidateOnFocus field to false.
useSWR(`/v1/locations?page=${pageIndex}&limit=${pageLimit}`, locationsFetcher, {
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
if (error.status === 404) return
if (retryCount >= 10) return
setTimeout(() => revalidate({ retryCount }), 5000)
},
revalidateOnFocus: false
})
Alternatively, you can use useSWRImmutable (rather than useSWR) to disable all kinds of automatic revalidations done by SWR.
import useSWRImmutable from 'swr/immutable'
// ...
useSWRImmutable(key, fetcher, options)
Which is essentially the same as calling:
useSWR(key, fetcher, {
// other options here
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
})
There are two debug points that i suggest to try first, although I believe the problem isn't caused by this component.
export default function Locations({...props}) {
console.log('Render')
and
const locationsFetcher = async() => {
console.log('Fetch')
The above are to confirm when switching tabs,
if the Locations component repaints
if the locationsFetcher has refired
The above questions will help you to dig further. My guts feeling is that you have another piece in your code that detects the tab switching, ex. listening to the page active or not. Because by default this Locations component shouldn't repaint by itself.
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)