How to solve this code duplication in an elegant way? [reactjs] - reactjs

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

Related

reusing array.map() in React.js

I can't find an answer in "many similar questions", sorry I'm new to react.
I have the following code inside return of my sfc that works/renders fine:
{data && allToDos && completed === 'all' && data.map(data => {
key++;
return (
<div key={key}>
<h2>{data.title}</h2>
<p className="apart">
Nr: {data.id} <span>Status: {!data.completed && <span>Not </span>}completed</span>
</p>
<h3>User: {data.userId}</h3>
<br/><hr/><br/>
</div>
);
});}
Now, I have 3 different options for completed and in each case i want to render different set of data, so i have isolated the data.map(data => {/*code*/}) into a separate function inside the same component,like that:
const dataMap = () => {
data.map(data => {
key++;
return (
<div key={key}>
<h2>{data.title}</h2>
<p className="apart">
Nr: {data.id} <span>Status: {!data.completed && <span>Not </span>}completed</span>
</p>
<h3>User: {data.userId}</h3>
<br/><hr/><br/>
</div>
);
});
}
and then i passed that function inside return like that:
{data && allToDos && completed === 'all' && dataMap()}
and nothing is rendered, but no error messages either.
You're not returning from dataMap() function it returns undefined .
try this
const dataMap = () => {
return data.map(data => {
key++;
return (
<div key={key}>
<h2>{data.title}</h2>
<p className="apart">
Nr: {data.id} <span>Status: {!data.completed && <span>Not </span>}completed</span>
</p>
<h3>User: {data.userId}</h3>
<br/><hr/><br/>
</div>
);
});
}

Cannot define properties of undefined

I am having an issue with my skillImage components.
I am not sure where the issue is coming from.
I get error message cannot read properties of undefined : id but it has been destructured correctly.
I have put below the following components involved, SkillContext, Skill, SkillsList.
The line of code in question is const { skillCard: {id, skill} } = useContext(SkillContext);
Can someone tell me what is wrong, please?
Thanks in advance
skill.js
import Image from "next/dist/client/image";
import { FaTimes, FaRegCalendarAlt, FaSchool,FaSpinner } from "react-icons/fa";
import styles from '../../styles/Home.module.scss'
//import { FaStar } from 'react-icons/fa';
import { AiFillStar , AiOutlineStar } from 'react-icons/ai'
import { useState, useContext } from 'react';
import { CourseFilterContext } from "../contexts/CourseFilterContext";
import { SkillProvider, SkillContext } from "../contexts/SkillContext";
function Course({ courseTitle, hours, level, year }) {
return (
<>
<span className={styles.course}>
<strong>Course :</strong> {courseTitle}{" "}
</span>
<span className={styles.course}>
<strong>Hours: </strong> {hours}
</span>
<span className={styles.course}>
<strong>Level :</strong> {level}
</span>
<span className={styles.course}>
<strong>Year :</strong> {year}
</span>
</>
);
}
function Courses() {
const { courseYear } = useContext(CourseFilterContext);
const { skillCard } = useContext(SkillContext);
const courses = skillCard.courses;
const level = skillCard.level;
return (
<div className={styles.courseBox, "h-250"}>
{courses
.filter(function (course) {
return course.year === courseYear;
})
.map(function (course) {
return (
<Course {...course} level={level} key={skillCard.courses.id} />
)
})
}
</div>
);
}
function SkillImage() {
const { skillCard: {id, skill} } = useContext(SkillContext);
,
return (
<div className="speaker-img d-flex flex-row justify-content-center align-items-center h-300">
<Image
src={`/images/skill-${id}.png`}
className="contain-fit"
alt={`${skill}`}
width="300"
height="300"
/>
</div>
);
}
function SkillFavorite () {
const { skillCard, updateRecord } = useContext(SkillContext);
const [inTransition, setInTransition] = useState(false);
function doneCallBack () {
setInTransition(false);
console.log(`In SkillFavorite: doneCallBack ${new Date().getMilliseconds()}`)
}
return (
<div className={styles.icon}>
<span onClick={function () {
setInTransition(true);
updateRecord(
{
...skillCard, favorite: !skillCard.favorite,
},
doneCallBack
)
}}>
{ skillCard.favorite === false ?
<AiFillStar fill="orange"/> : <AiOutlineStar fill="yellow"/>
}{" "}
Favorite{" "}
{ inTransition === true ? (
<span><FaSpinner className="fa-spin" fill="orange"/></span>
): null}
</span>
</div>
)
}
function SkillDescription() {
const { skillCard } = useContext(SkillContext);
const { skill, description, school, } = skillCard;
return (
<div>
<h3 className="text-truncate text-justify w-200">
{skill}
</h3>
<SkillFavorite />
<p className="text-justify skill-info">{description}</p>
<div className="social d-flex flex-row mt-4 justify-content-between">
<div className="school mb-2">
{/* <FaSchool className="m-3 align-middle " fill="#000"/> */}
<h5>School</h5>
<h6>{school}</h6>
</div>
<div className="calendar mb-2">
{/* <FaRegCalendarAlt className="m-3 align-middle " fill="#000"/> */}
{/* <h5>Year</h5>
<h6>{courses[0].year}</h6> */}
</div>
</div>
</div>
);
}
function SkillAction () {
return (
<div className="row mb-2">
<button
className="btn btn-primary w-100"
onClick={() => handleEditClick()}
>Edit
</button>
<div className="row mt-2">
<FaTimes className="m-3 " fill="#ffc800" onClick={() => handleDeleteClick(idx)} />
</div>
</div>
);
}
function Skill({ skillCard, updateRecord, insertRecord, deleteRecord }) {
const { showCourses } = useContext(CourseFilterContext);
return (
<SkillProvider skillCard={skillCard} updateRecord={updateRecord} insertRecord={insertRecord} deleteRecord={deleteRecord}>
<div className="col-xs-12 col-sm-12 col-md-6 col-lg-4 col-sm-12 col-xs-12">
<div className="card card-height p-4 mt-4 mb-2 h-300">
<SkillImage />
<div className="card-body">
<SkillDescription />
{showCourses === true ?
<Courses /> : null}
</div>
<div className="card-footer">
<SkillAction />
</div>
</div>
</div>
</SkillProvider>
);
}
export default Skill;
SkillsList
import Skill from "../components/Skill";
import styles from "../../styles/Home.module.scss";
import ReactPlaceholder from "react-placeholder";
import useRequestDelay, { REQUEST_STATUS} from "../hooks/useRequestDelay";
import { data } from '../../SkillData';
import { CourseFilterContext} from "../contexts/CourseFilterContext";
import { useContext } from "react";
function SkillsList () {
const {
data: skillData,
requestStatus,
error,
updateRecord,
insertRecord,
deleteRecord
} = useRequestDelay(2000, data)
const { searchQuery, courseYear } = useContext(CourseFilterContext);
if(requestStatus === REQUEST_STATUS.FAILURE) {
return(
<div className="text-danger">
Error: <b>Loading Speaker Data Failed {error}</b>
</div>
)
}
//if (isLoading === true) return <div className="mb-3">Loading...</div>
return (
<div className={styles.container, "container"}>
<ReactPlaceholder
type="media"
rows={15}
className="skillsList-placeholder"
ready={requestStatus === REQUEST_STATUS.SUCCESS}
>
<div className="row">
{skillData
.filter(function (skillCard) {
return (
skillCard.skill.toLowerCase().includes(searchQuery)
);
})
.filter(function (skillCard) {
return skillCard.courses.find((course) => {
return course.year === courseYear;
})
})
.map(function (skillCard) {
return (
<Skill
key={skillCard.id}
skillCard={skillCard}
updateRecord={updateRecord}
insertRecord={insertRecord}
deleteRecord={deleteRecord}
/>
);
})}
</div>
</ReactPlaceholder>
</div>
);
}
export default SkillsList;
SkillContext
import { createContext } from "react";
const SkillContext = createContext();
function SkillProvider({children, skillCard, updateRecord, insertRecord, deleteRecord}) {
return (
<SkillContext.Provider
value={skillCard, updateRecord, insertRecord, deleteRecord}>
{children}
</SkillContext.Provider>
)
}
export { SkillContext, SkillProvider}

Too many re-renders. React limits (Next JS)

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

Each child in a list should have a unique key - react error

EDIT, SOLVED: Its because the outer div needs the key set on it. The most parent element must have the key set.
Each div should have a unique key as I am using the shortid library to generate one, bit lost here?!
Error:
backend.js:6 Warning: Each child in a list should have a unique "key" prop.
Check the render method of JsonFeed. See htt......-warning-keys for more information. in div (created by JsonFeed) in JsonFeed.
Render Function:
// Renders to the browser
public render(): React.ReactElement<IJsonFeedProps> {
// Grabbing objects to use from state
const { posts, isLoading} = this.state;
const { postCount } = this.props;
return (
<div className={ styles.jsonFeed }>
<div className={ styles.container }>
<p className={ styles.title }>{ escape(this.props.description)}</p>
<div className={ styles.containerDiv } >
{!isLoading ? (
posts.slice(0, postCount).map(post => {
// Variables to use
let { id, name, url, imgUrl, endDate, startDate } = post;
// Return render of posts
return (
<div>
<div key={shortid.generate()} className={ styles.post }>
<Link href={ url } className={ styles.postLink } target="_blank">
{imgUrl !== 'undefined' &&
<div className={ styles.postImageContainer }><img className={ styles.postImage } src={ imgUrl } /></div>
}
<div className={ styles.postInfo }>
<p className={ styles.postName }>
{ name.length < 40 ? name : name.substring(0, 40).trim() + '...' }
</p>
{startDate !== 'undefined' &&
<p className={ styles.postDate }><Moment format="DD/MM/YYYY">{ startDate }</Moment> - <Moment format="DD/MM/YYYY">{ endDate }</Moment></p>
}
</div>
</Link>
</div>
</div>
);
})
) : ( <Spinner className={ styles.postSpinner } label={'Loading...'} /> )
}
{ !isLoading && posts.length == 0 || postCount == null ? <p className={ styles.postSorry }>Sorry, no posts are available.</p> : null }
</div>
</div>
</div>
);
}
You need to assign a unique key to each post that you are mapping to components. E.g.:
<div id={id}>
// Renders to the browser
public render(): React.ReactElement<IJsonFeedProps> {
// Grabbing objects to use from state
const { posts, isLoading, errors } = this.state;
const { postCount } = this.props;
// If their is any errors, return an error message instead
// if (errors) {
// return <p>{errors.message}</p>;
// }
return (
<div className={ styles.jsonFeed }>
<div className={ styles.container }>
<p className={ styles.title }>{ escape(this.props.description)}</p>
<div className={ styles.containerDiv } >
{!isLoading ? (
posts.slice(0, postCount).map(post => {
// Variables to use
let { id, name, url, imgUrl, endDate, startDate } = post;
// Return render of posts
return (
<div key={id}>
<div key={shortid.generate()} className={ styles.post }>
<Link href={ url } className={ styles.postLink } target="_blank">
{imgUrl !== 'undefined' &&
<div className={ styles.postImageContainer }><img className={ styles.postImage } src={ imgUrl } /></div>
}
<div className={ styles.postInfo }>
<p className={ styles.postName }>
{ name.length < 40 ? name : name.substring(0, 40).trim() + '...' }
</p>
{startDate !== 'undefined' &&
<p className={ styles.postDate }><Moment format="DD/MM/YYYY">{ startDate }</Moment> - <Moment format="DD/MM/YYYY">{ endDate }</Moment></p>
}
</div>
</Link>
</div>
</div>
);
})
) : ( <Spinner className={ styles.postSpinner } label={'Loading...'} /> )
}
{ !isLoading && posts.length == 0 || postCount == null ? <p className={ styles.postSorry }>Sorry, no posts are available.</p> : null }
</div>
</div>
</div>
);
}

Conditional Rendering of div in ReactJS?

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
}

Resources