React JSX for loop shows only the last value - javascript

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

Related

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.

How to render conditional with classNames with Reactjs

I am trying to use classNames to replace the conditional below in one line.
My problem is that i am not sure what is the right way to write the code because of the div etc...
I have the codes below :
conditional
const { pageTitle, removeTitle = false } = props; # destructuring
let result;
if(removeTitle){
result = <div className="header-without-title">{pageTitle}</div>;
} else {
result = <div className="header-with-title">{pageTitle}</div>;
}
return (<div className="result-title">{result});
};
If the div did not exist, i could write something like this...
const result = classNames({"header-without-title": removeTitle, "header-title": !removeTitle});
But i am not sure now that I have the div , I would appreciate some help here...
A solution outside of JSX would be very helpful
return (
<div className="result-title">
<div className={`header-${removeTitle ? 'without-title' : 'with-title'}`}>{pageTitle}</div>
</div>
);
or with use https://github.com/JedWatson/classnames
return (
<div className="result-title">
<div className={classNames({ 'header-without-title': removeTitle, 'header-with-title': !removeTitle })}>
{pageTitle}
</div>
</div>
);
EDIT:
A solution outside of JSX
const result = (
<div className={classNames({ 'header-without-title': removeTitle, 'header-with-title': !removeTitle })}>
{pageTitle}
</div>
);
return (
<div className="result-title">
{result}
</div>
);
You can just inline classNames function
const { pageTitle, removeTitle = false } = props;
const result = classNames({"header-without-title": removeTitle, "header-title": !removeTitle});
return (
<div className="result-title">
<div className={result}>
{pageTitle}
</div>
</div>);
);
There are several answers to this. Depends of each case
Ternary between two classes in React:
<div className={`header-${removeTitle ? 'without-title' : 'with-title'}`}>
Ternary between class or null in React Javascript:
<div className={removeTitle ? 'without-title' : null}>
Ternary between render class or not in React Javascript and Typescript:
<div className={...(removeTitle ? { className: 'without-title' } : {})}>

react throws type error for undefined object before ternary can check if object is undefined

LONG STORY SHORT: I would like for it to load the object in the nested array IF it is not equal to undefined but react throws typeError
I have this component that takes props from a parent component. Essentially I have an array that contains chat information and when I try to access it in this child component I get some very strange behaviour.
for example if I console log(props.conversations) I get my array which looks like this: conversations[{host, members[{ username }], log[{ author, content, timestamp }]}].
if I console log (props.conversations[0]) ill get the first object in that array. But if I console log (props.conversations[0].log) I get undefined. And thats fine because at the start the state will not be defined or contain anything, so I put a ternary operator as shown below in the code props.conversations[props.index].log[0] == null ?
but all i get is TypeError: Cannot read property 'log' of undefined at the ternary function.
Maybe I am not understanding this correctly or maybe it how react functions?
Again I would like for it to load the object in the nested array IF it is not equal to undefined.
Highly appreciate the help. The most important part is the friends component. I only show the other ones to show the state being passed down.
function Friends(props) {
console.log(props.conversations[props.index]);
return (
<div className="friend">
<img className="friendavatar" src={require("./static/bobby.jpg")}></img>
<div className="friendname">{props.username}</div>
<span className="iswatchingtitle"> is watching <strong>{props.watching}</strong></span>
<div className="friendchat" onClick={props.togglechat}>
{props.conversations[props.index].log[0] == null ?
<div>undefined</div>
:
<div>defined!</div>
}
</div>
</div>
)
}
social component
function Social(props) {
return (
<div>
<div className="userquickdash row">
<div className="usernamedash">{props.username}</div>
<div className="logout"><a href="/users/logout" onClick={props.fetchlogout}>logout</a></div>
</div>
<div>
<form className="search-form-flex" method="GET" action="/search">
<input className="user-search" id="search" type="search" placeholder=" Search users..." name="usersearch"></input>
</form>
</div>
<div className='friendchatcontainer' refs='friendchatcontainer'>
{/* Append friends from social bar state (props.friends). For each friend return appropriate object info to build Friends div using Friends(props) function above. */}
{props.friends.map(function(friend, index) {
// Shortens length of video title if length of string is over 48.
let friendWatching = function friendWatchingLengthSubstring() {
if (friend.watching.length > 57) {
let friendWatching = friend.watching.substring(0, 54) + '...';
return friendWatching;
} else {
friendWatching = friend.watching;
return friendWatching;
}
};
return (
<Friends username={friend.username}
watching={friendWatching()}
key={index}
index={index}
togglechat={props.togglechat}
conversations={props.conversations}
/>
)
})}
</div>
</div>
)
}
socialbar component
class Socialbar extends Component {
constructor(props) {
super(props);
this.state = { isLoggedIn: (cookies.get('loggedIn')),
sidebarximgSrc: sidebarcloseimg,
sidebarStatus: 'open',
username: cookies.get('loggedIn'),
friends: friends,
users: {},
conversations: [],
};
}
// function to run when mongodb gets information that state has changed.
// test if the current state is equal to new object array.
// then do something.
appendFriends() {
}
componentDidMount() {
if (this.state.sidebarStatus === 'open') {
document.getElementsByClassName('maindash')[0].classList.add('maindashwide');
this.openSideBar();
} else {
document.getElementsByClassName('maindash')[0].classList.remove('maindashwide');
this.closeSideBar();
}
// check for user logged in cookie, if true fetch users.
if (this.state.isLoggedIn) {
this.fetchUsers();
}
this.getFriendConversations();
};
getFriendConversations() {
// build loop function that updates state for conversations based on length of friends array in state.
var conversationsArray = this.state.conversations;
for (var i = 0; i < friends.length; i++) {
console.log(aconversationbetweenfriends[i]);
conversationsArray.push(aconversationbetweenfriends[i]);
}
this.setState({conversations: conversationsArray});
}
render() {
let sidebar;
const isLoggedIn = this.state.isLoggedIn;
if (!isLoggedIn) {
sidebar = <Login />
} else {
sidebar = <Social username={this.state.username} friends={this.state.friends} fetchlogout={this.fetchlogout} togglechat={this.togglechat} conversations={this.state.conversations} />
}
return (
<div>
<div className="sidebar sidebar-open" ref="sidebar">
<div className="sidebarcontainer">
{sidebar}
</div>
</div>
<div className="sidebarx sidebarxopen" ref="sidebarx" onClick={this.toggleSideBar}>
<img className="sidebaropenimg" src={this.state.sidebarximgSrc} ref='sidebarximg'></img>
</div>
</div>
);
}
};
It is not a good idea to access the element directly before validation.
Use something like this:
props.conversations[props.index] && props.conversations[props.index].log[0]
Tip: User object destructuring and default props.
You need to compare for undefined like this :
{props.conversations[props.index].log[0] === undefined ?
<div>undefined</div>
:
<div>defined!</div>
}
Also, You can go to below link for sandbox running example.
Sandbox link for example to show how you should check for undefined
Hi first of all check your {props.index} print this value. if it is proper then try this out.
{
props.conversations[props.index] ?
props.conversations[props.index].log[0] ? <div>defined!</div>:<div>Undefined</div>
:
<div>Undefined</div>
}
This will check if props.conversations[props.index] is defined then and then only try to process props.conversations[props.index].log[0]. So you will not get TypeError: Cannot read property 'log' of undefined at the ternary function.

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).

React ternary operator with array.map return

In my react render function, I am using array.map to return some JSX code in an array and then rendering it. This doesn't seem to work, I read a few questions here that suggested using return statement inside if/else block but that won't work in my case. I want to check whether round and duration are set on each array element and only then pass it in the JSX code.Could someone tell me a different approach please.
render() {
var interviewProcessMapped = interviewProcess.map((item, index) => {
return
{item.round ?
<div className="row title">
<div className="__section--left"><span className="section-title">Round {index + 1}</span></div>
<div className="__section--right">
<h3>{item.round}</h3>
</div>
</div>
: null
}
{
item.durationHours > 0 || item.durationMinutes > 0 ?
<div className="row">
<div className="__section--left">Duration</div>
<div className="__section--right border">
{item.durationHours > 0 ? <span>{item.durationHours} Hours</span> : null} {item.durationMinutes > 0 ? <span>{item.durationMinutes} Minutes</span> : null}
</div>
</div>
: null
}
});
return <div>{interviewProcessMapped}</div>
}
{ not required here:
return {item.round ?
If you use it that means you are returning an object.
Another issue is alone return means return; (automatic semicolon insertion) so either put the condition in same line or use ().
Write it like this:
render() {
let a, b;
var interviewProcessMapped = interviewProcess.map((item, index) => {
a = item.round ?
<div className="row title">
....
</div>
: null;
b = (item.durationHours > 0 || item.durationMinutes > 0) ?
<div className="row">
....
</div>
: null;
if(!a || !b)
return a || b;
return [a, b];
});
return (....)
}
You should probably use a combination of Array.prototype.map() and Array.prototype.filter()
No need for if at all
Here the pseudo code:
interviewProcess.filter(() => {
// return only if round and duration set
}).map(() => {
// transform the filtered list
});

Categories