I have an error with the code below
( Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.)
The goal is to add a span on each item excepted the last one.
What is the best way to do that?
const Section = () => {
const [lastItem, setlastItem] = React.useState(false);
// rendu des Sections
const sectionLentgh = Data.sections.length;
const sectionList = Data.sections.map((item, i) => {
// AJout du séparateur
if (sectionLentgh === i + 1) {
setlastItem(false);
} else {
setlastItem(true);
}
console.log(i);
return (
<div>
<h2>{item.title}</h2>
<img src={`/images/${item.image}`}></img>
<span style={{ backgroundImage:`url(/images/${item.image})` }}></span>
<p dangerouslySetInnerHTML={{ __html: item.description }} />
<span className={`${ lastItem ? styles.separator : '' }`}></span>
</div>
);
})
return (
<>
<div className={styles.sections}>
{sectionList}
</div>
</>
);
};
export default Section;
Just use the length of the array and compare it to the index of the iteration:
const Section = () => {
const sectionLength = Data.sections.length;
const sectionList = Data.sections.map((item, i) => {
const lastItem = i === (sectionLength - 1);
return (
<div>
<h2>{item.title}</h2>
<img src={`/images/${item.image}`}></img>
<span style={{ backgroundImage: `url(/images/${item.image})` }}></span>
<p dangerouslySetInnerHTML={{ __html: item.description }} />
<span className={`${lastItem ? styles.separator : ""}`}></span>
</div>
);
});
return (
<>
<div className={styles.sections}>{sectionList}</div>
</>
);
};
export default Section;
You enter an infinite loop because you call setlastItem in the map function, which in turn reruns on every render. Since setState triggers a new render, this causes the infinite loop.
What you want to is to put the generation of the sectionList in a useEffect, that reruns only every time the Data.sections changes.
Like this:
const Section = () => {
const [sectionList, setSectionList] = useState([]);
useEffect(() => {
if(!Data.sections|| Data.sections.length < 2){
setSectionList([]);
} else {
setSectionList(Data.sections.splice(-1, 1));
}
}, [Data.sections]);
return (
<div className={styles.sections}>
{sectionList.map(item => (
<div>
<h2>{item.title}</h2>
<img src={`/images/${item.image}`}></img>
<span style={{ backgroundImage: `url(/images/${item.image})`}
}></span>
<p dangerouslySetInnerHTML={{ __html: item.description }} />
<span className={`${lastItem ? styles.separator : ""}`}></span>
</div>
)}
</div>
);
};
As you see, I separated the generation of the data from the jsx, which makes the code more easy to understand and rebuild, I find.
const Section = () => {
return (
<>
<div className={styles.sections}>
{Data.sections.map((item, id) => (
<div key={id}>
<h2>{item.title}</h2>
<img src={`/images/${item.image}`}></img>
<span style={{ backgroundImage: `url(/images/${item.image})` }
}></span>
<p dangerouslySetInnerHTML={{ __html: item.description }} />
<span className={`${id < (Data.sections.length - 1) ? styles.separator : ""}`}></span>
</div>
))
}
</div>
</>
);
};
or
const Section = () => {
return (
<>
{Data.sections.map((item, id) => (
<div key={id}>
<div className={styles.sections} key={id}>
<h2>{item.title}</h2>
<img src={`/images/${item.image}`}></img>
<span style={{ backgroundImage: `url(/images/${item.image})` }
}></span>
<p dangerouslySetInnerHTML={{ __html: item.description }} />
</div>
{ id < (Data.sections.length - 1) &&
<span className={styles.separator}></span>
}
</div>
))
}
</>
);
};
Related
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>
);
}
I'm doing pokedex (pokemon wiki stuff). I want to change my component view, when clicking on pokemon images (description lookalike). When I click on an image - nothing happens (firstly, I want at least pokemon's name to be added to the pokemonDescription array). What am I doing wrong?
let pokemonDescription = [];
const useForceUpdate = () => {
const [value, setValue] = useState(true);
return () => setValue(value => !value);
}
const forceUpdate = useForceUpdate();
const onPokemonClick = (event) => {
console.log(
"wrapper clicked, event.target - ",
event.target.getAttribute('data-name')
);
pokemonDescription = [];
pokemonDescription.push(event.target.getAttribute('data-name'));
console.log("description array -", pokemonDescription);
forceUpdate();
};
useEffect(() => {
document.querySelector(".wrapper").addEventListener("click", onPokemonClick);
...
return () => {
document.querySelector(".wrapper").removeEventListener("click", onPokemonClick);
};
}, []);
...
return (
<div className="Pokemons">
<div className="column pokemons-list">
<div className="wrapper">
{
pokemonsData.map((p, id) => (
<div className="box" key={ id }>
<img
src={ p.sprites.front_default }
alt="pokemon-img"
title={ p.name }
className="icon"
data-name={p.name}
/>
{ p.name}
<div className="container">
{ pokemonsTypes[id] }
</div>
</div>
))
}
</div>
...
</div>
<div className="column description">
{ pokemonDescription }
</div>
</div>
)
You should add pokemonDescription to your component state
const [pokemonDescription, setPokemonDescription] = useState([]);
Remove the forceUpdate function and hook, it is unnecessary.
Attach the click handlers to the elements with the data-name attribute you are trying to handle.
Map the pokemonDescription state array to renderable JSX. I simply used a div, but you should use whatever your UI design requires.
const onPokemonClick = (event) => {
setPokemonDescription(names => [
...names,
event.target.getAttribute('data-name'),
]);
};
...
return (
<div className="Pokemons">
<div className="column pokemons-list">
<div className="wrapper">
{
pokemonsData.map((p, id) => (
<div className="box" key={ id }>
<img
src={ p.sprites.front_default }
alt="pokemon-img"
title={ p.name }
className="icon"
data-name={p.name}
onClick={onPokemonClick} // <-- attach click handler to img element
/>
{ p.name}
<div className="container">
{ pokemonsTypes[id] }
</div>
</div>
))
}
</div>
...
</div>
<div className="column description">
{pokemonDescription.map(name => (
<div>{name}</div>
))}
</div>
</div>
)
Add pokemonDescription to state instead of some local variable and it will solve your issue.
Try to avoid using forceUpdate, most of the times it means only that you are doing something silly.
I don't what that useForceUpdate does , but here is how would go about adding pokemon names to description array which is a state variable in my answer
const [pokemonDescription , setPokemonDescription ] = useState(null);
const onPokemonClick = (p) => {
const tempPokemonDescription = [...pokemonDescription ];
pokemonDescription.push(p.name);
console.log("description array -", pokemonDescription);
setPokemonDescription(tempPokemonDescription )
};
...
return (
<div className="Pokemons">
<div className="column pokemons-list">
<div className="wrapper">
{
pokemonsData.map((p, id) => (
<div className="box" onClick={e=>onPokemonClick(p)} key={ id }>
<img
src={ p.sprites.front_default }
alt="pokemon-img"
title={ p.name }
className="icon"
/>
{ p.name}
<div className="container">
{ pokemonsTypes[id] }
</div>
</div>
))
}
</div>
...
</div>
<div className="column description">
{ pokemonDescription }
</div>
</div>
)
I have a listing of articles that are displayed on cards.
I need to implement a search bar to search for articles.
In the code I make a map in the CardArticle component so that it can be rendered as the back-end sends the information.
In the back-end there is already a route to search: /v1/articles?search=${search}
When the user accesses the article page, he will display all articles and only when he clicks to search will the search be made. And when he deletes the words from the search bar, he will return to displaying all articles.
code:
export default function Articles() {
const { data: articles } = useSWR(`/v1/articles`, fetch);
if (!articles) {
return (
<div style={{ paddingTop: 90 }}>
<Loading />
</div>
);
}
return (
<>
<Search>
<span>
<IconSearch color={theme.colorsCommon.secundary} />
</span>
<input placeholder="Busque por autor ou artigos" />
</Search>
{articles.map(article => (
<Link to={`/articles/${article.slug}`}>
<CardArticle key={article.guid}>
<Image>
<img
src={!article ? noPhoto : verifyPhoto(article.cover_photo)}
alt={article.title}
/>
</Image>
<TopCard>
<div className="categorys">
{article.categories.map(category => (
<Category key={category.id}>{category.name}</Category>
))}
</div>
</TopCard>
<DetailsArticle>
<div className="title">
<span>{article.title}</span>
</div>
</DetailsArticle>
<BottomCard>
<div className="author">
<img
src={
!article.author
? noPhoto
: verifyPhoto(article.author.photo)
}
alt={!article.author ? [] : article.author.name}
/>
<span>{!article.author ? [] : article.author.name}</span>
</div>
<div className="createDate">{formatDate(article.created_at)}</div>
</BottomCard>
</CardArticle>
</Link>
))}
</>
);
}
export default function Articles() {
const [search, setSearch] = useState('');
const [debounceSearch, setdebounceSearch] = useState('');
const { data: articles } = useSWR(
`/v1/articles${debounceSearch ? `?search=${debounceSearch}` : ''}`,
fetch
);
const handleOnChange = useCallback(({ target: { value } }) => {
setSearch(value);
}, []);
useEffect(() => {
const timerId = setTimeout(() => {
setdebounceSearch(search);
}, 250);
return () => {
clearTimeout(timerId);
};
}, [search]);
if (!articles) {
return (
<div style={{ paddingTop: 90 }}>
<Loading />
</div>
);
}
return (
<>
<Search>
<span>
<IconSearch color={theme.colorsCommon.secundary} />
</span>
<input
value={search}
placeholder="Busque por autor ou artigos"
onChange={handleOnChange}
/>
</Search>
{articles.map(article => (
<Link to={`/articles/${article.slug}`}>
<CardArticle key={article.guid}>
<Image>
<img
src={!article ? noPhoto : verifyPhoto(article.cover_photo)}
alt={article.title}
/>
</Image>
<TopCard>
<div className="categorys">
{article.categories.map(category => (
<Category key={category.id}>{category.name}</Category>
))}
</div>
</TopCard>
<DetailsArticle>
<div className="title">
<span>{article.title}</span>
</div>
</DetailsArticle>
<BottomCard>
<div className="author">
<img
src={
!article.author
? noPhoto
: verifyPhoto(article.author.photo)
}
alt={!article.author ? [] : article.author.name}
/>
<span>{!article.author ? [] : article.author.name}</span>
</div>
<div className="createDate">{formatDate(article.created_at)}</div>
</BottomCard>
</CardArticle>
</Link>
))}
</>
);
}
It's my first project working with React, and I'm fetching an API. I'm trying to pass on the data but it keeps giving me the same error however I do it.
Also, I can log the data in the async function addTrade, but it logs twice for some reason?
fetch:
async function asyncData() {
const response = await fetch('https://api.pokemontcg.io/v1/cards?setCode=base1');
const data = await response.json();
return data;
}
my render function:
const addTrade = async () => {
const pokemonList = await asyncData();
const cards = pokemonList.cards;
console.log(cards);
return <>
<div className='add-trade-container'>
<div className='trade-add-window'>
<div id={1} onClick={e => handleActiveTab(e)} className={tradeBoard.activeTab === '1' ? 'trade-tab trade-tab-active' : 'trade-tab'}>
<span className='trade-tab-title'>Has</span>
{tradeBoard.has.map(has =>
<>
<div className={tradeBoard.activeTab === '1' ? 'window window-active' : 'window'}>{has.content}</div>
</>
)}
</div>
<img className='divider' src='./src/assets/img/trade-icon.svg' width='30' height='30'></img>
<div id={2} onClick={e => handleActiveTab(e)} className={tradeBoard.activeTab === '2' ? 'trade-tab trade-tab-active' : 'trade-tab'}>
<span className='trade-tab-title'>Wants</span>
{tradeBoard.wants.map(wants =>
<>
<div className={tradeBoard.activeTab === '2' ? 'window window-active' : 'window'}>{wants.content}</div>
</>
)}
</div>
</div>
<article className='list'>
<label className='search-label' htmlFor='search'>Search a Pokémon
<input className='search-field' type='text' id='search'></input>
</label>
<div className='list-grid'>
{cards.map((card, index) =>
<div onClick={() => handleClickContent(<PokeItem pokemon={card} index={index}/>)} key={index}>
<PokeItem pokemon={card} index={index}/>
</div>
)}
</div>
</article>
<button onClick={handleSeedClick} className='button-add'>Add a trade</button>
</div>
</>;
};
App:
const App = () => {
return useObserver(() => (
<>
{addTrade()}
<div className='trades'>
{showTrades()}
</div>
</>
));
};
const roottype = document.getElementById('root');
const headtype = document.getElementById('header');
ReactDOM.render(<App />, roottype);
ReactDOM.render(<Header />, headtype);
you need to use react hooks for this
first you need to import useState, useCallback from react library like this import React, { useState, useCallback } from "react";
then use useEffect like this:
const addTrade = async () => {
const [cards,setCards] = useState(null)
useEffect(() => {
async function getData() {
try {
const pokemonList = await asyncData();
const cardsList = pokemonList.cards;
setCards(cardsList);
console.log(cardsList);
} catch (error) {
console.log(error);
}
}
getData();
}, [cards]);
return <>
<div className='add-trade-container'>
<div className='trade-add-window'>
<div id={1} onClick={e => handleActiveTab(e)} className={tradeBoard.activeTab === '1' ? 'trade-tab trade-tab-active' : 'trade-tab'}>
<span className='trade-tab-title'>Has</span>
{tradeBoard.has.map(has =>
<>
<div className={tradeBoard.activeTab === '1' ? 'window window-active' : 'window'}>{has.content}</div>
</>
)}
</div>
<img className='divider' src='./src/assets/img/trade-icon.svg' width='30' height='30'></img>
<div id={2} onClick={e => handleActiveTab(e)} className={tradeBoard.activeTab === '2' ? 'trade-tab trade-tab-active' : 'trade-tab'}>
<span className='trade-tab-title'>Wants</span>
{tradeBoard.wants.map(wants =>
<>
<div className={tradeBoard.activeTab === '2' ? 'window window-active' : 'window'}>{wants.content}</div>
</>
)}
</div>
</div>
<article className='list'>
<label className='search-label' htmlFor='search'>Search a Pokémon
<input className='search-field' type='text' id='search'></input>
</label>
<div className='list-grid'>
{cards.map((card, index) =>
<div onClick={() => handleClickContent(<PokeItem pokemon={card} index={index}/>)} key={index}>
<PokeItem pokemon={card} index={index}/>
</div>
)}
</div>
</article>
<button onClick={handleSeedClick} className='button-add'>Add a trade</button>
</div>
</>;
};
I'm relatively new to React and trying to render an array of JSX fragments that all look like this.
<>
<div className="tourCard" key={address.name}>
<h3 className="tourCard__header">{address.name}</h3>
<div className="tourCard__capacity">Capacity: {foundUser.capacity}</div>
{foundUserAddress}
{foundUserAddress2}
<section className="tourCard__cityStateZip">
<div className="tourCard__city">{foundUser.city}</div>
<div className="tourCard__state">{foundUser.state}</div>
{foundUserZip}
</section>
<h5 className="tourCard__blurbHeader">About Us</h5>
{foundUserBlurb}
{socialMediaButtonClicked ? (
<>
{foundUserSocialMedia}
</>
) : (
<>
<button onClick={() => {
socialMediaButtonClicked = true
}}>Social media</button>
</>
)}
</div>
</>
I'm pushing them into an array exactly as above, and I'm struggling with the right format in the return statement below to get them to render.
I've tried
return (
<>
<section className="tourSection">
{tourCards}
</section>
</>
)
and
return (
<>
<section className="tourSection">
{tourcards.map(tourCard => {
return(
{tourCard}
)
}
</section>
</>
)
But neither were successful. Where am I going wrong?
Complete page code below:
import React, {useContext, useState} from "react"
import { withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps'
import { AddressContext } from "../addresses/AddressProvider"
import { UserContext } from "../user/UserProvider"
import { render } from '#testing-library/react'
export default (props) => {
const { addresses } = useContext(AddressContext)
const { users } = useContext(UserContext)
let tourCards = []
const PlanMap = withGoogleMap(props => (
<GoogleMap google={window.google} defaultCenter = { { lat: 39.5, lng: -98.35 } }
defaultZoom = { 4 }>
{
addresses.map(address =>
<>
<Marker
key={address.id}
position={{
lat: address.address.lat,
lng: address.address.lng
}}
label={{
text: "Hello World!",
fontFamily: "Arial",
fontSize: "14px",
}}
>
<InfoWindow
key={address.id}>
<>
<span>{address.name}</span>
<div>
<button onClick={() => {
let foundUser = users.find(user => user.name === address.name)
let foundUserAddress = ""
if (foundUser.hasOwnProperty("address") && foundUser.hasOwnProperty("addressPublic") && foundUser.addressPublic === true) {
foundUserAddress = <>
<div className="tourCard__address">{foundUser.address}</div>
</>
}
let foundUserAddress2 = ""
if (foundUser.hasOwnProperty("address2") && foundUser.hasOwnProperty("address2Public") && foundUser.address2Public === true) {
foundUserAddress2 = <>
<div className="tourCard__address2">{foundUser.address2}</div>
</>
}
let foundUserZip = ""
if (foundUser.hasOwnProperty("zip") && foundUser.hasOwnProperty("zipPublic") && foundUser.zipPublic === true) {
foundUserZip = <>
<div className="tourCard__zip">{foundUser.zip}</div>
</>
}
let foundUserBlurb = ""
if (foundUser.hasOwnProperty("blurb") && foundUser.hasOwnProperty("blurbPublic") && foundUser.blurbPublic === true) {
foundUserBlurb = <>
<div className="tourCard__blurb">{foundUser.blurb}</div>
</>
}
let socialMediaButtonClicked = false
let foundUserWebsite = ""
let foundUserFacebook = ""
let foundUserInstagram = ""
let foundUserTwitter = ""
let foundUserSocialMedia = <>
<section className>
{foundUserWebsite}
{foundUserFacebook}
{foundUserInstagram}
{foundUserTwitter}
</section>
</>
if (foundUser.webPublic === true) {
if (foundUser.hasOwnProperty("website")) {
foundUserWebsite = <>
<div className="tourCard__website">{foundUser.website}</div>
</>
}
if (foundUser.hasOwnProperty("facebook")) {
foundUserFacebook = <>
<div className="tourCard__facebook">{foundUser.facebook}</div>
</>
}
if (foundUser.hasOwnProperty("instagram")) {
foundUserInstagram = <>
<div className="tourCard__instagram">{foundUser.instagram}</div>
</>
}
if (foundUser.hasOwnProperty("twitter")) {
foundUserInstagram = <>
<div className="tourCard__twitter">{foundUser.twitter}</div>
</>
}
}
tourCards.push(
<div className="tourCard" key={address.name}>
<h3 className="tourCard__header">{address.name}</h3>
<div className="tourCard__capacity">Capacity: {foundUser.capacity}</div>
{foundUserAddress}
{foundUserAddress2}
<section className="tourCard__cityStateZip">
<div className="tourCard__city">{foundUser.city}</div>
<div className="tourCard__state">{foundUser.state}</div>
{foundUserZip}
</section>
<h5 className="tourCard__blurbHeader">About Us</h5>
{foundUserBlurb}
{socialMediaButtonClicked ? (
<>
{foundUserSocialMedia}
</>
) : (
<>
<button onClick={() => {
socialMediaButtonClicked = true
}}>Social media</button>
</>
)}
</div>
)
console.log(tourCards)
debugger
}}
>
Add to tour
</button>
</div>
</>
</InfoWindow>
</Marker>
</>
)
}
</GoogleMap>
));
return (
<>
<div>
<PlanMap
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px`, width: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
<section className="tourSection">
{tourCards}
</section>
</>
)
}
<> ... </> is not an array. Depending on how and where your tourCards are created you could do something like:
var tourCards = [];
tourCards.push(<div className="tourCard"> ... </div>);
// ... more tourCards.push()
return (
<>
<section className="tourSection">
{tourCards}
</section>
</>
);
As I said in the comments, I think your problem is just that your component doesn't re-render after you update tourCards. And even more, since tourCards is redefined as an empty array each time your component re-renders, it won't contain whatever you try to put into it. So you should use useState to keep track of it.
When working with arrays using react hooks it's a good idea to treat them as immutable and create a new array each time you set the state using array spreading
const [arr, setArr] = useState([])
// somewhere in an effect or callback
setArr(prevArr => [...prevArr, newValue]) // this is the same as arr.push
setArr(prevArr => [newValue, ...prevArr]) // this is the same as arr.unshift
setArr(prevArr => [...newArr, ...prevArr]) // this is arr.concat
Here's a working example you should be able to run right here on SO:
const {useState} = React
function App() {
const [tourcards, setTourcards] = useState([])
const addTourCard = function() {
// Do whatever you need to do here
const card = <div key={`card-${tourcards.length}`}>Card</div>
setTourcards(cards => [...cards, card])
}
return (
<div>
<button onClick={addTourCard}>Click Me</button>
<div>{tourcards}</div>
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#root"));
<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 id="root"></div>