How to improve the performance of this React 360 image? - reactjs

I've made a 360 Image based on 51 images. However this is a bit slow, as it needs to do a lot of calculations for each different mouse position. I've had a different version where it changes the image src dynamically based on the mouse position and that was a bit faster, however it would request the image for every different mouse position so that would make the server a LOT slower considering it would request ~50 images/ 1-2-3 seconds from each user. In this implementation it only requests the images once when clicking on the NOT 360 image.
Only the JSX starting from ** and ending with ** is relevant.
Thank you.
const [clicked, setClicked] = useState<boolean>(false);
const [perc, setPerc] = useState<number>(0);
const [activeArray, setActiveArray] = useState<boolean[]>()
const handle360 = ():void => {
setClicked(!clicked);
var arr:boolean[] = []
for(let i=1; i<52; i++){
if(i===1){
arr.push(true)
}else{
arr.push(false);
}
}
setActiveArray(arr);
}
const [mouseX, setMouseX] = useState<number>();
const {x, setToggleRemove, toggleRemove} = useMousePosition();
useEffect(()=>{
setToggleRemove(!clicked);
}, [clicked])
const [width, setWidth] = useState<number>();
const [refX, setRefX] = useState<number>();
const [percentage, setPercentage] = useState<number>();
const [xPercentage, setXPercentage] = useState<number>();
const imageAmount = 51;
useEffect(()=>{
if(mainRef.current && refX && width){
if(x - refX <= 0 || (x-refX) >= width){
setMouseX(0);
}else{
setMouseX(x-refX);
}
}
} ,[x, mainRef])
useEffect(()=>{
if(mainRef.current){
const {x: refX, width} = mainRef.current.getBoundingClientRect();
setWidth(width);
setRefX(refX);
setPercentage(width / imageAmount | 0);
}
}, [mainRef])
useEffect(()=>{
if(mouseX){
setXPercentage(2 * mouseX / imageAmount | 0);
}
}, [mouseX])
useEffect(()=>{
if(xPercentage && xPercentage >= 1 && xPercentage <= 51){
setActiveArray((prevState) => prevState?.map((_, i) => i=== xPercentage + 1 ? true : false));
}
}, [xPercentage])
**return (
<div className="product360" ref={mainRef}>
<div className={`product360__left ${clicked ? "product360__left-360" : "product360__left-360-inactive"}`} onClick={handle360}>
{!clicked ? <><img src={product.backgroundImg + "1.jpg"} alt={""} loading={"eager"}/>
<img src={product.foregroundImg + "10.jpg"} alt={""} loading={"eager"}/></> :
activeArray ? activeArray.map((active, i)=>{
return <img key={i + 1} src={product.backgroundImg + `${i + 1}.jpg`} alt={""} loading={"eager"} className={active ? "product360__left-img-active" : "product__left-360-img-inactive"}/>
})
: ""}
{clicked ? <div className="product360__left-spinner">
<span>{perc}</span>
</div> : "" }**
</div>
{!clicked ? <div className="product360__right">
<div className="product360__right-top">
<div className="product360__right-sizes">
<span>Available Sizes</span>
<ul>
{product.sizes.map((size, i) => {
return (
<li key={i} onClick={() => handleAddSize(i)} className={selectedSize[i] ? "product360__right-sizes-selected" : ""}>{size}</li>
)
})}
</ul>
</div>
<div className="product360__right-colors">
<span>Available Colors</span>
<ul>
{product.colors.map((color, i) => {
return (
<li key={i} className={selectedColor[i] ? "product360__right-colors-selected" : ""} onClick={() => handleAddColor(i)}>{color}</li>
)
})}
</ul>
</div>
<div className="product360__right-custom">
{data ?
<>
<span>Add extra Accessories</span>
<ul>
{data.getAccessoriesBasedOnParent.map((accessory:AccessoryType, i:number)=> {
return (
<li key={i} onClick={() => handleAddAccessory(i)} className={addedAccessory ? addedAccessory[i] ? "product360__right-custom-active" : "" : ""}>
<img src={accessory.image} alt={""} loading={"lazy"}/>
<div className="product360__right-custom-info">
<span>{accessory.title}</span>
<span>${accessory.price}</span>
</div>
<div className={`product360__right-custom-checker ${addedAccessory ? addedAccessory[i] ? "product360__right-custom-checker-active" : "" : ""}`}></div>
</li>
)
})}
</ul>
</>
: ""}
</div>
<div className="product360__right-price">
<span>Total:</span> <span>${total}</span>
</div>
</div>
<div className={`product360__right-bottom ${accIndex!==undefined ? "product360__right-bottom-disabled" : ""}`}>
<span>Your custom style</span>
<div className="product360__right-bottom-info">
{file && accIndex === undefined ? <img src={URL.createObjectURL(file)} alt={"Retry"} loading="eager"/> : ""}
<div className={`product360__right-bottom-upload ${file && accIndex === undefined ? "product360__right-bottom-upload-uploaded" : ""}`}>
<label htmlFor="upload-photo">{accIndex !== undefined ? "Not available." : "Upload"}</label>
<input type="file" name="photo" multiple={false} accept="image/*" id="upload-photo" onChange={(e) => handleFileChange(e)} disabled={accIndex !== undefined ? true : false}/>
{file && accIndex === undefined ? <button type="button" onClick={()=> setFile(undefined)}>Remove</button> : ""}
</div>
</div>
</div>
</div>
:""}
<img src={"/assets/cursor/360-icon.svg"} alt={""} loading={"lazy"}/>
</div>
);
}

Related

How can i make a counter for each component in functional component?

Halo everybody, i want to ask for my problem right now..
i want to make a counter in each component list.. i already have a function for handle the counter, but the counter doesnt increment in spesific id. that function increment the counter value for all component.. please help me
there is my code
MenuPage.js
import React, { useEffect, useState } from "react";
import "../../assets/index.css";
import ProductList from "../../components/ProductList";
import { productList } from "../../components/constant/productList";
import NavigationBottom from "../../components/NavigationBottom";
function MenuPage() {
const [showDisplay, setShowDisplay] = useState("grid");
const [quantityProduct, setQuantityProduct] = useState(0);
const handleShowDisplay = (e) => {
setShowDisplay(e);
};
const handleAdd = (value, idx) => {
if (value.id === idx + 1) {
setQuantityProduct(quantityProduct + 1);
} else {
setQuantityProduct(0);
}
};
const handleMinus = () => {
if (quantityProduct === 0) {
return 0;
}
setQuantityProduct(quantityProduct - 1);
};
return (
<div className="main_container">
<div className="container_menu__style">
<div
onClick={() => handleShowDisplay("grid")}
className="button_grid__view"
>
<i class="bi bi-grid"></i>
</div>
<div
onClick={() => handleShowDisplay("list")}
className="button_list__view"
>
<i class="bi bi-list"></i>
</div>
</div>
<div
className={` ${
showDisplay === "grid"
? "container_menu__page"
: "container_menu__page_list"
}`}
>
{productList.map((item, idx) => {
return (
<ProductList
key={idx}
id={idx}
item={item}
quantityProduct={quantityProduct}
showDisplay={showDisplay}
handleAdd={handleAdd}
handleMinus={handleMinus}
/>
);
})}
<NavigationBottom />
</div>
</div>
);
}
export default MenuPage;
ProductList.js
import React from "react";
function ProductList({
id,
item,
quantityProduct,
showDisplay,
handleAdd,
handleMinus,
}) {
return (
<div
className={` ${
showDisplay === "grid"
? "wrapper_menu__product"
: "wrapper_menu__product_list"
}`}
>
<div
className={` ${
showDisplay === "grid"
? "content_menu__product"
: "content_menu__product_list"
}`}
>
<div
className={`${
showDisplay === "grid"
? "image_menu__product"
: "image_menu__product_list"
}`}
>
<img
alt="product-list"
className={` ${showDisplay === "grid" ? "" : "img_list"}`}
src="https://i.pinimg.com/originals/23/91/52/2391523603cbd5153d7eb4e37eb3c882.png"
/>
</div>
<div className="wrapper_menu__detail">
<div className="container_menu">
<div className="menu_title__product">{item.product_name}</div>
<div className="menu_price__product">Rp. {item.product_price}</div>
</div>
<div className="container_quantity">
{/* <div className="quantity_text">Jumlah pesanan</div> */}
<button
className="button_minus__product"
onClick={() => handleMinus(item)}
>
<i class="bi bi-dash"></i>
</button>
<div className="quantity_product">{quantityProduct}</div>
<button
className="button_add__product"
onClick={() => handleAdd(item, id)}
>
<i class="bi bi-plus"></i>
</button>
</div>
{/* <div className="wrapper_quantity__product">
<div className="quantity_product__text">Jumlah Pesanan</div>
<div className="quantity_product__number"></div>
</div> */}
</div>
</div>
</div>
);
}
export default ProductList;
Your MenuPage only has a single quantityProduct (and setQuantityProduct) variable. All items are using and setting this same variable.

Unexpected token in JSX when using conditional statements

I'm trying to wrap every 3 items with a carousel component, but unfortunately as soon as I change the <p>start</p> <p>end</p> test element to the real component I get the following error:
"Unexpected token. Did you mean `{'}'}` or `&rbrace;`?",
const Alerts = (props) => {
const alerts = props.data;
let pager = 1;
return (
<ul className={styles.alerts}>
<Swiper
spaceBetween={30}
pagination={{
clickable: true,
}}
className="mySwiper"
>
{alerts.map((e, index) => (
<>
{pager % 4 === 0 || pager === 1 ? (<p>start</p>) : ""}
<div key={index} id={`alert-${index}`} className={styles.alert}>
<div className={styles.alert__content}>
<div className={styles.alert__content_top}>
<strong>
{e.token}
<a href={e.token_link}>
<i className="fa fa-external-link"></i>
</a>
</strong>
<span>{e.date}</span>
</div>
<div className={styles.alert__desc}>{e.description}</div>
</div>
<div className={styles.alert__type}>{e.type}</div>
</div>
{pager % 3 === 0 ? (<p>end</p>) : ""}
{pager === 3 ? (pager = 0) : ""}
{(pager = pager + 1)}
</>
))}
</Swiper>
</ul>
);
};
After the modification the lines looks like this:
{pager % 4 === 0 || pager === 1 ? (<SwiperSlide>) : ""}
and
{pager % 4 === 0 || pager === 1 ? (</SwiperSlide>) : ""}
What's wrong with the syntax? The numbers from the pager variable also visible on the site, maybe it's related to my syntax error?
The problem is <p>start</p> is correct JSX element. And <SwiperSlide> is just a part of element.
I'd recommend you convert your initial data array into a new array with small arrays with 3 elements in each. And go through it with two maps:
function splitToBulks(arr, bulkSize = 20) {
const bulks = [];
for (let i = 0; i < Math.ceil(arr.length / bulkSize); i++) {
bulks.push(arr.slice(i * bulkSize, (i + 1) * bulkSize));
}
return bulks;
}
const Alerts = (props) => {
const alerts = splitToBulks(props.data, 3);
let pager = 1;
return (
<ul className={styles.alerts}>
<Swiper
spaceBetween={30}
pagination={{
clickable: true,
}}
className="mySwiper"
>
{alerts.map((allerts3, index) => (
<SwiperSlide>
{allerts3.map((e, index) => (
<div key={index} id={`alert-${index}`} className={styles.alert}>
<div className={styles.alert__content}>
<div className={styles.alert__content_top}>
<strong>
{e.token}
<a href={e.token_link}>
<i className="fa fa-external-link"></i>
</a>
</strong>
<span>{e.date}</span>
</div>
<div className={styles.alert__desc}>{e.description}</div>
</div>
<div className={styles.alert__type}>{e.type}</div>
</div>
)}
</SwiperSlide>
)}
</>
))}
</Swiper>
</ul>
);
};

How to disable a button within a mapped div for a period of time

I have an array of objects, and by .map() method i am returning the objects' elements within the divs. There is this button that calls a function with 24hr cooldown, and i would like to disable the button for that period of time when it's clicked. Otherwise it throws me an error from the serverside (I'm using blockchain as the backend, and transactions throw error if the requirements aren't met). I can't use a state like isButtonDisabled, because i just want a specific button to be disabled.
Here is the mapped div part of the code;
<div className="row" id="chars-row">
<main role="main" className="col-lg-12 d-flex flex-wrap justify-content-center">
{ characters.map( (chr, key) => {
return(
<div className="col-md-2 card m-1" key={key} >
<img className="card-img-top chr_img" src= {(Math.max(chr.strength, chr.health, chr.dexterity, chr.intelligence, chr.magic) == chr.strength)
? require('../assets/str.png')
: (Math.max(chr.strength, chr.health, chr.dexterity, chr.intelligence, chr.magic) == chr.health)
? require('../assets/hp.png')
: (Math.max(chr.strength, chr.health, chr.dexterity, chr.intelligence, chr.magic) == chr.dexterity)
? require('../assets/dex.png')
: (Math.max(chr.strength, chr.health, chr.dexterity, chr.intelligence, chr.magic) == chr.intelligence)
? require('../assets/int.png')
: (Math.max(chr.strength, chr.health, chr.dexterity, chr.intelligence, chr.magic) == chr.magic)
? require('../assets/mgc.png') : null } alt=""/>
<div className="card-body">
<div> <b>Name:</b> <span>{(chr.name).replace(/['"]+/g, '')}</span></div>
<div> <b>ID:</b> <span>{key}</span></div>
<div> <b>STR:</b> <span>{chr.strength}</span></div>
<div> <b>HP:</b> <span >{chr.health}</span></div>
<div> <b>Dex:</b> <span>{chr.dexterity}</span></div>
<div> <b>INT:</b> <span >{chr.intelligence}</span></div>
<div> <b>MAGIC:</b> <span >{chr.magic}</span></div>
<div> <b>LEVEL:</b> <span >{chr.level}</span></div>
<div> <b>EXP:</b> <span >{chr.experience}</span> / 255</div>
<div className="progress">
<div className="progress-bar progress-bar-striped bg-info" role="progressbar" style={{'width': (chr.experience * 100 / 255) + '%'}} aria-valuenow="0" aria-valuemin="0" aria-valuemax="255"></div>
</div>
<small className="text-secondary owner_address">{ownerAccounts[key]}</small>
</div>
<button className="btn btn-danger my-1" value={key} onClick={openModal(key)}>Battle!</button>
</div>
)
})}
</main>
</div>
These are just snippets. But the idea is working. This will make the clicked button disabled.
Parent
const Parent = () => {
const data = ['a', 'b', 'c'];
return (
<div>
{data.map((data) => (
<Child/>
))
}
</div>
)
}
Child
const Child = ({data}) => {
const [disable, setDisable] = useState(false);
return (
<div onPress={ () => setDisable(true)}>
{data}
</div>
)
}
export default Child
First of all, thanks for the helps. I have solved my problem.
I could query my last battle epoch time for each member of the array from blockchain
(from mapping(uint256 => uint256) public tokenIdToCooldown ):
useEffect(() => {
const getLastBattle = async () => {
let temp = [];
let totalChars = await contract.methods.getTotalCharacterNumber().call({ from: accounts });
for(let i=0; i<totalChars; i++) {
let lastBattle = await contract.methods.tokenIdToCooldown(i).call();
temp[i] = lastBattle;
}
setLastBattle(temp);
}
if(typeof web3 !== 'undefined' && typeof accounts !== 'undefined' && typeof contract !== 'undefined') {
getLastBattle();
}
}, [accounts, web3, contract])
Then i have checked whether that time passes now (after adding 24hr in seconds), and assinged a bool value for each of them;
useEffect(() => {
const lastBattleBool = async () => {
var now = Math.round(Date.now() / 1000);
let totalChars = await contract.methods.getTotalCharacterNumber().call({ from: accounts });
let temp = [];
for(let i=0; i<totalChars; i++) {
if((lastBattle[i] + 86400) < now) {
temp[i] = true;
} else {
temp[i] = false;
}
setBoolBattle(temp);
}
}
if(typeof web3 !== 'undefined' && typeof accounts !== 'undefined' && typeof contract !== 'undefined') {
lastBattleBool();
}
}, [lastBattle, accounts, web3, contract]);
My last approach was checking that bool value before displaying the button;
{boolBattle[characterCountOfOwner[key]]
? <button className="btn btn-danger my-1" value={characterCountOfOwner[key]}
onClick={battle(characterCountOfOwner[key])}>Battle!</button>
: null}

How can you update the props of a single element of an array using state?

I'm trying to create a component that allows a video to autoplay on mouseenter and pauses on mouseleave. However, the current code causes all videos to autoplay when you put the mouseover any single one of the videos. How can I only make the video that you're interacting with update its state in a more isolated way?
I can't seem to find a solution using React hooks anywhere, that I can understand and implement into my own code.
export default function VideoGrid(props) {
const [playing, setPlaying] = useState(false);
return (
<div>
<div className={styles.VideoGrid}>
<div className="container">
<h2 className={styles.title + " text-lg uppercase title"}>{props.title}</h2>
<div className={styles.videos}>
{props.videos ? videos.output.map((video, index) => {
return (
<div className={styles.video} key={index}>
{ video.acf.video_url ? (
<ReactPlayer
controls={false}
playing={playing}
onMouseEnter={() => setPlaying(true)}
onMouseLeave={() => setPlaying(false)}
height={205}
url={video.acf.video_url + '&showinfo=0&controls=0&autohide=1'}
width='100%'
config= {{
youtube: {
playerVars: {showinfo: 0, controls: 0}
}
}}
/>
) : (
<img src={video._embedded ? video._embedded['wp:featuredmedia'][0].media_details.sizes.full.source_url : '/logo.svg'} height={205} />
)}
<p className="mt-2">{video.title.rendered}</p>
{video.acf.description && router.pathname != '/' ? <p className={styles.description + " text-xs"}>{video.acf.description}</p> : ''}
</div>
)
}) : ''}
</div>
</div>
</div>
</div>
)
}
You can create a separate component and deal with the state individually.
const Video = (props) => {
const [playing, setPlaying] = useState(false);
return (
<div className={styles.video} key={index}>
{video.acf.video_url ? (
<ReactPlayer
controls={false}
playing={playing}
onMouseEnter={() => setPlaying(true)}
onMouseLeave={() => setPlaying(false)}
height={205}
url={video.acf.video_url + "&showinfo=0&controls=0&autohide=1"}
width="100%"
config={{
youtube: {
playerVars: { showinfo: 0, controls: 0 },
},
}}
/>
) : (
<img
src={
video._embedded
? video._embedded["wp:featuredmedia"][0].media_details.sizes.full
.source_url
: "/logo.svg"
}
height={205}
/>
)}
<p className="mt-2">{video.title.rendered}</p>
{video.acf.description && router.pathname != "/" ? (
<p className={styles.description + " text-xs"}>
{video.acf.description}
</p>
) : (
""
)}
</div>
);
};
export default Video;
Then render it in your map. You need to do the proper changes to pass your data into the video component.
export default function VideoGrid(props) {
return (
<div>
<div className={styles.VideoGrid}>
<div className="container">
<h2 className={styles.title + " text-lg uppercase title"}>
{props.title}
</h2>
<div className={styles.videos}>
{props.videos
? videos.output.map((video, index) => {
return <Video />;
})
: ""}
</div>
</div>
</div>
</div>
);
}

How to properly use onNext with .map with links, so far only the final link works

I am trying to list our these 4 Shirt images and link to that shirt's homepage when the image is clicked on. Only the final link works (onNext("ANECKHOME")). Is there a better solution that I may be overlooking? Below is my data set and the code.
Data Set is shirtTypesData
export const shirtTypes = [
{
name: "SHORTSLEEVE",
image: "https://imagelink1.png",
},
{
name: "LONGSLEEVE",
image: "https://imagelink2.png",
},
{
name: "VNECK",
image: "https://imagelink3.png",
},
{
name: "ANECK",
image: "https://imagelink4.png",
},
];
component is SelectShirtType
import React from "react";
import { shirtTypes } from "./shirtTypesData";
const SelectShirtType = ({ onNext}) => (
<main>
<h3>Available Tshirts for Purchase</h3>
<hr />
<div className="boxes">
{shirtTypes.map((type) => (
<button
style={type.name !== "SHORTSLEEVE" ? : null}
className="product-outer-box"
onClick={
type.name == "SHORTSLEEVE"
? () => {
onNext("SHORTSLEEVEHOME");
}
: null
}
style={type.name !== "LONGSLEEVE" ? : null}
className="product-outer-box"
onClick={
type.name == "LONGSLEEVE"
? () => {
onNext("LONGSLEEVEHOME");
}
: null
}
style={type.name !== "VNECK" ? : null}
className="product-outer-box"
onClick={
type.name === "VNECK"
? () => {
onNext("VNECKHOME");
}
: null
}
style={type.name !== "ANECK" ? : null}
className="product-outer-box"
onClick={
type.name === "ANECK"
? () => {
onNext("ANECKHOME");
}
: null
}
>
<div className="product-inner-box">
<div className="image-box">
<img src={type.image} alt={type.name} />
</div>
<div className="black-box">
<p>{type.name}</p>
</div>
</div>
</button>
))}
</div>
</main>
);
export default SelectShirtType;
The problem is that you are overwriting the onClick property each time you write it out, that's why it only works in the last scenario.
Let's go by steps (solution at the bottom).
ClassName is always the same for all types, so instead of repeating that attribute, we'll just leave it once at the top of our button element:
<button
className="product-outer-box"
style={type.name !== "SHORTSLEEVE" ? : null}
style={type.name !== "LONGSLEEVE" ? : null}
style={type.name !== "VNECK" ? : null}
style={type.name !== "ANECK" ? : null}
onClick={
type.name == "SHORTSLEEVE"
? () => {
onNext("SHORTSLEEVEHOME");
}
: null
}
onClick={
type.name == "LONGSLEEVE"
? () => {
onNext("LONGSLEEVEHOME");
}
: null
}
onClick={
type.name === "VNECK"
? () => {
onNext("VNECKHOME");
}
: null
}
onClick={
type.name === "ANECK"
? () => {
onNext("ANECKHOME");
}
: null
}
>
<div className="product-inner-box">
<div className="image-box">
<img src={type.image} alt={type.name} />
</div>
<div className="black-box">
<p>{type.name}</p>
</div>
</div>
</button>
Now we have a button that will always have a style attribute that is either "ANECK" or null, and an onClick attribute that will always contain the following:
type.name === "ANECK"
? () => {
onNext("ANECKHOME");
}
: null
We should unify our style and onClick attributes into just one of each, like so:
<button
className="product-outer-box"
style={type.name}
onClick={() => onNext(`${type.name}HOME`)}
>
<div className="product-inner-box">
<div className="image-box">
<img src={type.image} alt={type.name} />
</div>
<div className="black-box">
<p>{type.name}</p>
</div>
</div>
</button>
This way, our buttons will always have whatever type.name holds as their style, and when clicked will redirect our users to "type.name + HOME" (for example, if type.name is "SHORTSLEEVE", our onNext will take us to "SHORTSLEEVEHOME").
Do you want something like this?
const SelectShirtType = ({ onNext}) => ( shirtTypes.map( type => (<button onClick={() => onNext(type.name.toUpperCase()+"HOME")}></button> ))

Resources