Why does sort not work in React component - javascript

I have this array of rows that I try to sort alphabetically with the sortByTitle() function. The sort method I am pretty sure is OK but there must be something with where I am calling it that makes it not work and it does not mutate the array at all. It may be because of the lifecycle of react.
The sort on getRows() works perfectly.
getHistory() {
if (this.state.historyUser && this.state.historyUser.length) {
return this.state.historyUser.map(x => {
x.state = x.finishDate ? 'closed' : 'open';
return x;
});
} else {
return [];
}
}
getRows() {
let rows = this.state.rows || this.getHistory();
rows.sort((a, b) => b.creationDate - a.creationDate)
return rows.map((x, j) => {
return (
<div key={j} className={`testTable__row testTable__test testTable__test--${x.state}`}>
{this.allTypes.map((type, i) => {
let value = type.className !== 'checks' ? x[type.prop] : x.checked;
if (type.className === 'name') {
value = (
<a href={`/test/${x._id}#1`}>{x[type.prop]}</a>
);
}
return (
<CellUser
key={i}
id={x._id}
value={value}
className={type.className}
changeChecked={this.changeChecked}
isSimulacro={x.isSimulacro}
score={x.scoreProMIR}
/>
);
})}
</div>
);
});
}
handleInputChange(e) {
let rows = this.state.historyUser;
const selector = e.target.getAttribute("label")
rows = rows.filter(elm => elm[selector].toLowerCase().includes(e.target.value.toLowerCase()));
this.setState({ inputValue: e.target.value, rows: rows});
}
sortByTitle() {
let rows = this.state.rows || this.getHistory();
rows.sort((a, b) => a.title.localeCompare(b.title));
this.setState({ rows: row });
}
render() {
return (
<div style={{ height: '100%', width: '100%' }}>
<div className="testTable">
<div className="testTable__row testTable__header">
<div className="testTable__column testTable__column--name">
Nombre
<input type="text" label="title" onChange={this.handleInputChange} />
<button onClick={this.sortByTitle}> Ordenar </button>
</div>
<div className="testTable__column testTable__column--score"> Nota </div>
<div className="testTable__column testTable__column--type"> Tipo </div>
<div className="testTable__column testTable__column--date"> Fecha </div>
<div className="testTable__column testTable__column--state"> Estado </div>
<div className="testTable__column testTable__column--checks">
<label>
Abiertos <Checkbox selected={this.state.checkOpen} onClick={this.allOpened} />
</label>
<label>
Cerrados <Checkbox selected={this.state.checkClose} onClick={this.allClosed} />
</label>
<label>
Todos <Checkbox selected={this.state.selectedAllRows} onClick={this.allRows} />
</label>
</div>
</div>
<div className="testTable__body">
<Scrollbars {...scrollbarsProps()}>{this.getRows()}</Scrollbars>
</div>
<div
className={`testTable__row testTable__footer${
this.state.btnClose || this.state.btnReset || this.state.btnReopen ? ' active' : ''
}`}
>
<ReactCSSTransitionGroup
component="div"
transitionName="topBottom"
transitionEnterTimeout={0}
transitionLeaveTimeout={0}
>
{this.state.btnClose ? (
<button className="button button--close" onClick={this.requestAction} name="close">
Cerrar seleccionados
</button>
) : null}
{this.state.btnReset ? (
<button className="button button--reset" onClick={this.requestAction} name="reset">
Resetear seleccionados
</button>
) : null}
{this.state.btnReopen ? (
<button className="button button--open" onClick={this.requestAction} name="open">
Reabrir seleccionados
</button>
) : null}
{this.state.btnAddToStats ? (
<button className="button button--add" onClick={this.requestAction} name="add">
Añadir a estadísticas
</button>
) : null}
</ReactCSSTransitionGroup>
</div>
</div>
<ReactCSSTransitionGroup
component="div"
transitionName="topBottom"
className={`superCoverMsg${this.state.confirmAction ? '' : ' none'}`}
transitionEnterTimeout={0}
transitionLeaveTimeout={0}
>
{this.state.confirmAction ? (
<div className="coverMsg confirmPopUp" key="0">
<p>{this.state.textAction}</p>
<div className="coverLabelInput coverLabelInput__botones columnWidth">
<ul className="cien">
<li className="cincuenta cancelar">
<a onClick={this.removeConfirmAction} href="#" title="Cancelar">
Cancelar
</a>
</li>
<li className="cincuenta aceptar">
<a onClick={this.aceptAction} href="#" title="Aceptar">
Aceptar
</a>
</li>
</ul>
</div>
</div>
) : null}
</ReactCSSTransitionGroup>
</div>
);
}
}

It looks like your setting rows equal to row, which I don't see defined. Maybe change this
this.setState({ rows: row });
to
this.setState({ rows: rows });
Also I think the react-friendly way to modify an array from state would be use a spread operator so as not to mutate the state object directly, like so:
let rows = [...this.state.rows] || this.getHistory();

sortByTitle() {
let rows = this.state.rows || this.getHistory();
rows.sort((a, b) => a.title.localeCompare(b.title));
this.setState({ rows: row });
}
You are mutating the state. instead copy the content into separate new array, sort it and use that new variable in .setState.

Related

I have input a unique "key" prop but still got error asking me to input it in React

so this is my render, it render basically a ui where a username and their email. it also have the option to add new user but when I run the code in my local host, i get this error in my console: Warning: Each child in a list should have a unique "key" prop.
render() {
return (
<div className="App">
{this.state.users.map((user) => {
return (
<React.Fragment>
<div key={user._id} className="box">
<h3>{user.name}</h3>
<h4>{user.email}</h4>
<button
onClick={() => {
this.beginEdit(user);
}}
>
Update
</button>
<button
onClick={() => {
this.deleteUser(user);
}}
>
Delete
</button>
</div>
</React.Fragment>
);
})}
{this.renderAddUser()}
</div>
);
}
}
this is my AddRenderUser
// render new User
renderAddUser() {
return (
<React.Fragment >
<input
type="text"
placeholder="User name"
value={this.state.newUserName}
onChange={this.updateFormField}
name="newUserName"
/>
<input
type="text"
placeholder="User email"
value={this.state.newUserEmail}
onChange={this.updateFormField}
name="newUserEmail"
/>
<button onClick={this.addUser}>Add</button>
</React.Fragment>
);
}
Although I did put the keys props in my div, so not sure how to correct this error
Your key should be in your top-most element
render() {
return (
<div className="App">
{this.state.users.map((user) => {
return (
<React.Fragment key={user._id}>
<div className="box">
<h3>{user.name}</h3>
<h4>{user.email}</h4>
<button
onClick={() => {
this.beginEdit(user);
}}
>
Update
</button>
<button
onClick={() => {
this.deleteUser(user);
}}
>
Delete
</button>
</div>
</React.Fragment>
);
})}
{this.renderAddUser()}
</div>
);
}
}
You need to add the key prop to the React.Fragment tag like this:
<React.Fragment key={user._id}>
Or please try to use index value instead of user.id as follows:
render() {
return (
<div className="App">
{this.state.users.map((user, i) => {
return (
<React.Fragment key={i}>
<div className="box">
<h3>{user.name}</h3>
<h4>{user.email}</h4>
<button
onClick={() => {
this.beginEdit(user);
}}
>
Update
</button>
<button
onClick={() => {
this.deleteUser(user);
}}
>
Delete
</button>
</div>
</React.Fragment>
);
})}
{this.renderAddUser()}
</div>
);
}

Modal dialog displays from all elements of mapped array. How to select each item by ts and react js?

This code:
How to display a dialog when a button is clicked using react and typescript?
I wanna open dialog from each todos, how to make it ? I used react js and typescript. Help me to resolve this problem.
interface ListProps {
todos: INote[];
onDelete: (title: string) => void;
}
const TodoList: React.FunctionComponent<ListProps> = ({ todos, onDelete }) => {
const [showAlert, setShowAlert] = useState(false);
const [todo, setTodos] = useState(null);
How to select each item by ts?It doesn't work. What is reason? Thanks!
const handleOpenDialog = (todos: any) => {
setTodos(todos);
setShowAlert(true);
};
const handleCloseDialog = () => {
setShowAlert(false);
};
return (
<>
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
{showAlert && todo && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</div>
))}
</section>
</>
);
};
export default TodoList;
just add a condition to only show the AlertDialog on selected todos
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
{showAlert && todos.title===todo?.title && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</div>
))}
</section>
or just move the AlertDialog outside the map
<section className="list list--wrapper">
{todos.map((todos) => (
<div className="item list__item" key={todos.title}>
<span className="item__title">{todos.title}</span>
<div className="item__group">
<input
className="item__completed"
type="checkbox"
checked={todos.completed}
/>
<span className="item__decs">{todos.desc}</span>
</div>
<div className="item__btn">
<button
className="item__btnd"
onClick={() => handleOpenDialog(todos)}
>
Delete
</button>
<button className="item__btne">Edit</button>
</div>
</div>
))}
{showAlert && todo && (
<AlertDialog
handleCloseDialog={handleCloseDialog}
title={todos.title}
/>
)}
</section>

I am mapping items in react, each item has a text are underneath for a comment, when I type in one box the text appears in all the boxes

I am mapping items in react, each item has a text area underneath for a comment, when I type in one box the text appears in all the boxes. I know it's because the values are the same this.state.comment but I not sure how to fix it. I have tried changing the value but i haven't figured out a way that would work since the the number of item can be 1 or 1000 depending on how many items are added.
class Content extends React.Component {
state = {
allUserItems: [],
image: null,
url: "",
video: "",
isActive: false,
isActive2: false,
comment: "",
checkInputID: null,
whichComment: null,
optionId: "",
edit_id: "",
editContent: "",
editPicture: "",
comment_id: "",
comOption_id: "",
postComment_id: "",
editComment: "",
isNotiOpen:false,
componentDidMount() {
console.log(this.props)
this.listFriendsItems()
}
listFriendsItems = () => {
API.getFriendsItems({ friends: this.props.userInfo.friends, })
.then(res => {
this.setState({ allUserItems: res.data })
console.log(res.data)
})
.catch(err => console.log(err));
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
submitComment = (id,posters_id) => {
API.saveComment(id, {
comment: this.state.comment,
user_id: this.props.userInfo.user_ID,
user: this.props.userInfo.firstname + " " +
this.props.userInfo.lastname,
picUrl: this.state.url,
})
.then(res => console.log(res))
.catch(err => console.log(err));
let data ={
comment: this.state.comment,
user_id: this.props.userInfo.user_ID,
name: this.props.userInfo.firstname + " " +
this.props.userInfo.lastname,
userPic: this.state.url,
}
if(this.props.userInfo.user_ID !== posters_id){
this.props.saveNotification(posters_id,data,id)
}
this.setState({ comment: "", checkInputID: null }, () =>
this.listFriendItems());
}
<section className="feed ">
{this.state.allUserItems.length ? (
<div>
{this.state.allUserItems.map(content => {
return (
<div className="feed_Container" key={content._id} >
<div className="info">
<div className="uploadedInfo">
{(content.picUrl === "" ) ? <div
className="story"> </div> :
<div className="miniUpImage"><img
className={`${(content.picUrl === "") ? "story" : "miniUpImage"}`} src=
{content.picUrl} alt="uploaded pic" /></div>
}
<div className={(content.videoUrl ===
"") ? "noVideo" : "uploadedVideo"}> <VideoPost video={content.videoUrl} />
</div>
</div>
<div className="colorBackground">
<div className="updateInfo">
<div className="timenOptions">
<div className="time">{moment(content.dateCreated).calendar()}</div>
<div className=
{(this.state.optionId === content._id) ? "optionsContainer active" :
"optionsContainer"} onClick={() => this.optionsClicked(content._id)} >
<div className=
{(content.user_ID === this.props.userInfo.user_ID) ? "options" :
"noOptions"}> ...</div>
<div
className="optionsDropdown">
<ul
className="optionsList">
<div
className="edit" onClick={() => this.editPostClicked(content._id,
content.content, content.picUrl)}> Edit</div>
<div
className="delete" onClick={() => this.removePost(content._id)}>Delete</div>
</ul>
</div>
</div>
</div>
<p>{content.content}
</p>
</div>
</div>
<div className="mapComments">{content.comments.map((comment, picUrl) =>
<div key={picUrl} className="commentList"><div className="timeStamp">
{moment(comment.dateCreated).calendar()}<div>
<div className={(this.state.comOption_id ===
comment._id"comOptionsContainer active" : "comOptionsContainer"}
onClick={() => this.commentOptions(comment._id)} >
<button type="button" className={(comment.user_id ===
this.props.userInfo.user_ID) ? "commentOptions" : "noOptions"} ><i
class="far fa-comment-dots"></i></button>
<div className="comOptionsDropdown">
<ul className="optionsList">
<div className="edit" onClick={() =>
this.editCommentClicked(content._id,
comment._id, content.content, comment.comment, content.picUrl)}>
Edit</div>
<div className="delete" onClick={() => this.removeComment(content._id,
comment._id)}>Delete</div>
</ul>
</div>
</div>
</div> </div><span> <strong>{comment.user} </strong>
</span>
{comment.comment}
<div className=
{(comment.picUrl !== "") ? "commentPic" : "nocommentPic"}><img
className="commentUrl" src={comment.picUrl} alt="comment pic" /></div>
</div>
)}
<div className="responseComments">
<textarea name="comment" value={this.state.comment} onChange=
{this.handleChange} className="commentArea" placeholder="Comment"
rows="8"
cols="80" />
<div className="commentPhoto">
<button type="button" className="button photo" onClick={() => {
this.fileInput2.click(); this.getID(content._id); }}> <i className="far
fa-images"></i></button>
</div>
</div>
<div>
<input type="file" style={{ display: "none" }} onChange=
{this.handleImageSelected2} ref={fileInput => this.fileInput2 = fileInput}
/>
<img className=
{(this.state.checkInputID === content._id) ? "uploadReady active" :
"uploadReady"} src={this.state.url} alt="preview" height="40" width="50" />
<progress className= {(this.state.checkInputID === content._id) ?
"uploadReady
active" : "uploadReady"} value={this.state.progress} max="100" />
<button className= {(this.state.checkInputID === content._id) ?
"uploadReady
active" : "uploadReady"} onClick={this.handleUpload}>Upload</button>
<span
className={(this.state.checkInputID === content._id) ? "uploadReady active"
:
"uploadReady"}>File </span>
</div>
<div className="commentButtons">
<div className="replyButton" onClick={this.state.comment === "" &&
this.state.url === "" ? null : () =>
this.submitComment(content._id,content.user_ID)} ><i className="fas fa-
share">
<div className="likessection">
{(content.likes.findIndex(i => i.user_id === this.props.userInfo.user_ID) >
-1) ?
<div className="likeButton" onClick={() =>
this.removeLikes(content._id)}>Unlike</div>
: <div className="likeButton" onClick={() =>
this.handleLikes(content._id)}>
<i
className="far fa-thumbs-up"></i></div>}
</div>
</div>
</div>
</div>
</div>
);
})
}
</div>
You have to initialize state comments with the number of comments you have.
In your constructor create your comments like this, an array of comments:
this.state= {
comments: new Array(n) //n is your number of comments
}
In the handleChange function, you will have to pass the index of comment to be updated.
handleChange = (commentIndex, e) => {
const commentsUpdated = this.state.comments.map((comment, i) => {
if (i == commentIndex) return e.target.value;
else return comment;
});
this.setState({ comments: commentsUpdated });
};
And you have to update the JSX, you will need to render a collection of comments, and every comment connect with its comment state.
<div className="responseComments">
{this.state.comments.map((comment, i) => (
<textarea
name="comment"
value={comment}
onChange={e => this.handleChange(i, e)}
className="commentArea"
placeholder="Comment"
rows="8"
cols="80"
/>
))}
</div>;

Unable to set property of a single element in an array

In an array named admin, I want to show a div on the click of a button "update" when when I do so, the div shows below all the elements of the array. I only want it to show below the selected element.
function Admin(props) {
const [showMe, setShowMe] = React.useState(false);
const [updateName, setupdateName] = React.useState("");
const [updateDesc, setupdateDesc] = React.useState("");
return (
<div>
<div className="adminProducts">
{props.admin.map((x, i) => (
<div>
{showMe ? (
<div className="UpdateSection">
<input
type="text"
placeholder="Product Name"
onChange={e => setupdateName(e.target.value)}
value={updateName}
/>
<br />
<textarea
placeholder="Product Description"
onChange={e => setupdateDesc(e.target.value)}
value={updateDesc}
/>
<button
type="submit"
onClick={e => {
props.UpdateInfo({ updateName, updateDesc }, { x }, i);
setupdateName("");
setupdateDesc("");
}}
>
Save
</button>
</div>
) : null}
<div>{x.name}</div>
<div>
<button onClick={e => setShowMe(!showMe)}>
{showMe ? "Close" : "Update"}
</button>
</div>
</div>
))}
</div>
</div>
);
}
I want to set showMe as true for individual elements in array so that the div with classname UpdateSection only shows for that specific element and not for any other element.
You can save the id of the element you want to be shown:
const [showMe, setShowMe] = React.useState([]);
// ...
const isShown = el => showMe.includes(el.id);
const toggleShown = el => {
setShowMe(shown => {
if (shown.includes(el.id)) {
return shown.filter(id => id !== el.id);
}
return [...shown, el.id];
});
};
//...
return (
<div>
<div className="adminProducts">
{props.admin.map((x, i) => (
<div>
{isShown(x) ? (
//...
<div>
<button onClick={e => toggleShown(x)}>
{isShown(x) ? "Close" : "Update"}
</button>
</div>
</div>
))}
</div>
</div>
);

React rendering - performance

My react render slowly works in mobile.
My component renders list of some items. This is the render method.
return (
<div className={ bemCn('columns-container') }>
{ isLanding && isMobileMode && this.renderTabs(platform) }
<div className={ bemCn('columns', {isLanding: isLanding}) }>
<div className={ bemCn('column').mix(bemCn('counters', {isLanding: isLanding, isMobileModeAndLanding: isMobileMode && isLanding})) }>
{this.props.table.rows.map(row => (
<div className={ bemCn('counters-item') } key={ row }>
{this.props.from + row}
</div>
))}
</div>
{columnsCategory[platform].map(column => (
<div key={ column } className={ bemCn('column', {isLanding: isLanding}) }>
<div className={ bemCn('title', { [column]: true, isLanding: isLanding }) }>{TITLES[column]}</div>
<ul className={ bemCn('list') }>
{this.props.table.rows.map(idx => {
const item = columns[column]
? columns[column].find(item => item.position === idx + 1)
: null
if (!item) {
return <li key={ idx } className={ bemCn('list-item', { empty: true, isLandingAndEmpty: isLanding }) } />
}
if (!applications[item.app_id]) return null
let modifier
let prefix
let positionChangeValue
const yesterdayPosition = yesterday[`${item.app_id}:${column}`]
if (yesterdayPosition) {
modifier = item.position < yesterdayPosition ? 'up' : 'down'
prefix = item.position < yesterdayPosition ? '+' : '-'
positionChangeValue = Math.abs(item.position - yesterdayPosition)
}
const isBlur = chartsTypeFilterSet.size ? this.checkBlur(item.app_id, publishers, advertisers, chartsTypeFilterSet) : false
return (
<li key={ idx } className={ bemCn('list-item', { blur: isBlur, isLanding: isLanding })() }>
<Link className={ bemCn('link', {isLanding: isLanding}) } to={ this.getLink(item.app_id, applications[item.app_id].slug) }>
{isLanding && <div className={bemCn('mobile-counter', {isMobileMode: isMobileMode})}>{ idx + 1 }</div>}
<div className={ bemCn('icon') }>
<img
src={ setImageSize({
url: applications[item.app_id].icon_url,
platform,
size: 44
}) }
alt={ applications[item.app_id].name }
/>
</div>
<div className={ bemCn('info', {isLanding: isLanding}) }>
<div className={ bemCn('info-names') }>
<div className={ bemCn('publisher').mix('u-text-ellipsis') }>
{applications[item.app_id].developer.name}
</div>
<div className={ bemCn('name').mix('u-text-ellipsis') }>
{applications[item.app_id].name}
</div>
</div>
<div className={ bemCn('rating') }>
{!!item.rating && <StarRatings rating={ item.rating } starDimension="12px" starSpacing="1px" />}
{publishers[item.app_id] && (
<SimpleTip tip="Advertising monetization of this app" noWrap>
<div className={ bemCn('pub-icon')() }>Pub</div>
</SimpleTip>
)}
{advertisers[item.app_id] && (
<SimpleTip tip="Advertising campaigns of this app" noWrap>
<div className={ bemCn('pub-icon', { ad: true })() }>Ad</div>
</SimpleTip>
)}
{!isLanding && fb[item.app_id] && (
<SimpleTip tip="fb" noWrap>
<div className={ bemCn('pub-icon', { fb: true })() }>Fb</div>
</SimpleTip>
)}
</div>
</div>
<div className={ bemCn('changes') }>
<div className={ bemCn('price') }>
{item.price === 'FREE' || item.code === 'FREE' ? 'FREE' : null}
{item.price !== 0 && (
<span>
{item.code} {item.price}
</span>
)}
</div>
{positionChangeValue !== 0 && (
<div className={ bemCn('changes-value', { [modifier]: true }) }>
{prefix}
{positionChangeValue}
</div>
)}
</div>
</Link>
</li>
)
})}
</ul>
</div>
))}
</div>
</div>
)
}
I don't know why render so slow. In this render I often use construction className={ bemCn('info', {isLanding: isLanding}) } (check isLanding). How does this construction affect performance, if I need render >100 items?

Categories

Resources