I'm a newbie to ReactJS. I want to insert a condition in my code below so that when noPeopleText.length > 0, then only should the "no-people-row" div render, otherwise, I do not want this div rendering to the DOM if noPeopleText is an empty string or undefined.
What's the best way do add in a conditional for this?
const peopleMember = (props) => {
const { people, noPeopleText, title } = props;
const hasPeople = Boolean(people && people.length);
const peopleGroup = _.groupBy(people, (person, i) =>
Math.floor(i / 2)
);
return (
<div>
{ hasPeople &&
<SectionHeader
title={title}
/>
}
{ (hasPeople || noPeopleText) &&
<div className="c-team-members">
<div className="container">
{ hasPeople ? _.map(peopleMemberGroups, (members, i) => (
<div className="row" key={i}>
{members && members.map((member, j) => (
<TeamMember
key={j}
name={member.name}
/>
))
}
</div>
)) : //If noPeopleText.length > 0, render div below
<div className="row no-people-row">
<div className="col-xs-12" dangerouslySetInnerHTML={{__html: noPeopleText}} />
</div>
}
</div>
</div>
}
</div>
);
};
You already have conditional rendering in your code. For example:
{ hasPeople &&
<SectionHeader title={title} />
}
This will only render the component SectionHeader if hasPeople evaluates to true. If hasPeople evaluates to false then the whole expression would evaluate to false regardless of the second part of the &&. Thus it is never executed (rendered).
So do you want something like this?
const peopleMember = (props) => {
const { people, noPeopleText, title } = props;
const hasPeople = Boolean(people && people.length);
const peopleGroup = _.groupBy(people, (person, i) =>
Math.floor(i / 2)
);
return (
<div>
{ hasPeople &&
<SectionHeader
title={title}
/>
}
{ (hasPeople || noPeopleText) &&
<div className="c-team-members">
<div className="container">
{ hasPeople ? _.map(peopleMemberGroups, (members, i) => (
<div className="row" key={i}>
{members && members.map((member, j) => (
<TeamMember
key={j}
name={member.name}
/>
))
}
</div>
)) : (noPeopleText.length > 0) &&
<div className="row no-people-row">
<div className="col-xs-12" dangerouslySetInnerHTML={{__html: noPeopleText}} />
</div>
}
</div>
</div>
}
</div>
);
};
I think you can just use a nested ternary operator:
{ hasPeople
? //mapping
: noPeopleText.length > 0
? <div className="row no-people-row">
<div className="col-xs-12" dangerouslySetInnerHTML={{__html: noPeopleText}} />
</div>
: null
}
Related
How would you simplify this small react component.
I'm not happy with repeating the html elements but I got no clean way to do it.
The reason it's not so obvious for me is because there is a small logic in the latter case that needs to be dealth with.
const RemainingSessionTime = (props: RemainingSessionTimeProps) => {
const { model, showTitle } = props;
if (model.remainingTime == -1) {
return (
<div className="gadget longContent remainingTime">
{showTitle && <div className="title">{title}</div>}
<div className="value">
<span>-</span>
</div>
</div>
);
}
const isCalculated = model.valueType === ValueTypes.CALCULATED;
return (
<div className="gadget longContent remainingTime">
{props.showTitle && <div className="title">{title}</div>}
<div className="value">
{isCalculated && <span>~</span>}
<span>{t}</span>
<span>{model.remainingTime < 3600 ? "m" : "h"}</span>
</div>
</div>
);
}
you can encapsulate the logic into another component like:
return (
<div className="gadget longContent remainingTime">
{showTitle && <div className="title">{title}</div>}
<div className="value">
<ShowTime remainingTime={model.remainingTime} isCalculated={false} t='t' />
</div>
</div>
);
The new ShowTime component:
function ShowTime({ remainingTime, isCalculated, t }) {
return isCalculated && remainingTime === -1 ? (
<span>~</span>
) : (
<span>
{t}
{remainingTime < 3600 ? "m" : "h"}
</span>
);
}
const RemainingSessionTime = (props: RemainingSessionTimeProps) => {
const { model, showTitle } = props;
if (model.remainingTime == -1) {
return (
<div className="gadget longContent remainingTime">
{showTitle && <div className="title">{title}</div>}
<div className="value">
<span>-</span>
</div>
</div>
);
}
const isCalculated = model.valueType === ValueTypes.CALCULATED;
return (
<div className="gadget longContent remainingTime">
{props.showTitle && <div className="title">{title}</div>}
<div className="value">
{isCalculated && <span>~</span>}
<span>{t}</span>
<span>{model.remainingTime < 3600 ? "m" : "h"}</span>
</div>
</div>
);
}
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've been getting a "Each child in a list should have a unique key prop" error. I've read up on the documentation and put keys in, but no luck. Not sure what I'm misunderstanding!
blogPosts is imported from a context.
The ids being called as keys are unique (POSTID1, POSTID2, ...etc).
Got rid of some classnames for better readability.
const topicPosts = blogPosts.filter((post)=>post.topic.toLowerCase()===topic);
const handleLoadMore = () => {
const newIndex = articleIndex + 5 > topicPosts.length ? topicPosts.length : articleIndex + 5;
setArticleIndex(newIndex);
}
return (
<section onClick={handleOutsideClick}>
<div>
<section>
<h1>{topic}</h1>
{
topicsData.map((topicData)=> topicData.name.toLowerCase()===topic && <p key={topicData.id}>{topicData.intro}</p>)
}
</section>
<div>
<section>
<div>
{
topicPosts.slice(0,3).map((post)=>{
const { id } = post;
return (
<TopicPagePost key={id} {...post}/>
)
})
}
</div>
<div className={topicPosts.length>3 ? `subpage-articles` : `display-none`}>
{
topicPosts.slice(3,articleIndex).map((post, index)=>{
const { id } = post;
return (
<>
<TopicPagePost key={id} {...post}/>
{
index%4===0 && <div key={index} className="subpage-main-ad">
Ad placement here
</div>
}
</>
)
})
}
</div>
<button type="button" onClick={handleLoadMore}>Load more articles</button>
</section>
<aside>
<SubscribeBlock/>
<div>
Ad placement here
</div>
</aside>
</div>
</div>
</section>
)
That is because you need to add a key to the first element returned from the map, in this case <>.
To do that, you need to replace <> with a <Fragment> instead, so you can add a key directly to it:
import React, { Fragment } from "react"
{topicPosts.slice(3,articleIndex).map((post, index) => {
const { id } = post;
return (
<Fragment key={id}>
<TopicPagePost {...post}/>
{index%4===0 && (
<div className="subpage-main-ad">
Ad placement here
</div>
)}
</Fragment>
)
})}
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>
))
}
</>
);
};
I'm having some issues with child re-rendering, I pass methods to children to see if a button should be displayed or not but when the state of the parent changes, the children are not re-rendered.
I tried with the disabled attribute for the button but didn't work either.
Here's my code (I removed unnecessary part):
function Cards(props) {
const isCardInDeck = (translationKey) => {
return props.deck.some(
(card) => !!card && card.translationKey === translationKey
);
};
const addToDeck = (card) => {
if (!isCardInDeck(card.translationKey) && !!card) {
props.deck.push(card);
}
};
const removeFromDeck = (card) => {
if (isCardInDeck(card.translationKey) && !!card) {
var index = props.deck.findIndex(
(c) => c.translationKey === card.translationKey
);
props.deck.splice(index, 1);
}
};
return (
<div className="cardsContent">
<div className="cards">
{cardList.length > 0 ? (
cardList.map((item, index) => {
return (
<Card key={index} card={item} addToDeckDisabled={isCardInDeck(item.translationKey)} addToDeckClick={addToDeck} removeFromDeckClick={removeFromDeck} />
);
})
) : (
<span>
<FormattedMessage id="app.cards.label.no.card.found" defaultMessage="No card found with filter."/>
</span>
)}
</div>
</div>
);
}
function Card(props) {
const toggleShowDescription = () => {
if (!showDescription) {
setShowDescription(!showDescription);
}
};
return (
<div onClick={toggleShowDescription} onBlur={toggleShowDescription} >
<img src={"../images/cards/" + props.card.image} alt={props.card.image + " not found"} />
{showDescription ? (
<div className="customCardDetail">
<div className="cardName"></div>
<div className="cardType">
{props.addToDeckDisabled ? (
<Button onClick={() => { props.removeFromDeckClick(props.card);}} startIcon={<RemoveIcon />}>
Remove from deck
</Button>
) : (
<Button onClick={() => { props.addToDeckClick(props.card); }} startIcon={<AddIcon />}>
Add to deck
</Button>
)}
</div>
<div className="cardDescription">
<span>
<FormattedMessage id={props.card.description} defaultMessage={props.card.description} />
</span>
</div>
</div>
) : (
""
)}
</div>
);
}
You code does not update state. Cards mutates the props that it is receiving.
To use state in a functional component in React you should use the useState hook.
Cards would then look something like this:
function Cards(props) {
const [deck, setDeck] = useState(props.initialDeck)
const isCardInDeck = (translationKey) => {
return deck.some(
(card) => !!card && card.translationKey === translationKey
);
};
const addToDeck = (card) => {
if (!isCardInDeck(card.translationKey) && !!card) {
setDeck([...deck, card])
}
};
const removeFromDeck = (card) => {
if (isCardInDeck(card.translationKey) && !!card) {
setDeck(deck.filter(deckItem => deckItem.translationKey !== card.translationKey))
}
};
return (
<div className="cardsContent">
<div className="cards">
{cardList.length > 0 ? (
cardList.map((item, index) => {
return (
<Card key={index} card={item} addToDeckDisabled={isCardInDeck(item.translationKey)} addToDeckClick={addToDeck} removeFromDeckClick={removeFromDeck} />
);
})
) : (
<span>
<FormattedMessage id="app.cards.label.no.card.found" defaultMessage="No card found with filter."/>
</span>
)}
</div>
</div>
);
}