React - Using a for-loop inside JSX - reactjs

In the following React component:
import React, { useState, useEffect } from 'react';
const List = () => {
const docLimit = 2;
const maxColNumber = 6;
const [cards, setCards] = useState([]);
const [beginAfter, setBeginAfter] = useState(0);
return (
<>
<div className='collection'>
<div className='collection__set' onClick={() => setBeginAfter(0)}>
First 2 data
</div>
<div
className='collection__set'
onClick={() => setBeginAfter(docLimit * 1)}
>
Next 2 data
</div>
<div
className='collection__set'
onClick={() => setBeginAfter(docLimit * 2)}
>
Next 2 data
</div>
<div
className='collection__set'
onClick={() => setBeginAfter(docLimit * 3)}
>
Next 2 data
</div>
</div>
<ul className='list'>
{cards.map((card) => (
<li key={card.id} className='list__item'>
<DeleteCard card={card} />
</li>
))}
</ul>
</>
);
};
export default List;
I want to use a loop to output this part:
<div className='collection'>
<div className='collection__set' onClick={() => setBeginAfter(0)}>
First 2 data
</div>
<div
className='collection__set'
onClick={() => setBeginAfter(docLimit * 1)}
>
Next 2 data
</div>
<div
className='collection__set'
onClick={() => setBeginAfter(docLimit * 2)}
>
Next 2 data
</div>
<div
className='collection__set'
onClick={() => setBeginAfter(docLimit * 3)}
>
Next 2 data
</div>
</div>
But it doesn't matter what I try, it doesn't work.
I tried this:
const renderMenu = () => {
const menu = [];
for (var i = 0; i < maxColNumber; i++) {
menu.push(
<div
className='collection__set'
onClick={setBeginAfter(docLimit * i)}
>
Next 2 data
</div>
);
}
return (
<div className='collection'>menu</div>
)
};
But it didn't work.
Can someone please help me with this?

Insted of using for, you can use map.
Try this method:
const Menu = () => Array(maxColNumber / 2).fill().map((_, i) => {
const onClick = () => setBeginAfter(docLimit * i)
return (
<div className='collection'>
<div
className='collection__set'
onClick={onClick}
>
Next 2 data
</div>
</div>
)
})
// later in your code , just add the tag
<Menu />

Related

How to get a particular Index from a shuffled array

I've been working on a quiz app using NextJs and after figuring out how to work around the hydration issue in Nextjs, Now I'm stuck trying to figure out how to keep track of a value in the array.
export default function quiz({ questions }) {
const link = `https://the-trivia-api.com/api/questions`;
const [number, setNumber] = useState(0);
const [quizData, setQuizData] = useState(questions);
const [numbering, setNumbering] = useState(1);
const [initialRenderComplete, setInitialRenderComplete] = useState(false);
console.log(quizData[number].correctAnswer);
const oldArray = [...quizData[number].incorrectAnswers, quizData[number].correctAnswer];
oldArray.push(quizData[number].correctAnswer); //Here I pushed the correct answer and incorrect answer into a single array.
const newArray = [...new Set(oldArray)]; // 'newArray' is the new array containing all the four options.
const increase = () => {
setNumber(number + 1);
setNumbering(numbering + 1);
};
const decrease = () => {
setNumber(number - 1);
setNumbering(numbering - 1);
};
useEffect(() => {
setInitialRenderComplete(true);
}, []);
if (!initialRenderComplete) {
return null;
} else {
newArray.sort(() => (Math.random() > 0.5 ? 1 : -1)); // Here i shuffled the newArray, So the correct and incorrect answer is shuffled.
return (
<div className={styles.container}>
<div className={styles.profile}>
<div className={styles.mainProfile}>
<div className={styles.profilePic}>
<img src={image} alt="img" />
</div>
<h2>{state} </h2>
</div>
</div>
<div className={styles.main}>
<h2>
{numbering}. {questions[number].question}{" "}
</h2>
<div className={styles.list}>
<ul className={styles.list1}>
<div className={styles.flex}>
<h3>A. </h3>
<button>
<li>{newArray[1]} </li>
</button>
</div>
<div className={styles.flex}>
<h3>B. </h3>
<button>
{" "}
<li> {newArray[0]} </li>
</button>
</div>
</ul>
<ul className={styles.list2}>
<div className={styles.flexOption}>
<h2>C. </h2>
<button>
<li>{newArray[3]}</li>
</button>
</div>
<div className={styles.flexOption}>
<h2>D. </h2>
<button>
<li>{newArray[2]} </li>
</button>
</div>
</ul>
</div>
<div className={styles.btnStyle}>
<button onClick={decrease} className={styles.prev}>
Previous{" "}
</button>
<button onClick={increase} className={styles.next}>
Next{" "}
</button>
</div>
</div>
</div>
);
}
}
//Here is my Api Call
export const getStaticProps = async () => {
const data = await Axios.get("https://the-trivia-api.com/api/questions");
// const data = req;
const initialData = data.data;
return {
props: {
// question: data.data[indexing],
questions: initialData,
},
};
};
Now I'm stuck on how to keep track of which is the correct or incorrect Option onClick of the button, I figured I could maybe keep track of the index of the correct answer in the array but I don't think it would work and I can't figure out how to do it, Thanks in Advance.

toggle is not toggling element but it's collapsing element in navigation

I'm creating navigation for mobile devices. Everything works, but when I'm trying to open third level of navigation, it doesn't open, but collapses previous level. I dont know what to do and I'm pretty dissapointed, because it seems like easy thing and I feel like dumb. Someone have any tips please? :(
function MobileCategoriesLinks(props: MobileLinksProps) {
const { data } = useCollectionsQuery();
const { level = 0, onItemClick } = props;
const handleItemClick = (item: any) => {
if (onItemClick) {
onItemClick(item);
}
};
const linksList = data?.collections.items.map((department, index) => {
let item;
if(department.parent?.id === "1") {
const renderItem: RenderItemFn = ({ toggle, setItemRef, setContentRef }) => {
let arrow = null;
let subLinks = null;
let linkOrButton = null;
let link = "/" + department.children
if(department.children?.length) {
arrow = (
<button className="mobile-links__item-toggle" type="button" onClick={toggle}>
<ArrowRoundedDown12x7Svg className="mobile-links__item-arrow" />
</button>
);
}
let depChild = department.children?.map((name) => {
let x = name.name
let link = "/" + name.slug
let child = name
let c = child.children
if(child.children?.length) {
let thirdChild = child.children?.map((item, index) =>{
return item.name
})
let thirdChildSlug = child.children?.map((item, index) =>{
return item.slug
})
arrow = (
<button className="mobile-links__item-toggle" type="button" onClick={toggle}>
<ArrowRoundedDown12x7Svg className="mobile-links__item-arrow" />
</button>
);
subLinks = (
<div className="mobile-links__item-sub-links" ref={setContentRef}>
<ul className="mobile-links mobile--links--level--3">
<li>
<div className="mobile-links__item">
<div className="mobile-links__item-title">
{thirdChild}
</div>
</div>
</li>
</ul>
</div>
)
if(child.children?.length) {
linkOrButton = (
<AppLink
href={"/" + child.slug}
className="mobile-links__item-link"
onClick={() => handleItemClick(link)}
>
{child.name}
</AppLink>
)}
return (
<li>
<div className="mobile-links__item" ref={setItemRef}>
<div className="mobile-links__item-title">
<AppLink
href={"/" + child.slug}
className="mobile-links__item-link"
onClick={() => handleItemClick(link)}
>
{name.name}
</AppLink>
{arrow}
</div>
{subLinks}
</div>
</li>
)}
return (
<li>
<div className="mobile-links__item">
<div className="mobile-links__item-title">
{x}
</div>
</div>
</li>
);
}
)
subLinks = (
<div className="mobile-links__item-sub-links" ref={setContentRef}>
<ul className="mobile-links mobile--links--level--2">
{depChild}
</ul>
</div>
)
if(department.children) {
linkOrButton = (
<AppLink
href={"/" + department.slug}
className="mobile-links__item-link"
onClick={() => handleItemClick(link)}
>
{department.name}
</AppLink>
)} else {
linkOrButton = (
<button
type="button"
className="mobile-links__item-link"
onClick={() => handleItemClick(link)}
>
{department.name}
</button>
)
}
return (
<div className="mobile-links__item" ref={setItemRef}>
<div className="mobile-links__item-title">
{linkOrButton}
{arrow}
</div>
{subLinks}
</div>
);
};
item = <Collapse toggleClass="mobile-links__item--open" render={renderItem} />;
}
return <li key={index}>{item}</li>
}
);
return ( <ul className={`mobile-links mobile-links--level--1`}>
{linksList}
</ul>
);
}
export default withApollo(MobileCategoriesLinks)

Use State not updating as expected

Fairly new to react and trying to build a clone of The Movie Database site. I want this toggle switch to change my api call from movies to tv. It starts working after a couple clicks, but then it throws everything off and it's not displaying the correct items anyway. Not really sure what's going on here...or even why it starts working after two clicks. Anyone know whats up with this?
import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";
const Popular = ({ imageUri }) => {
// GET POPULAR MOVIES
const [popularMovies, setPopularMovies] = useState("");
const [genre, setGenre] = useState("movie");
console.log(genre);
const getPopular = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, []);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeOption = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
getPopular();
listOptions.forEach((option) => {
option.classList.remove("selected");
});
el = el.target.parentElement.parentElement;
let getStartingLeft = Math.floor(
listOptions[0].getBoundingClientRect().left
);
let getLeft = Math.floor(el.getBoundingClientRect().left);
let getWidth = Math.floor(el.getBoundingClientRect().width);
let leftPos = getLeft - getStartingLeft;
background.setAttribute(
"style",
`left: ${leftPos}px; width: ${getWidth}px`
);
el.classList.add("selected");
};
return (
<section className="container movie-list">
<div className="flex">
<div className="movie-list__header">
<h3>What's Popular</h3>
</div>
<div className="switch flex">
<div className="switch--option selected">
<h3>
<a
data-genre="movie"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
In Theaters
</a>
</h3>
<div className="background"></div>
</div>
<div className="switch--option">
<h3>
<a
data-genre="tv"
onClick={(e) => changeOption(e)}
className="switch--anchor"
>
On TV
</a>
</h3>
</div>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie, idX) => (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + "w500" + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
))}
</div>
</div>
</div>
</section>
);
};
export default Popular;
You're using the array index as your key prop when you're mapping your array.
You should use an id that is specific to the data that you're rendering.
React uses the key prop to know which items have changed since the last render.
In your case you should use the movie id in your key prop instead of the array index.
popularMovies.map((movie) => (
<div key={movie.id} className="card">
<div className="image">
<img src={imageUri + 'w500' + movie.poster_path} />
</div>
<p>{movie.title}</p>
</div>
));
Also
You're calling the api directly after setGenre. However state changes aren't immediate. So when you're making your api call you're still sending the last movie genre.
Two ways of fixing this:
You could call your function with the genre directly, and change your function so it handles this case:
getPopular('movie');
Or you could not call the function at all and add genre as a dependency of your useEffect. That way the useEffect will run each time the genre change.
useEffect(() => {
getPopular();
}, [genre]);
PS: You should consider splitting your code into more component and not interacting with the DOM directly.
To give you an idea of what it could look like, I refactored a bit, but more improvements could be made:
const Popular = ({ imageUri }) => {
const [popularMovies, setPopularMovies] = useState('');
const [genre, setGenre] = useState('movie');
const getPopular = async (movieGenre) => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${movieGenre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
};
const isMovieSelected = genre === 'movie';
const isTvSelected = genre === 'tv';
return (
<section className="container movie-list">
<div className="flex">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle onChange={changeHandler} selected={isMovieSelected}>
In Theaters
</Toggle>
<Toggle onChange={changeHandler} selected={isTvSelected}>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies.map((movie) => {
const { title, id, poster_path } = movie;
return (
<MovieItem
title={title}
imageUri={imageUri}
key={id}
poster_path={poster_path}
/>
);
})}
</div>
</div>
</div>
</section>
);
};
export default Popular;
const Toggle = (props) => {
const { children, onChange, selected } = props;
const className = selected ? 'switch--option selected' : 'switch--option';
return (
<div className={className}>
<h3>
<a
data-genre="movie"
onClick={onChange}
className="switch--anchor"
>
{children}
</a>
</h3>
<div className="background"></div>
</div>
);
};
const MovieHeader = (props) => {
const { children } = props;
return (
<div className="movie-list__header">
<h3>{children}</h3>
</div>
);
};
const MovieItem = (props) => {
const { title, imageUri, poster_path } = props;
return (
<div key={idX} className="card">
<div className="image">
<img src={imageUri + 'w500' + poster_path} />
</div>
<p>{title}</p>
</div>
);
};

React array item selection

I am trying to click on one card of a dynamically created list using map(). I want to click on one card from the array and add a class to it, while at the same time deselecting the other card that was previously clicked. How can I accomplish this? This is what I have so far:
const CardList = () => {
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} />
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id
}) => {
const [isSelected, setIsSelected] = useState(false);
const [clickId, setClickId] = useState('');
function handleClick(id) {
setIsSelected(!isSelected);
setClickId(id);
}
return (
<div
className={`card ${isSelected && clickId === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;
The parent component should control what card is clicked. Add className property to card component:
const Card = ({
//...
className,
onClick
}) => {
//...
return (
<div
className={`card ${className}`}
onClick={() => onClick(id)}
>...</div>
)
}
In parent component pass the className 'clicked' and add the onClick callback to set the selected card:
const CardList = () => {
const [isSelected, setIsSelected] = useState(null);
const handleClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} className={isSelected===id && 'clicked'} onClick ={handleClick} {...otherData} />
))}
</div>
);
};
You can do something like this.
First you don't have to set state to each card. Instead Lift state Up.
You define which card is selected in parent so you can pass that to children and add classes if current selected is matching that children.
const CardList = () => {
const [isSelected, setIsSelected] = useState();
const handleCardClick = (id) => {
setIsSelected(id);
}
return (
<div className='card-list'>
{CardData.map(({ id, ...otherData }) => (
<Card key={id} {...otherData} handleClick={handleCardClick} isSelected={isSelected}/>
))}
</div>
);
};
export default CardList;
const Card = ({
headline,
time,
views,
thumbImg,
trainerImg,
workouts,
id,
isSelected,
handleClick
}) => {
return (
<div
className={`card ${isSelected === id ? 'clicked' : ''}`}
onClick={() => handleClick(id)}
>
<div className='thumbnail-div'>
<img className='thumbnail-img' src={thumbImg} alt='video' />
{workouts ? (
<div className='workout-overlay'>
<p>{workouts}</p>
<p className='workouts'>workouts</p>
</div>
) : null}
</div>
<div className='card-info'>
<div className='card-headline'>
<p>{headline}</p>
<img src={trainerImg} alt='trainer' />
</div>
{time && views ? (
<div className='trainer-data'>
<span>
<i className='glyphicon glyphicon-time'></i>
{time}
</span>
<span>
<i className='glyphicon glyphicon-eye-open'></i>
{views}
</span>
</div>
) : null}
</div>
</div>
);
};
export default Card;

Async/await React: objects are not valid, [object promise]

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>
</>;
};

Resources