I need to create thousands of elements and then perform some work based on their refs.
The problem however is that the refs are all undefined on the initial render. I need them to be available on initial page load.
You can find my codesandbox here. Once you click the button (which forces a rerender), all refs are defined as expected.
Here is the source code for completeness:
import { useRef, useState } from "react";
export default function App() {
const refs = useRef(new Map());
const [bla, setBla] = useState(0);
return (
<>
<button onClick={() => setBla((prevState) => prevState + 1)}>
click me ({bla})
</button>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<div>
{[...Array(100)].map((_, k) => (
<div key={k} ref={(ref) => refs.current.set(k, ref)}>
{k}
</div>
))}
</div>
<div>
{[...Array(100)].map((_, k) => (
<div key={k}>{refs.current.get(k) ? "defined" : "undefined"}</div>
))}
</div>
</div>
</>
);
}
Solved it with useEffect. (codesandbox)
import { useEffect, useRef, useState } from "react";
export default function App() {
const refs = useRef(new Map());
const outerRef = useRef(null);
const [outerRefLoaded, setOuterRefLoaded] = useState(false);
useEffect(() => {
// setTimeout(() => {
setOuterRefLoaded(outerRef.current !== null);
// }, 5000);
}, [outerRef]);
const [bla, setBla] = useState(0);
return (
<>
<button onClick={() => setBla((prevState) => prevState + 1)}>
click me ({bla})
</button>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
<div ref={outerRef}>
{[...Array(100)].map((_, k) => (
<div key={k} ref={(ref) => refs.current.set(k, ref)}>
{k}
</div>
))}
</div>
<div>
{!outerRefLoaded && "loading..."}
{outerRefLoaded &&
[...Array(100)].map((_, k) => (
<div key={k}>{refs.current.get(k) ? "defined" : "undefined"}</div>
))}
</div>
</div>
</>
);
}
Related
I would appreciate to know why it gives this './undefined.jpg' before anything else and only AFTER that, renders all the actual paths.
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import style from './CarsListPage.module.scss';
//import cars from './car-content';
import CarsList from '../components/CarsList';
const CarsListPage = () => {
const [carsInfo, setCarsInfo] = useState([{}]);
useEffect(() => {
const loadCarsInfo = async () => {
const response = await axios.get('/api/cars');
const newCarsInfo = response.data;
setCarsInfo(newCarsInfo);
};
loadCarsInfo();
}, []);
return (
<div className={style.mainCotainer}>
<main className={style.main}>
<h1>Cars</h1>
<div className={style.container}>
{carsInfo.map((car) => (
<Link to={`/cars/${car.name}`} key={car.id}>
<div className={style.card} key={car.id}>
<h3>{car.name}</h3>
{/* {console.log(`../temp-img/${car.title}.jpg`)} */}
<p>{car.body_type}</p>
<p>{car.origin}</p>
<p>{car.year}</p>
<img
src={require(`../temp-img/${car.title}.jpg`)}
alt={car.name}
style={{ width: '200px' }}
/>
</div>
</Link>
))}
</div>
</main>
</div>
);
};
export default CarsListPage;
I've found couple solutions like wrapping everying into div and check whether value exists or not but i could not optimize it for my code.
Change the default state of carsInfo to [] otherwise you will map on an empty object until you get the data from the API:
const CarsListPage = () => {
const [carsInfo, setCarsInfo] = useState([]);
useEffect(() => {
const loadCarsInfo = async () => {
const response = await axios.get('/api/cars');
const newCarsInfo = response.data;
setCarsInfo(newCarsInfo);
};
loadCarsInfo();
}, []);
return (
<div className={style.mainCotainer}>
<main className={style.main}>
<h1>Cars</h1>
<div className={style.container}>
{carsInfo.length && carsInfo.map((car) => (
<Link to={`/cars/${car.name}`} key={car.id}>
<div className={style.card} key={car.id}>
<h3>{car.name}</h3>
{/* {console.log(`../temp-img/${car.title}.jpg`)} */}
<p>{car.body_type}</p>
<p>{car.origin}</p>
<p>{car.year}</p>
<img
src={require(`../temp-img/${car.title}.jpg`)}
alt={car.name}
style={{ width: '200px' }}
/>
</div>
</Link>
))}
</div>
</main>
</div>
);
};
I have created a navbar with light and dark mode, everything working well. Now I want to update it to multi-page with react-router and with a layout. If I gave to the path name to the url is working well. The problem is the url shows me the page but the navbar doesn't navigate to the url and doesn't toggle the dark/light mode.
import React, { useRef, useEffect } from "react";
import "./header.css";
import { Link, useMatch, useResolvedPath } from "react-router-dom"
const nav__links = [
{
to: "home",
display: "Home",
},
{
to: "service",
display: "Service",
},
{
to: "preise",
display: "Preise",
},
{
to: "kontakt",
display: "Kontakt",
},
];
const Header = ({ theme, toggleTheme }) => {
const headerRef = useRef(null);
const menuRef = useRef(null);
const headerFunc = () => {
if (
document.body.scrollTop > 80 ||
document.documentElement.scrollTop > 80
) {
headerRef.current.classList.add("header__shrink");
} else {
headerRef.current.classList.remove("header__shrink");
}
};
useEffect(() => {
window.addEventListener("scroll", headerFunc);
return () => window.removeEventListener("scroll", headerFunc);
}, []);
const handleClick = (e) => {
e.preventDefault();
const targetAttr = e.target.getAttribute("to");
const location = document.querySelector(targetAttr).offsetTop;
window.scrollTo({
left: 0,
top: location - 80,
});
};
const toggleMenu = () => menuRef.current.classList.toggle("menu__active");
function CustomLink({ to, children, ...props }) {
const resolvedPath = useResolvedPath(to)
const isActive = useMatch({ path: resolvedPath.pathname, end: true })
return (
<li className={isActive ? "active" : ""}>
<Link to={to} {...props}>
{children}
</Link>
</li>
);
};
return (
<header className="header" ref={headerRef}>
<div className="container">
<div className="nav__wrapper">
<div className="logo" to="home">
<Link to="home"><h2>Q-Tech</h2></Link>
</div>
{/* ========= navigation ============= */}
<div className="navigation" ref={menuRef} onClick={toggleMenu}>
<ul className="menu">
{nav__links.map((item, index) => (
<li className="menu__item" key={index}>
<CustomLink
to={item.to}
onClick={handleClick}
className="menu__link"
>
{item.display}
</CustomLink>
</li>
))}
</ul>
</div>
{/* ============ light mode ============= */}
<div className="light__mode">
<span onClick={toggleTheme}>
{theme === "light-theme" ? (
<span>
<i class="ri-moon-line"></i>Dark
</span>
) : (
<span>
<i class="ri-sun-line"></i> Light
</span>
)}
</span>
</div>
<span className="mobile__menu" onClick={toggleMenu}>
<i class="ri-menu-line"></i>
</span>
</div>
</div>
</header>
);
};
export default Header;
Why navigation is not working?
Look at the navigation link definition - CustomLink:
<CustomLink
onClick={handleClick} // <--
...
>
You have an onClick handler on the link. Inside this event handler you call e.preventDefault(), which prevents the navigaton behaviour.
Why toggle light/dark theme it not working?
The toggle button here looks fine. So it is probably a problem in the code outside Header component. Try to debug toggleTheme functionality.
Here is the code,
import React, { useEffect, useState } from 'react'
import './Body.css'
export default function Navbar() {
const [value, setvalue] = useState(0);
const [value1, setvalue1] = useState(0);
return (
<>
<div>
<h1>Our App</h1>
<div className="back">
<h3>Counter</h3>
<button onClick={() => [() =>{setvalue(value - 1)} , ()=>{setvalue1(value1 - 1)}] }> - </button>
<button>{value}</button>
<button>{value1}</button>
<button onClick={()=> [() =>{setvalue(value + 1)} , ()=>{setvalue1(value1 + 1)}] } > + </button>
</div>
</div >
</>
)
}
I checked here how to pass two functions in the onClick event and this is the only way I am not getting a syntax error.
Can anyone suggest some edits to this?
Your click handler does not call the two functions instead, it returns two functions in an array.
You just need to call the two functions like this:
function Navbar() {
const [value, setvalue] = React.useState(0);
const [value1, setvalue1] = React.useState(0);
return (
<div>
<h1>Our App</h1>
<div className="back">
<h3>Counter</h3>
<button
onClick={() => {
setvalue((value) => value - 1);
setvalue1((value1) => value1 - 1);
}}
>
{" "}
-{" "}
</button>
<button>{value}</button>
<button>{value1}</button>
<button
onClick={() => {
setvalue((value) => value + 1);
setvalue1((value1) => value1 + 1);
}}
>
{" "}
+{" "}
</button>
</div>
</div>
);
}
ReactDOM.render(<Navbar />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
In the code that you share, in onclick attribute you're executing an arrow function that only returns (implicitly) an array containing two functions, but no one es executed ever as they're only two references.
For this to work you must only call the arrow function and in that function execute your two setState(). Here's your code modified and working.
import { useState } from 'react';
export default function Navbar() {
const [value, setvalue] = useState(0);
const [value1, setvalue1] = useState(0);
return (
<>
<div>
<h1>Our App</h1>
<div className="back">
<h3>Counter</h3>
<button onClick={() => {
setvalue(value - 1)
setvalue1(value1 - 1)
}}
> - </button>
<button>{value}</button>
<button>{value1}</button>
<button onClick={() => {
setvalue(value + 1)
setvalue1(value1 + 1)
}} > + </button>
</div>
</div >
</>
)
}
Verified & tested
onClick={()=> {setvalue(value + 1); setvalue1(value1 + 1)};}}
When I use setHover it reflects to all list data which returned from map loop. How can I use hover to reflect on itself element?
const [hover, setHover] = useState(true)
function MouseOver(event) {
setHover(true)
}
function MouseOut(event){
setHover(false)
}
{data.map((item, index) => (
//When I hover parent div I want to show the {item.arrow} div inside and not all {item.arrow} divs in the loop
<div key={index} onMouseEnter={MouseOver} onMouseLeave={MouseOut} className="flex gap-3">
<div>
{item.content}
</div>
<div hidden={hover}>
{item.arrow}
</div>
</div>
))}
If the state does not need to be controlled by the parent you can create a new component to use in the list.
Each component will then control its own hover state.
const List = ({data}) => {
return (
<div>
{
data.map((item, index) => (<Item key={index} item={item} />))
}
</div>
)
}
const Item = ({item}) => {
const [hover, setHover] = useState(true)
const mouseOver = (event) => {
setHover(true)
}
const mouseOut = (event) => {
setHover(false)
}
return (
<div onMouseEnter={mouseOver} onMouseLeave={mouseOut} className="flex gap-3">
<div>
{item.content}
</div>
<div hidden={hover}>
{item.arrow}
</div>
</div>
);
}
If the state does need to be controlled by the parent you can use a Record<number, boolean> to store the states.
const List = ({data}) => {
const [hover, setHover] = useState({})
const mouseOver = (event, index) => {
setHover(c => {
return {
...c,
[index]: true
};
})
}
const mouseOut = (event, index) => {
setHover(c => {
return {
...c,
[index]: false
};
})
}
return (
<div>
{
data.map((item, index) => (
<div
key={index}
onMouseEnter={(e) => {
mouseOver(e, index);
}}
onMouseLeave={(e) => {
mouseOut(e, index);
}}
className="flex gap-3"
>
<div>
{item.content}
</div>
<div hidden={hover[index]}>
{item.arrow}
</div>
</div>
))
}
</div>
)
}
If the state is not needed for anything other than hiding a div you could also just use CSS.
CSS will not require the component to rerender everytime you hover over it.
CSS
.hoverable-show {
display: none;
}
.hoverable-item:hover .hoverable-show {
display: block;
}
JS
const List = ({data}) => {
return (
<div>
{
data.map((item, index) => (
<div
className="flex gap-3 hoverable-item"
>
<div>
{item.content}
</div>
<div className="hoverable-show">
{item.arrow}
</div>
</div>
))
}
</div>
)
}
Preference should be CSS -> Individual State -> Parent (list) State.
This looks like a use case for the useReducer hook available right from the react library.
I am using a class component in react and would like to know how I can add a CSS class to the current i.e clicked element which is inside a map statement. I would like to do it using state.
<div key={q.id} id={q.id}>
<h2 className={this.state.title}>{q.title}</h2>
<h3>{q.questionText}</h3>
<div key={q.id}>
{q.options.map((opt, index) => (
<div
key={opt.id}
val={opt.val}
ref={this.options}
className={index === this.state.clickedItem ? 'myclass' : null}
onClick={() => this.setState({ clickedItem: index })}>
<p onClick={this.submitQuestion} ref={this.correctRef}>
{opt.text}
</p>
</div>
))}
</div>
Here your state
state = {clickedItem: 0}
in render
yourArray.map((el, index) => {
<div
onClick={() => this.setState({clickedItem: index})}
key={index}
className={index === this.state.clickedItem ? 'Your ClassName' : null}>
{el.name}
</div>
})
In functional with useState hook, without class.
Hope this can help.
https://codesandbox.io/s/blissful-boyd-6px43?file=/src/App.js
import "./styles.css";
/*
.is-checked {
background-color: #901c1c;
color: white;
}
*/
import React, { useState } from "react";
const App = () => {
const tags = ["portrait", "événements", "voyage", "animaux"];
const [clickedItem, setClickedItem] = useState(null);
const handleCSS = (e) => {
e.preventDefault();
let selectedTag = e ? parseInt(e.target.id, 10) : null;
setClickedItem(selectedTag);
console.log(">> clickedItem", clickedItem);
};
return (
<>
<div className="App">
<h1>Hello !</h1>
</div>
<div>
{tags.map((tag, index) => {
return (
<button
type="button"
key={index}
onClick={handleCSS}
id={index}
className={index === clickedItem ? "is-checked" : null}
>
{`#${tag}`}
</button>
);
})}
</div>
</>
);
};
export default App;