How to rid error of null properties in react hook? - javascript

I'm trying to custom hook to get few data. I want check where is one element in comparison to second element. I want to center it among themselves. It works but I have repeated errors in console.
Error:
Uncaught TypeError: Cannot read properties of null (reading
'offsetLeft') at getPosition (usePosition.js:18:1)
import { useEffect, useRef, useState } from "react";
const usePosition = () => {
const elRef = useRef();
const elSecondRef = useRef();
const [fromLeftEl, setFromLeftEl] = useState();
const [fromTopEl, setFromTopEl] = useState();
const [widthEl, setWidthEl] = useState();
const [heightEl, setHeightEl] = useState();
const [widthElSecond, setWidthElSecond] = useState();
const [finalX, setFinalX] = useState();
const [finalY, setFinalY] = useState();
// This function calculates the position underneath the element and centering it with respect to the other element
const getPosition = () => {
const fromLeftEl = elRef.current.offsetLeft;
setFromLeftEl(fromLeftEl);
const fromTopEl = elRef.current.offsetTop;
setFromTopEl(fromTopEl);
const widthEl = elRef.current.offsetWidth;
setWidthEl(widthEl);
const heightEl = elRef.current.offsetHeight;
setHeightEl(heightEl);
const widthElSecond = elSecondRef.current.offsetWidth;
setWidthElSecond(widthElSecond);
const middleEl = widthEl / 2;
const middleElSecond = widthElSecond / 2;
const finalX = fromLeftEl + middleEl - middleElSecond;
setFinalX(finalX);
const finalY = fromTopEl + heightEl;
setFinalY(finalY);
};
// Get the position of the first element
useEffect(() => {
getPosition();
}, []);
useEffect(() => {
window.addEventListener("resize", getPosition);
window.addEventListener("click", getPosition);
window.addEventListener("scroll", getPosition);
return () => window.removeEventListener("scroll", getPosition);
}, []);
return {
elRef,
elSecondRef,
fromLeftEl,
fromTopEl,
widthEl,
heightEl,
finalY,
finalX,
widthElSecond,
};
};
export default usePosition;
component that uses usePosition:
import classNames from "classnames";
import { useState } from "react";
import useSticky from "./useSticky";
import usePosition from "./usePosition";
import "../style/sass/Nav.sass";
import { NavLink } from "react-router-dom";
const Nav = (props) => {
const [isActive, setActive] = useState(false);
const { sticky, stickyRef } = useSticky();
const { elRef, elSecondRef, finalY, finalX } = usePosition();
const handleToggle = () => {
setActive(!isActive);
};
return (
<div
ref={stickyRef}
className={classNames("nav", { sticky })}
style={{
display: props.display,
height: sticky ? `${stickyRef.current?.clientHeight}px` : "10vh",
width: props.width,
gridTemplateColumns: props.navGridCol,
gridTemplateRows: props.navGridRow,
height: props.height,
position: props.position,
left: props.left,
animation: props.animationMenu,
}}
>
<div
className="nav__logo"
style={{
gridArea: props.gArea1,
height: props.heightHomeBtn,
width: props.btnWidth,
animation: props.animationHome,
display: props.homeDisplay,
}}
>
<NavLink className="link__logo" to="/">
<i
class="icon-home"
style={{
width: props.btnWidth,
fontSize: props.fweight,
}}
></i>
</NavLink>
</div>
<NavLink
className="link__about"
to="/about"
exact="true"
style={{ gridArea: props.gArea2 }}
>
<button
className="nav__aboutBtn"
style={{
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationAbout,
display: props.aboutDisplay,
}}
>
<p>o firmie</p>
</button>
</NavLink>
<button
onClick={handleToggle}
className={!isActive ? "offerDD__btn" : "offerDD__btn_active"}
style={{
gridArea: props.gArea3,
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationOffer,
display: props.offerDisplay,
}}
ref={elRef}
>
<i class="icon-down-open"></i>
<p>oferta</p>
</button>
<div
className={
!isActive ? "offerDD__dropdown_disabled" : "offerDD__dropdown"
}
ref={elSecondRef}
style={{ top: `${finalY}px`, left: `${finalX}px` }}
>
<NavLink className="link__air" to="/air-conditioning">
<div
className="air__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>klimatyzacje</p>
</div>
</NavLink>
<NavLink className="link__vent" to="/ventilation">
<div
className="vent__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>wentylacje</p>
</div>
</NavLink>
<NavLink className="link__heat" to="/heat-pump">
<div
className="heat__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>pompy ciepła</p>
</div>
</NavLink>
<NavLink className="link__recu" to="/recuperation">
<div
className="recu__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>rekuperacja</p>
</div>
</NavLink>
<NavLink className="link__fire-protection" to="/fire-protection">
<div
className="fire__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>ppoż</p>
</div>
</NavLink>
<NavLink className="link__shop" to="/shop">
<div
className="shop__p"
style={{ height: props.btnHeight, width: props.btnWidth }}
>
<p>sklep</p>
</div>
</NavLink>
</div>
<NavLink
className="link__blog"
to="/blog"
style={{ gridArea: props.gArea4 }}
>
<button
className="nav__blogBtn"
style={{
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationBlog,
display: props.blogDisplay,
}}
>
<p>blog</p>
</button>
</NavLink>
<NavLink
className="link__contact"
to="/contact"
style={{ gridArea: props.gArea5 }}
>
<button
className="nav__contactBtn"
style={{
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationContact,
display: props.contactDisplay,
}}
>
<p>kontakt</p>
</button>
</NavLink>
<NavLink
className="link__login"
to="/login"
style={{ gridArea: props.gArea6 }}
>
<button
className="nav__loginBtn"
style={{
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationLogin,
display: props.loginDisplay,
}}
>
<p>zaloguj</p>
</button>
</NavLink>
</div>
);
};
export default Nav;
I want know how to code this issue correctly without error in console and working hook.
I know that this error is due to the fact that it is impossible to read something that is not there and therefore it is null but how to write this code to make it correct?

I don't see any overt issue with the way the elRef is used in the consuming component. elRef is attached to a DOM element, e.g. button, and there is no conditional rendering so button is rendered to the DOM during the initial render.
const Nav = (props) => {
...
const { elRef, elSecondRef, finalY, finalX } = usePosition();
...
return (
<div
...
>
...
<button
onClick={handleToggle}
className={!isActive ? "offerDD__btn" : "offerDD__btn_active"}
style={{
gridArea: props.gArea3,
height: props.btnHeight,
width: props.btnWidth,
animation: props.animationOffer,
display: props.offerDisplay,
}}
ref={elRef}
>
<i class="icon-down-open"></i>
<p>oferta</p>
</button>
...
</div>
);
};
I do however see potential issue in the usePosition hook. The hook doesn't clean up all the effects it creates. In the case of accessing into potentially null or undefined references it is advised to use a null-check/guard-clause or the Optional Chaining operator prior to accessing properties. In this case an if block to check if the refs have a truthy current value is sufficient.
const usePosition = () => {
const elRef = useRef();
const elSecondRef = useRef();
const [fromLeftEl, setFromLeftEl] = useState();
const [fromTopEl, setFromTopEl] = useState();
const [widthEl, setWidthEl] = useState();
const [heightEl, setHeightEl] = useState();
const [widthElSecond, setWidthElSecond] = useState();
const [finalX, setFinalX] = useState();
const [finalY, setFinalY] = useState();
// This function calculates the position underneath the element and centering it with respect to the other element
const getPosition = () => {
// Only access ref properties if there is a current value
if (elRef.current && elSecondRef.current) {
const fromLeftEl = elRef.current.offsetLeft;
setFromLeftEl(fromLeftEl);
const fromTopEl = elRef.current.offsetTop;
setFromTopEl(fromTopEl);
const widthEl = elRef.current.offsetWidth;
setWidthEl(widthEl);
const heightEl = elRef.current.offsetHeight;
setHeightEl(heightEl);
const widthElSecond = elSecondRef.current.offsetWidth;
setWidthElSecond(widthElSecond);
const middleEl = widthEl / 2;
const middleElSecond = widthElSecond / 2;
const finalX = fromLeftEl + middleEl - middleElSecond;
setFinalX(finalX);
const finalY = fromTopEl + heightEl;
setFinalY(finalY);
}
};
useEffect(() => {
window.addEventListener("resize", getPosition, { passive: true });
window.addEventListener("click", getPosition);
window.addEventListener("scroll", getPosition, { passive: true });
// Call once on initial component render
getPosition();
// Return cleanup function to remove all listener callbacks!
return () => {
window.removeEventListener("resize", getPosition, { passive: true });
window.removeEventListener("click", getPosition);
window.removeEventListener("scroll", getPosition, { passive: true });
};
}, []);
return {
elRef,
elSecondRef,
fromLeftEl,
fromTopEl,
widthEl,
heightEl,
finalY,
finalX,
widthElSecond,
};
};
export default usePosition;

Related

React limits the number of renders to prevent an infinite loop error, while using empty array dependencies in useEffect()

I am trying to render some fetched data to the DOM from an API which is called in useEffect() hook. I set a boolean "isFetching" to start rendering the data after the fetch process ends, but I get an error for infinite loop instead.
The only way that my fetched data appears on render is while I use setTimeout() and wait for the data to be fetched, but this is only for testing...
import "bootstrap/dist/css/bootstrap.css";
import { useEffect } from "react";
import axios from "axios";
export default function App() {
//the array of loadedQuestions that is fetched from the API
let [loadedQuestions, setLoadedQuestions] = React.useState([]);
let [answers, setAnswers] = React.useState([]);
let [isFetching, setIsFetching] = React.useState(true);
async function fetchData() {
const response = await axios.get(
"https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple"
);
console.log(response);
const data = await response.data;
loadedQuestions = data.results;
setLoadedQuestions(loadedQuestions);
//adding the correct answer to incorrect_answers array
for (let obj of loadedQuestions)
obj.incorrect_answers.splice(
Math.floor(Math.random() * 4),
0,
obj.correct_answer
);
setIsFetching(false);
isFetching = false;
console.log(isFetching);
}
useEffect(() => {
fetchData();
}, []);
const [isClicked, setIsClicked] = React.useState(false);
const [currentQuestion, setCurrentQuestion] = React.useState(0);
const [score, setScore] = React.useState(0);
const [finished, setFinished] = React.useState(false);
const [beforeFinish, setBeforeFinish] = React.useState("");
//setTimeout only for testing....
// setTimeout(() => {
if (!isFetching) {
setAnswers(
loadedQuestions[currentQuestion].incorrect_answers.map((element) => (
<li>
<span
key={currentQuestion.toString()}
onClick={() => handleClick(element)}
style={{ cursor: "pointer" }}
className={isClicked ? textColor(element) : "bg"}
>
{element}
</span>
</li>
))
);
setBeforeFinish(
<div>
<h5 className="m-3" style={{ textDecoration: "underline" }}>
{loadedQuestions[currentQuestion].question}
</h5>
<ol style={{ listStyleType: "lower-latin" }}>{answers}</ol>
<button
onClick={() => nextQuestionFunction()}
style={{
backgroundColor: "#0c88fb",
color: "white",
marginLeft: 10,
}}
>
Next Question
</button>
<h5 style={{ marginTop: 15, marginLeft: 10 }}>
Your score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>!
</h5>
</div>
);
}
// }, 2000);
const afterFinish = (
<div>
<h1>Finished!</h1>
<h5>
Your Score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>
</h5>
<button
onClick={() => tryAgain()}
style={{ backgroundColor: "#0c88fb", color: "white", marginLeft: 2 }}
>
Try Again
</button>
</div>
);
function handleClick(element) {
setIsClicked(true);
textColor(element);
if (element === loadedQuestions[currentQuestion].correct_answer) {
setScore(score + 100 / loadedQuestions.length);
}
}
function textColor(element) {
let classN = "bg ";
element === loadedQuestions[currentQuestion].correct_answer
? (classN += "bg-info")
: (classN += "bg-secondary");
return classN;
}
function nextQuestionFunction() {
if (currentQuestion + 1 === loadedQuestions.length) {
setFinished(true);
} else {
setCurrentQuestion(currentQuestion + 1);
setIsClicked(false);
}
}
function textDisplay() {
if (finished) {
return afterFinish;
} else {
return beforeFinish;
}
}
function tryAgain() {
setCurrentQuestion(0);
setScore(0);
setIsClicked(false);
setFinished(false);
}
return textDisplay();
}
if (!isFetching) {
setAnswers(
loadedQuestions[currentQuestion].incorrect_answers.map((element) => (
// etc
))
);
setBeforeFinish(
<div>
// etc
</div>
);
}
You are calling setAnswers and setBeforeFinish in the middle of rendering. Setting state causes the component to rerender and when it renders you set state again, which renders again, which sets state again, etc.
These don't look like they should even be states at all. loadedQuestions and isFetching are the true state, and then answers and beforeFinish are just values calculated from that state. I recommend you delete the answers and beforeFinish states, and where you used to have the if (!isFetching) code you do:
let answers = null;
let beforeFinish = null;
if (!isFetching) {
answers = loadedQuestions[currentQuestion].incorrect_answers.map(
(element) => (
<li>
<span
key={currentQuestion.toString()}
onClick={() => handleClick(element)}
style={{ cursor: "pointer" }}
className={isClicked ? textColor(element) : "bg"}
>
{element}
</span>
</li>
)
);
beforeFinish = (
<div>
<h5 className="m-3" style={{ textDecoration: "underline" }}>
{loadedQuestions[currentQuestion].question}
</h5>
<ol style={{ listStyleType: "lower-latin" }}>{answers}</ol>
<button
onClick={() => nextQuestionFunction()}
style={{
backgroundColor: "#0c88fb",
color: "white",
marginLeft: 10,
}}
>
Next Question
</button>
<h5 style={{ marginTop: 15, marginLeft: 10 }}>
Your score is{" "}
<span style={{ color: "#0c88fb", fontWeight: "bold" }}>{score}</span>!
</h5>
</div>
);
}

Form resets stored components upon submission

I want to make an editable piece of display text, such that:
The text itself is not a textarea/input, it is just standard text
Clicking on the text will open a text area and edit button, which allows you to edit the text
Clicking the edit button will update the display text, and hide the textarea/edit button (display:none)
I have implemented every part of this, and now have a bug where any time I hit the 'edit' button, all of the text components are erased from the page entirely.
To achieve this, I have a Page component which stores a series of EditableText components (you can add more EditableText components via a + button at the bottom of the page):
const Page = (props) => {
const classes = useStyles()
var [components, setComponents] = useState([])
var [displayButton, setDisplayButton] = useState(false)
const toggleButton = () => {
displayButton = !displayButton
setDisplayButton(displayButton)
}
const addCaution = () => {
setComponents(components.concat(<Caution />))
displayButton = !displayButton
setDisplayButton(displayButton)
}
const addImportant = () => {
setComponents(components.concat(<Important />))
displayButton = !displayButton
setDisplayButton(displayButton)
}
const opbutton = (
<div>
<Button onClick={addHeader}>header</Button>
<Button onClick={addText}>text</Button>
<Button onClick={addCaution}>caution</Button>
<Button onClick={addImportant}>important</Button>
</div>
)
return (
<div className={classes.page}>
{components}
{displayButton ? opbutton : null}
<Button onClick={toggleButton}>+</Button>
</div>
)
The EditableText components are added into an array via useState.
Here is the code for the EditableText:
const EditableText = (props) => {
const classes = useStyles()
const { inputs, handleInputChange, handleSubmit } = useSubmit()
var [displayText, setDisplayText] = useState("click here to add notes!")
var [showBox, setShowBox] = useState(false)
var [showClickArea, setClickArea] = useState(true)
const inputBox = (
<form
onSubmit={handleSubmit}
style={{ display: "flex", flexDirection: "row", width: "100%" }}
>
<textarea
type="text"
name="displayText"
className={classes.textbox}
onChange={handleInputChange}
value={inputs}
style={{ borderColor: props.border }}
onSubmit={"return inputs"}
>
{inputs}
</textarea>
<Button id="editbutton" type="submit" className={classes.button}>
edit
</Button>
</form>
)
const toggleEdit = () => {
showClickArea = !showClickArea
setClickArea(showClickArea)
showBox = !showBox
setShowBox(showBox)
}
const clickArea = (
<div
style={{ width: "80%", height: "200%", position: "absolute" }}
onClick={toggleEdit}
/>
)
return (
<div>
{showClickArea ? clickArea : null}
<div className={classes.background} style={{ color: props.color }}>
{inputs}
<div>{showBox ? inputBox : displayText}</div>
</div>
</div>
)
}
You may notice the use of useSubmit which is a custom hook:
const useSubmit = (callback) => {
const [input, setInput] = useState({})
const handleSubmit = (event) => {
callback()
}
const handleInputChange = (event) => {
event.persist()
setInput((input) => ({
...input,
[event.target.name]: [event.target.value],
}))
}
return (handleSubmit, handleInputChange, input)
}
What I figure is that this may be an issue with the custom hook, or the use of form and submit. I think form is supposed to clear the page once submitted. But I'm not entirely sure. Any idea how to prevent this?
Solved by creating a function and an 'edit' variable:
function setDisplayState() {
if (edit === false) {
return (
<div
style={{
color: props.color,
fontWeight: props.weight ? props.weight : 400,
fontSize: props.size ? props.size : 14,
}}
>
{displayText}
</div>
)
} else {
return (
<div>
<textarea
className={classes.textbox}
style={{ color: props.color }}
value={displayText}
onChange={(e) => {
displayText = e.target.value
setDisplayText(displayText)
}}
/>
<Button className={classes.button} onClick={saveChange}>
save
</Button>
</div>
)
}
}

react-countdown resets when rendered in ReactJs

I am creating a game which user can tap a button as much as they can in a range of time.
I have use react-countdown to create Timer. But when I tap to a button, timer is auto reset.
Here my code.
import React, { useState } from "react"
import Countdown from 'react-countdown';
function App() {
const [name, setName] = useState("")
const [isLogin, setIsLogin] = useState(false)
const [counter, setCounter] = useState(0)
const submitName = () => {
setIsLogin(true)
}
const updateCounter = () => {
console.log(counter)
setCounter(counter + 1)
}
return (
<div>
{!isLogin ? (
<div>
<input
style={{ width: 300, height: 30 }}
placeholder="Input name here"
value={name}
onChange={e => setName(e.target.value)}
/>
<button
style={{ width: 50, height: 20, background: "red" }}
onClick={() => submitName()}
>Go</button>
</div>
) : (
<div
style={{ width: "100vw", height: "100vh", background: "yellow" }}>
<Countdown date={Date.now() + 100000} />
<h1>{name}</h1>
<h3>Click counter: {counter} </h3>
<button
style={{
width: 100,
height: 50,
background: "red",
border: "none",
borderRadius: 25,
color: "white",
cursor: "pointer"
}}
onClick={() => updateCounter()}>Click here!</button>
</div>
)
}
</div>
);
}
export default App;
The problem is when I tap the button, setCounter(counter + 1) is running and rerender the page. That made the Counter reset.
I know why it have issue but I cannot fix it.
You need to memoize the Countdown component:
const CountdownWrapper = () => <Countdown date={Date.now() + 100000} />;
const MemoCountdown = React.memo(CountdownWrapper);
// usage
<MemoCountdown/>
Beforehand, on every render Date.now() gets a new value which resets the timer.
import React from 'react';
const time = React.useMemo(() => {
return Date.now() + 120 * 1000;
}, []);

TypeError: Cannot read property 'data' of undefined for shopping cart functionality

I keep getting this error: TypeError: Cannot read property 'data' of undefined, when there is no data being passed to my shopping cart page. How can I fix this error? Ideally, I would just like the page to display: "This cart is empty". I tried adding a conditional statement above the UserCardBlock, but it did not change anything. Thank you
import React, { useState } from 'react'
import { useDispatch } from 'react-redux';
import {
removeCartItem,
onSuccessBuy
} from '../../../_actions/user_actions';
import UserCardBlock from './Sections/UserCardBlock';
import { Result, Empty, Button } from 'antd';
import Paypal from '../../utils/Paypal';
function CartPage(props) {
const dispatch = useDispatch();
console.log(props)
const [Total, setTotal] = useState(props.location.state.data.price)
const [ShowTotal, setShowTotal] = useState(true)
const [ShowSuccess, setShowSuccess] = useState(false)
const removeFromCart = (productId) => {
dispatch(removeCartItem(productId))
}
const transactionSuccess = (data) => {
dispatch(onSuccessBuy({
cartDetail: props.user.cartDetail,
paymentData: data
}))
.then(response => {
setShowSuccess(true)
setShowTotal(false)
}
)
}
const transactionError = () => {
console.log('Paypal error')
}
const transactionCanceled = () => {
console.log('Transaction canceled')
}
const propductList = (data) =>{
console.log(data)
setTotal(data)
}
return (
<div style={{ width: '85%', margin: '3rem auto' }}>
<h1>My Cart</h1>
<div>
<UserCardBlock
productData={props.location.state.data}
removeItem={removeFromCart}
productList={data => propductList(data)}
/>
{ShowTotal ? (
<div style={{ marginTop: "3rem" }}>
<h2>Total amount: ${Total * 15} </h2>
</div>
) : ShowSuccess ? (
<Result status="success" title="Successfully Purchased Items" />
) : (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "center",
}}
>
<br />
<Empty description={false} />
<p>No Items In The Cart</p>
</div>
)}
</div>
{/* Paypal Button */}
{ShowTotal &&
<Paypal
toPay={Total}
onSuccess={transactionSuccess}
transactionError={transactionError}
transactionCanceled={transactionCanceled}
/>
}
</div>
)
}
export default CartPage
Seems like your component is dependent on location state.
const [Total, setTotal] = useState(props.location.state.data.price)
and
<UserCardBlock
productData={props.location.state.data}
Try using optional chaining and nullish coalescing
const [Total, setTotal] = useState(props.location?.state?.data?.price ?? 0)
<UserCardBlock
productData={props.location.state?.data ?? []}
It seems like you are using redux so i will suggest you to use redux store instead of location state.

need to create a new hidden div that shows after a onclick

I have a component that displays experiences. I want to create the option of showing a box ontop of my experiences with the different options they can do when they click an experience. I have different options for each experience.
This is my experiences
and I want to create a box like this with the corresponding images of each experience.
This is my experience component
import React, { memo, useCallback } from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/styles';
import Typography from '#material-ui/core/Typography';
import clsx from 'clsx';
import Popper from '#material-ui/core/Popper';
import gastronomia from 'assets/experiences/gastronomia.jpg';
import productos from 'assets/experiences/productos.jpg';
import giftcard from 'assets/experiences/giftcard.jpg';
import diversion from 'assets/experiences/diversion.jpg';
import deporte from 'assets/experiences/deporte.jpg';
import belleza from 'assets/experiences/belleza.jpg';
import gastronomiaExperiences from 'data/gastronomia';
import productosExperiences from 'data/productos';
import giftcardExperiences from 'data/giftcard';
import diversionExperiences from 'data/diversion';
import deporteExperiences from 'data/deporte';
import bellezaExperiences from 'data/belleza';
// Proptypes definitions to the component.
const propTypes = {
/** Custom root className. */
className: PropTypes.string,
};
// Default props definitions.
const defaultProps = {
className: null,
};
// Component's styles
const useStyles = makeStyles(theme => ({
root: {
display: 'block',
margin: '0 auto',
maxWidth: '50%',
[theme.breakpoints.down('md')]: {
maxWidth: '70%',
},
[theme.breakpoints.down('sm')]: {
maxWidth: '100%',
},
'& .experiences-column': {
display: 'inline-block',
verticalAlign: 'top',
textAlign: 'center',
'&.col1': {
width: '36.31%',
[theme.breakpoints.down('sm')]: {
width: 'initial',
},
},
'&.col2': {
width: '63.69%',
[theme.breakpoints.down('sm')]: {
width: 'initial',
},
},
'& .experience': {
padding: 2,
position: 'relative',
'& img': {
width: '100%',
display: 'block',
},
'& .experience-title': {
position: 'absolute',
bottom: 30,
left: 0,
right: 0,
textAlign: 'center',
},
},
},
},
}), { name: 'ExperiencesStyle' });
/**
* Component used to render a grid of experiences.
*
* #param {object} props - The component's props.
* #returns {object} React element.
*/
const Experiences = memo(
(props) => {
const { className } = props;
const classes = useStyles(props);
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = event => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : undefined;
const experience = useCallback((img, title) => (
<div className="experience" aria-describedby={id} onClick={handleClick} onClick={() => setAnchorEl(!anchorEl)}>
<img
data-sizes="auto"
className="lazyload"
data-src={img}
alt={title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{ title }
</Typography>
</div>
</div>
), []);
return (
<div className={clsx(classes.root, className)}>
<div className="experiences-column col1">
{experience(gastronomia, 'GASTRONOMÍA')}
{experience(giftcard, 'GIFT CARD')}
{experience(deporte, 'DEPORTE')}
</div>
<div className="experiences-column col2">
{experience(productos, 'PRODUCTOS')}
{experience(diversion, 'DIVERSIÓN')}
{experience(belleza, 'BELLEZA')}
</div>
<Popper id={id} open={open} anchorEl={anchorEl}>
<div className={classes.paper}>
<p>Array of images here depending on category </p>
</div>
</Popper>
</div>
);
},
);
// Component proptypes.
Experiences.propTypes = propTypes;
// Component default props.
Experiences.defaultProps = defaultProps;
export default Experiences;
I am complete lost on how to start this task. I am new to react
Ok, so
Delete experience from Experiences.
Add array of object - of your experiences
const iterateExperience = [
{img: gastronomia, title: 'GASTRONOMIA' },
{img: productos, title: 'PRODUCTOS' },
];
Later map it like this:
<div className={clsx(classes.root, className)}>
{iterateExperience.map((experience, index) => (
<div key = {index}>
<Experience img = {experience.img} title = {experience.title}/>
</div>
))}
</div>
Create Child Component - Experience:
const Experience = (props) => {
const [clicked, setClicked] = useState(true)
return (
<div onClick={() => setClicked(!clicked)}
className={clicked ? 'experience' : 'experience-clicked'}
>
<img
data-sizes="auto"
className="lazyload"
data-src={props.img}
alt={props.title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{props.title}
</Typography>
</div>
</div>
);
}
export default Experience
And if You wan't render the different component based on click, just add in Experience (You can also apply another style - whatever you want)
const Experience = (props) => {
const [clicked, setClicked] = useState(true)
return (
<div onClick={() => setClicked(!clicked)} className='experience'>
{clicked ?
<div>
<img
data-sizes="auto"
className="lazyload"
data-src={props.img}
alt={props.title}
/>
<div className="experience-title">
<Typography
color="textSecondary"
variant="subtitle2"
className="highlight highlight1"
display="inline"
>
{props.title}
</Typography>
</div>
</div>
:
<p>Another Component</p>}
</div>
);
}

Categories

Resources