Account for null values when mapping an array ES6 - javascript

This is I'm mapping an array successfully..
const faculty = props.faculty;
{(faculty.science_department || []).map(science_dep => (
<div className="row">
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
))}
But what if the array is empty? How can I account for null values/empty states? How can I check within this map function? Ie: show
<p>There are no faculty members within this category</p>

But what if the array is empty? How can I account for null
values/empty states? How can I check within this map function? Ie: show
<p>There are no faculty members within this category</p>
Given those requirements I would first filter the array, then render it with the map if it contains anything, otherwise render the placeholder message:
const {faculty} = this.props;
const science_department = (faculty.science_department || []).filter(dep => !!dep);
{
science_department.length ? science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>)
:
<p>There are no faculty members within this category</p>
}
PS: What is warning.id? The key should be a field of the science_dep with a unique value.

Assuming you want to do this within JSX syntax, then you can use the ternary operator:
const faculty = props.faculty;
{
faculty.science_department.length !== 0 ?
faculty.science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
))
:
<p>There are no faculty members within this category</p>
}

{(faculty.science_department || []).length ? faculty.science_department.map(science_dep => (
<div className="row" key={warning.id}>
<p>{science_dep.name} : {science_dep.start_date}</p>
</div>
)) : (science_dep => (
<div className="row" key={warning.id}>
<p>There are no faculty members within this category</p>
</div>)()}

Related

Update the render every time the props object is updated

I am making an expense tracker. I am receiving an object of data in the income component as props. I want to render it only when the object is updated / new data is passed to the object, I want the new data is rendered along with the previous data. But every time I pass the first data, the component returns 2 empty div. How can I stop getting 2 empty div's at first render.
const Income = ({incomeData}) => {
const [totalAmount, setTotalAmount] = useState(0)
const [displayData, setDisplayData] = useState([])
useEffect(()=>{
if(incomeData.amount) setTotalAmount(totalAmount + incomeData.amount)
setDisplayData((prevData)=> [...prevData, incomeData])
}, [incomeData])
return (
<div className={style.container}>
<div className={totalAmount > 0 ? style.main : style.mainBlock}>
<h1>Total Income: {totalAmount}</h1>
{displayData.map((data, idx)=>{
const {total, type, date, amount, income, id} = data
return (
<div key={id} className={style.incomeBox}>
<div className={style.incomeDetail}>
<p>{income}</p>
<div>
<span>{amount}</span>
<span>{date}</span>
</div>
</div>
<button id={id}>
<AiFillDelete />
</button>
</div>
)
})}
</div>
</div>
)
}
export default Income
I am expecting the 2 empty data not to be displayed in the income list.
It sounds like you’re passing in empty objects for previousData and incomeData. If that’s the case then what’s rendered initially will be empty values b/c there’s no initial data.
Your options are to either pass in data on the initial render, or hide elements if data is empty. As you have a number of divs, this is just a guess at what you want to hide.
return (
<div className={style.container}>
<div className={totalAmount > 0 ? style.main : style.mainBlock}>
<h1>Total Income: {totalAmount}</h1>
{displayData[0].amount && displayData[1].amount && displayData.map((data, idx)=>{
const {total, type, date, amount, income, id} = data
return (
<div key={id} className={style.incomeBox}>
<div className={style.incomeDetail}>
<p>{income}</p>
<div>
<span>{amount}</span>
<span>{date}</span>
</div>
</div>
<button id={id}>
<AiFillDelete />
</button>
</div>
)
})}
</div>
</div>
)
The idea being that with a conditional check before the mapping of data you can either hide or display based on whether data is initially provided.

if else question mark in reactjs

I am having an error with if else question mark in react.
let skillSetStr = useSkillset(userInfo.Skills);
// In some cases the db returns the userInfo in different objects.
// For now this fixes the problem.
if (userInfo.Skills === undefined) {
skillSetStr = skillStr;
}
{skillSetStr ?
<div className="user-preview-info">
<div className="grid-item">Skillsets:</div>
<div className="grid-item user-preview-info_data">
{userInfo.Skills ?
<div>
{userInfo.Skills.map(
(skillName, index) => <div key={index} className="user-skill"> {skillName.skill} </div>
)}
</div>
: <div>Unspecified</div>}
</div>
</div> : ""}
So in if else condition it's suppose return "Unspecified" for in but instead it's return null.
Here is how my project look like:
How can I fix this error?
If it is saying that the array is undefined you can check it isn't empty before getting its length with "option chaining".
It would look like this:
userInfo?.Skills?.length > 0 this removes the need to do two checks.

React JSX for loop shows only the last value

I have seen similar questions here, but these haven't been helpful so far.
I have a component that has an array state:
eventData: []
Some logic watches for events and pushes the objects to the state array:
eventData.unshift(result.args);
this.setState({ eventData });;
unshift() here is used to push the new elements to the top of the array.
What I want to achieve is rendering the content of the state array. I have written a conditional that checks for a certain state, and based on that state decides what to output.
let allRecords;
if (this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
} else if (!this.state.allRecords) {
for (let i = 0; i < this.state.eventData.length; i++) {
if (this.state.account === this.state.eventData[i].paramOne) {
(i => {
allRecords = (
<div className="event-result-table-container">
<div className="result-cell">
{this.state.eventData[i].paramOne}
</div>
<div className="result-cell">
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}}
</div>
<div className="result-cell">
{this.state.eventData[i].paramThree.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFour.c[0]}
</div>
<div className="result-cell">
{this.state.eventData[i].paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{this.state.eventData[i].paramSix.c[0]}
</div>
</div>
);
}).call(this, i);
}
}
}
Problems that I have with this piece of code:
The code always renders the very last value of eventData state object.
I would like to limit the rendered elements to always show not more than 20 objects (the last 20 records of the state array).
paramTwo is a bool, and according to its value I expect to see either Win or Loose, but the field is empty (I get the bool value via the console.log, so I know the value is there)
Is this even the most effective way of achieving the needed? I was also thinking of mapping through the elements, but decided to stick with a for loop instead.
I would appreciate your help with this.
A few things :
First, as the comments above already pointed out, changing state without using setState goes against the way React works, the simplest solution to fix this would be to do the following :
this.setState(prevState => ({
eventData: [...prevState.eventData, result.args]
}));
The problem with your code here. Is that the arrow function was never called :
{() => {
if (this.state.eventData[i].paramTwo) {
<span>Win</span>;
} else {
<span>Loose</span>;
}
}
}
This function can be reduced to the following (after applying the deconstructing seen in the below code) :
<span>{paramTwo ? 'Win' : 'Lose'}</span>
Next up, removing repetitions in your function by mapping it. By setting conditions at the right place and using ternaries, you can reduce your code to the following and directly include it the the JSX part of your render function :
render(){
return(
<div> //Could also be a fragment or anything
{this.state.allRecords || this.state.account === this.state.eventData[i].paramOne &&
this.state.eventData.map(({ paramOne, paramTwo, paramThree, paramFour, paramFive, paramSix }, i) =>
<div className="event-result-table-container" key={i}> //Don't forget the key like I just did before editing
<div className="result-cell">
{paramOne}
</div>
<div className="result-cell">
<span>{paramTwo ? 'Win' : 'Lose'}</span>
</div>
<div className="result-cell">
{paramThree.c[0]}
</div>
<div className="result-cell">
{paramFour.c[0]}
</div>
<div className="result-cell">
{paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{paramSix.c[0]}
</div>
</div>
)
}
</div>
)
}
Finally, to only get the 20 first elements of your array, use slice :
this.state.eventData.slice(0, 20).map(/* CODE ABOVE */)
EDIT :
Sorry, I made a mistake when understanding the condition you used in your rendering, here is the fixed version of the beginning of the code :
{this.state.allRecords &&
this.state.eventData.filter(data => this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
Here, we are using filter to only use your array elements respecting a given condition.
EDIT 2 :
I just made another mistake, hopefully the last one. This should ahve the correct logic :
this.state.eventData.filter(data => this.state.allRecords || this.state.account === data.paramOne).slice(0, 20).map(/* CODE ABOVE */)
If this.state.allRecords is defined, it takes everything, and if not, it checks your condition.
I cleaned up and refactored your code a bit. I wrote a common function for the repetitive logic and passing the looped object to the common function to render it.
Use Map instead of forloops. You really need to check this this.state.account === this.state.eventObj.paramOne statement. This could be the reason why you see only one item on screen.
Please share some dummy data and the logic behind unshift part(never do it directly on state object), we'll fix it.
getRecord = (eventObj) => (
<React.Fragment>
<div className="result-cell">
{eventObj.paramOne}
</div>
<div className="result-cell">
{eventObj.paramTwo ? <span>Win</span> : <span>Loose</span>}
</div>
<div className="result-cell">
{eventObj.paramThree.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFour.c[0]}
</div>
<div className="result-cell">
{eventObj.paramFive.c[0] / 10000}
</div>
<div className="result-cell-last">
{eventObj.paramSix.c[0]}
</div>
</React.Fragment>
)
render() {
let allRecords;
if (this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => this.getRecord(eventObj)}</div>;
} else if (!this.state.allRecords) {
allRecords = <div>{this.state.eventData.map(eventObj => {
if (this.state.account === this.state.eventObj.paramOne) {
return this.getRecord(eventObj);
}
return null;
})}</div>;
}
return (<div className="event-result-table-container">{allRecords}</div>);
}

How to Programmatically Assign and Get Refs in React

I was wondering if it is possible to programmatically assign and get refs in React. Suppose I wanted to go through a loop creating elements, giving them refs that consist of a name + an index. I know I can assign them like that using strings. However, the only way I know how to access refs consists of using this.refs.refname which, as far as I know, precludes me from doing something like this.refs.{refname + index}. Is there any way I can do something like this? The source code below should hopefully give you an idea of what I'm asking.
render = () => (<div className='row signature-group'>
<div className='col-md-1 col-xs-2'>
<b>{this.props.signerDescription}</b>
</div>
<div className='col-md-4 col-xs-7'>
{this.props.signers.map((signer, index) => <div className='text-with-line' key={index} ref={"sig" + index}>{signer}</div>)}
</div>
<div className='col-md-2 col-xs-3'>
{this.props.signers.map((signer, index) => {
return (index > 0 && this/*.refs.sig+index.value == whateverValue*/) ?
(<div className='text-with-line-long-name' key={index}>Date</div>) :
(<div className='text-with-line' key={index}>Date</div>);
})}
</div>
</div>)
Also, I've heard that using strings to assign refs is considered legacy. Is there any way to programmatically assign refs in a more up-to-date fashion?
Yes, you can use a ref callback to achieve this. The function passed as the ref attribute value will be passed the DOM node of the component once, after it is rendered:
applyRef = (index, ref) => {
this[`sig${index}`] = ref;
};
render = () => (
<div className="row signature-group">
<div className="col-md-1 col-xs-2">
<b>{this.props.signerDescription}</b>
</div>
<div className="col-md-4 col-xs-7">
{this.props.signers.map((signer, index) => (
<div className="text-with-line" key={index} ref={this.applyRef.bind(this, index)}>
{signer}
</div>
))}
</div>
<div className="col-md-2 col-xs-3">
{this.props.signers.map((signer, index) => {
return index > 0 && this[`sig${index}`].clientHeight > 0 ? (
<div className="text-with-line-long-name" key={index}>
Date
</div>
) : (
<div className="text-with-line" key={index}>
Date
</div>
);
})}
</div>
</div>
);
You can use bracket notation to create a new property on your class component (this) and then you access it with the same name (this.sig1, this.sig2).
String refs are deprecated and should no longer be used. Your refs are now applied directly to the component instance (this).

how to run different array on my map function for my nav component

Pretty new at coding so sorry if my question looks ridiculous...
I am building a menu on my website which is divided in several categories.
In this example, we have Theory and Video categories ( Video only 1 level and Theory is going deeper on 2 levels).
The code below is working for only 1 category at time (thats why the comments).
I would like to ask you how to build a more generic function that can run whatever the array is (for the map function) and avoid this: "Cannot read property 'map' of undefined".
render() {
const theories = this.props.menuTheory;
const videos = this.props.menuVideos;
// const menuTheory = theories.map((theory, i) => (
//
// <div className="nav__category" key={i} onClick={() => this.onSelect(theory.category)}>
//
// <div className={this.state.isSelected === theory.category
// ? "nav__category__dropdown nav__category__dropdown--isSelected"
// : "nav__category__dropdown"}>
// <span className="nav__category__text">{theory.category}</span>
// <span className="checked"><img src={'../static/icons/nav__check.svg'}/></span>
// </div>
// <ul className={this.state.isExpanded === theory.category
// ? "nav__chapterBox"
// : "nav__chapterBox nav__chapterBox--isNotExpanded"}>
// {theory.chapters && theory.chapters.map((chapter, i) => <NavChapter key={i} id={chapter.objectId} title={chapter.name} onClick={() => this.onSelect1(chapter.objectId)}/>)}
// </ul>
// </div>
// ))
const menuVideo = videos.map((video, i) => (
<div className="nav__category" key={i} onClick={() => this.onSelect(video.category)}>
<div className={this.state.isSelected === video.category
? "nav__category__dropdown nav__category__dropdown--isSelected"
: "nav__category__dropdown"}>
<span className="nav__category__text">{video.category}</span>
<span className="checked"><img src={'../static/icons/nav__check.svg'}/></span>
</div>
</div>
))
return (
<nav className="nav__categoryBox">
{/* {menuTheory} */}
{menuVideo}
</nav>
)
}
Thanks.
Okay, so it sounds like you're looking for a way to do conditional rendering in React. What you need to do is add some state to the component. I've added showVideos as a bool (true|false). Use showVideos in the render method to determine which menu to show. You still create the menus using map, but in the return block, check this.state.showVideos to determine which content to return. To get this fully working, you'll also need to add a button that calls toggleMenu onClick that will update your state and switch which menu is being shown.
toggleMenu(){
this.setState({
showVideos: !this.state.showVideos
});
}
render() {
const theories = this.props.menuTheory;
const videos = this.props.menuVideos;
const menuTheory = theories.map((theory, i) => (
<div className="nav__category" key={i} onClick={() => this.onSelect(theory.category)}>
<div className={this.state.isSelected === theory.category ? "nav__category__dropdown nav__category__dropdown--isSelected" : "nav__category__dropdown"}>
<span className="nav__category__text">{theory.category}</span>
<span className="checked"><img src={'../static/icons/nav__check.svg'}/></span>
</div>
<ul className={this.state.isExpanded === theory.category ? "nav__chapterBox" : "nav__chapterBox nav__chapterBox--isNotExpanded"}>
{theory.chapters && theory.chapters.map((chapter, i) => <NavChapter key={i} id={chapter.objectId} title={chapter.name} onClick={() => this.onSelect1(chapter.objectId)}/>)}
</ul>
</div>
))
const menuVideo = videos.map((video, i) => (
<div className="nav__category" key={i} onClick={() => this.onSelect(video.category)}>
<div className={this.state.isSelected === video.category ? "nav__category__dropdown nav__category__dropdown--isSelected" : "nav__category__dropdown"}>
<span className="nav__category__text">{video.category}</span>
<span className="checked"><img src={'../static/icons/nav__check.svg'}/></span>
</div>
</div>
))
return (
<nav className="nav__categoryBox">
<button onClick={this.toggleMenu}>Toggle Menu</button>
{this.state.showVideos ? menuVideo : menuTheory}
</nav>
)
}

Categories