React list rendering wrong data after deleting item - reactjs

I have a simple list of student objects with name and their scores in state.
Their name is bound to <b>{student.name}</b> and their score is bound to
<input type="text" defaultValue={student.score}/>
Whenever I want to delete the first student from this list and
re-rendering the component by calling set state.
The input tag of the second student showing the score of the first student instead of having its own. Why this happening where I am doing it wrong ??
Here is my code jsbin
class App extends React.Component{
constructor(){
super();
this.state ={
students:[{name:"A",score:10},{name:"B",score:20},{name:"C",score:30}]
}
}
onDelete(index){
this.state.students.splice(index,1);
this.setState(this.state);
}
render(){
return(
<div>
{this.state.students.map((student,index)=>{
return(
<div key={index}>
<b>{student.name}</b> - <input type="text" defaultValue={student.score}/>
<button onClick={this.onDelete.bind(this,index)}>delete</button>
</div>
)
})}
</div>
)
}
}
ReactDOM.render(<App/>,document.getElementById("main"));

It's because you're using key={index} instead of a value unique to the student.
When the array is modified the students after the index removed will have the wrong key and React will not register a key change to rerender with the updated data.
You should instead use something like this...
<div key={student.name}>
assuming that student.name is unique.

It is always better to use unique id as a key instead of index. If your JSON does not provide a unique Id, you can use something like uuid npm module which generates it for you. This is the link https://www.npmjs.com/package/uuid.
You can then import it and use as below
import { v4 } from 'uuid'; //there are 5 versions. You can use any.
then use it to generate uique Id by calling the v4 function like below
id={v4()}

Best practice
To be honest, you shouldn't mutate state data directly. You should clone then do your task like this
onDelete(index) {
const newStudents = [...this.state.students];
newStudents.splice(index, 1);
this.setState({ students: newStudents });
}
Why can't I directly modify a component's state, really?
The previous state will be polluted with your mutation. Due to which, the shallow compare and merge of two states will be
disturbed or won't happen, because you'll have only one state now.
This will disrupt all the React's Lifecycle Methods.
Your problem & solution
In case that you use the input readOnly, you should change from defaultValue to value then your code work well.
<input type="text" value={student.score} readOnly />
class App extends React.Component {
constructor() {
super();
this.state = {
students: [
{ name: "A", score: 10 },
{ name: "B", score: 20 },
{ name: "C", score: 30 }
]
};
}
onDelete(index) {
const newStudents = [...this.state.students];
newStudents.splice(index, 1);
this.setState({ students: newStudents });
}
render() {
return (
<div>
{this.state.students.map((student, index) => {
return (
<div key={index}>
<b>{student.name}</b> -{" "}
<input type="text" value={student.score} readOnly />
<button onClick={this.onDelete.bind(this, index)}>delete</button>
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Otherwise, you should provide the unique key. If student.name is not unique, you can randomly GUID like this.
const getGUID = () => "id" + Math.random().toString(16).slice(2);
> <div key={getGUID()}>
const getGUID = () => "id" + Math.random().toString(16).slice(2);
class App extends React.Component {
constructor() {
super();
this.state = {
students: [
{ name: "A", score: 10 },
{ name: "B", score: 20 },
{ name: "C", score: 30 }
]
};
}
onDelete(index) {
const newStudents = [...this.state.students];
newStudents.splice(index, 1);
this.setState({ students: newStudents });
}
render() {
return (
<div>
{this.state.students.map((student, index) => {
return (
<div key={getGUID()}>
<b>{student.name}</b> -{" "}
<input type="text" defaultValue={student.score} />
<button onClick={this.onDelete.bind(this, index)}>delete</button>
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
However, in this case, Unstable keys will cause harmful performance because component instances - Virtual DOM & DOM node - actual DOM will always unnecessary recreated.
In short, depends on your data and behavior, you can choose the correct way to complete it.

Related

stuck at Displaying Updated Array Data

I have an array List and I'm using the .push() method to add new elements to it and then concat the input with List in Onclickadd method but its not updating the array to display the items in to-do
import React from 'react'
import './App.css'
class App extends React.Component {
constructor(){
super()
this.state={
List: ['potato']
}
}
onAddChange=(event)=>{
this.setState=({
input: event.target.value
})
}
Onclickadd=()=>{
console.log('clicked')
this.setState=({List: this.state.List.concat(this.state.input)})
}
render (){
return (
<div className="App">
<h1>TODO LIST</h1>
<input onChange={this.onAddChange} type='text' placeholder='Add Items'/>
<button onClick={this.Onclickadd} className='btn'>Add</button>
<ol>
{this.state.List.map((items, keys) => {
return <li key={keys}> {items}</li>
})}
</ol>
</div>
);
}
}
export default App;
this.setState is an function witch expects an object, you are trying to assign the value, instead off this.setState= use;
this.setState({
input: event.target.value
})
class App extends React.Component {
constructor(){
super()
this.state={
List: ['potato']
}
}
onAddChange=(event)=>{
this.setState({
input: event.target.value
})
}
Onclickadd=()=>{
this.setState({List: this.state.List.concat(this.state.input)})
}
render (){
return (
<div className="App">
<h1>TODO LIST</h1>
<input onChange={this.onAddChange} type='text' placeholder='Add Items'/>
<button onClick={this.Onclickadd} className='btn'>Add</button>
<ol>
{this.state.List.map((items, keys) => {
return <li key={keys}> {items}</li>
})}
</ol>
</div>
);
}
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
More info about setState can be found here
this.setState=... is the issue in your code. It is a function call there is no assignment using =.
You have done the same mistake in two places
Change the onAddChange to
onAddChange=(event)=>{
this.setState({
input: event.target.value
})
}
and
Onclickadd to
Onclickadd=()=>{
console.log('clicked')
this.setState({List: this.state.List.concat(this.state.input)})
}
Working example => https://codesandbox.io/s/intelligent-galois-ih7w4?file=/src/App.js:294-418
I recommend you to add value={this.state.input} to the input like following :
<input value={this.state.input} onChange={this.onAddChange} type='text' placeholder='Add Items'/>
The syntax of this.setState() is not right, it's like this :
this.setState({ List: this.state.List.concat(this.state.input) })
this.setState({ input: event.target.value })

sort items in state alphabetically

I have a class based React component that is using items in state and rendering result. Here is short snippet how I do this:
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
items: props.items.edges,
someItems: props.items.edges,
}
}
render() {
if (this.state.items.length > 0) {
return (
<div className="container">
<div className="row">
{this.state.someItems.map(({ node }) => {
return (
<div key={node.id}>
<div>
render some data
</div>
</div>
)
})}
</div>
</div>
);
}
}
}
The data is received as objects inside an array, like this:
My question is would it be possible to sort these items alphabetically before being rendered? What would be the best approach for this?
The best approach is to sort the items before you set them to the state. You can use the built in Array.prototype.sort method in order to sort the items. You can use the String.prototype.localeCompare in order to compare strings alphabetically.
I don't know the expected structure of your data so here is a general solution.
class App extends React.Component {
constructor(props) {
super(props);
// Make a copy so as not to modify the original array directly
const sortedCopy = [...props.items];
sortedCopy.sort((a, b) => a.name.localeCompare(b.name));
this.state = {
items: sortedCopy,
};
}
render() {
return (
<div>
{this.state.items.map((item) => (
<p key={item.id}>
<div>Item - {item.name}</div>
</p>
))}
</div>
);
}
}
// Example items prop is out of order
const items = [
{ id: 0, name: "C" },
{ id: 1, name: "B" },
{ id: 2, name: "A" },
{ id: 3, name: "D" },
];
ReactDOM.render(<App items={items} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to render multiple component in reactjs, what i'm doing wrong?

First i have a function to fetch data from database then
if the data be changed, i will create list components.
but it didnt work, what i'm doing wrong?
console:
class TweetContainer extends React.Component{
constructor(props){
super(props);
this.state = {
tweetData:{},
tweetRender : [],
listTweet:[]
}
}
here is my function to fetch data from database
componentDidMount(){
fetch('http://localhost:5000/tweet')
.then(function(response) {
return response.json();
})
.then(result=>{
this.setState({
tweetData: result
}, ()=>console.log(this.state.tweetData));
});
}
my function to make list component
componentDidUpdate(){
this.state.tweetRender = this.state.tweetData.data.slice(1,6);
console.log(this.state.tweetRender);
this.state.listTweet = this.state.tweetRender.map((tweet)=><Tweet
linkAvatar={'/image/jennyshen.jpg'}
name={"Vuongxuan"}
userName={'#vuggg'}
tweetText={tweet.content} />);
console.log(this.state.listTweet);
}
render(){
return(
<div id="main">
<h2>Tweet</h2>
<div id="stream">
{this.state.listTweet}
</div>
</div>
);
}
}
i dont know what i'm doing wrong.
Accordingly to React docs, componentDidMount lifecycle most common use is for:
Updating the DOM in response to prop or state changes.
And you want to get and render the tweets, right? Not necessarily listen to updates.
For now a solution is remove your componentDidUpdate() method and change your `render´ method to:
render(){
var tweetRender = this.state.tweetData.data.slice(1,6);
return(
<div id="main">
<h2>Tweet</h2>
<div id="stream">
{listTweet.map((tweet, idx) =>
<Tweet
key={idx}
linkAvatar={'/image/jennyshen.jpg'}
name={"Vuongxuan"}
userName={'#vuggg'}
tweetText={tweet.content} />
)}
</div>
</div>
);
}
It's generally not a good idea to put React elements (JSX) inside your component state. You could instead just store the data in state, and derive the JSX from that data in the render method.
Example
class TweetContainer extends React.Component {
state = {
tweetData: [],
tweetRender: [],
listTweet: []
};
componentDidMount() {
setTimeout(() => {
this.setState({
tweetData: [
{
id: 1,
name: "foo",
username: "#foo"
},
{
id: 2,
name: "bar",
username: "#bar"
}
]
});
}, 1000);
}
render() {
return (
<div id="main">
<h2>Tweet</h2>
<div id="stream">
{this.state.tweetData.map(obj => (
<div key={obj.id}>
{obj.username} - {obj.name}
</div>
))}
</div>
</div>
);
}
}
ReactDOM.render(<TweetContainer />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Iterate Item Inside JSX React Native [duplicate]

could you please tell me how to render a list in react js.
I do like this
https://plnkr.co/edit/X9Ov5roJtTSk9YhqYUdp?p=preview
class First extends React.Component {
constructor (props){
super(props);
}
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>;
return (
<div>
hello
</div>
);
}
}
You can do it in two ways:
First:
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>);
return (
<div>
{listItems }
</div>
);
}
Second: Directly write the map function in the return
render() {
const data =[{"name":"test1"},{"name":"test2"}];
return (
<div>
{data.map(function(d, idx){
return (<li key={idx}>{d.name}</li>)
})}
</div>
);
}
https://facebook.github.io/react/docs/jsx-in-depth.html#javascript-expressions
You can pass any JavaScript expression as children, by enclosing it within {}. For example, these expressions are equivalent:
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
This is often useful for rendering a list of JSX expressions of arbitrary length. For example, this renders an HTML list:
function Item(props) {
return <li>{props.message}</li>;
}
function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
<ul>
{todos.map((message) => <Item key={message} message={message} />)}
</ul>
);
}
class First extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{name: 'bob'}, {name: 'chris'}],
};
}
render() {
return (
<ul>
{this.state.data.map(d => <li key={d.name}>{d.name}</li>)}
</ul>
);
}
}
ReactDOM.render(
<First />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Shubham's answer explains very well. This answer is addition to it as per to avoid some pitfalls and refactoring to a more readable syntax
Pitfall : There is common misconception in rendering array of objects especially if there is an update or delete action performed on data. Use case would be like deleting an item from table row. Sometimes when row which is expected to be deleted, does not get deleted and instead other row gets deleted.
To avoid this, use key prop in root element which is looped over in JSX tree of .map(). Also adding React's Fragment will avoid adding another element in between of ul and li when rendered via calling method.
state = {
userData: [
{ id: '1', name: 'Joe', user_type: 'Developer' },
{ id: '2', name: 'Hill', user_type: 'Designer' }
]
};
deleteUser = id => {
// delete operation to remove item
};
renderItems = () => {
const data = this.state.userData;
const mapRows = data.map((item, index) => (
<Fragment key={item.id}>
<li>
{/* Passing unique value to 'key' prop, eases process for virtual DOM to remove specific element and update HTML tree */}
<span>Name : {item.name}</span>
<span>User Type: {item.user_type}</span>
<button onClick={() => this.deleteUser(item.id)}>
Delete User
</button>
</li>
</Fragment>
));
return mapRows;
};
render() {
return <ul>{this.renderItems()}</ul>;
}
Important : Decision to use which value should we pass to key prop also matters as common way is to use index parameter provided by .map().
TLDR; But there's a drawback to it and avoid it as much as possible and use any unique id from data which is being iterated such as item.id. There's a good article on this - https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
Try this below code in app.js file, easy to understand
function List({}) {
var nameList = [
{ id: "01", firstname: "Rahul", lastname: "Gulati" },
{ id: "02", firstname: "Ronak", lastname: "Gupta" },
{ id: "03", firstname: "Vaishali", lastname: "Kohli" },
{ id: "04", firstname: "Peter", lastname: "Sharma" }
];
const itemList = nameList.map((item) => (
<li>
{item.firstname} {item.lastname}
</li>
));
return (
<div>
<ol style={{ listStyleType: "none" }}>{itemList}</ol>
</div>
);
}
export default function App() {
return (
<div className="App">
<List />
</div>
);
}
import React from 'react';
class RentalHome extends React.Component{
constructor(){
super();
this.state = {
rentals:[{
_id: 1,
title: "Nice Shahghouse Biryani",
city: "Hyderabad",
category: "condo",
image: "http://via.placeholder.com/350x250",
numOfRooms: 4,
shared: true,
description: "Very nice apartment in center of the city.",
dailyPrice: 43
},
{
_id: 2,
title: "Modern apartment in center",
city: "Bangalore",
category: "apartment",
image: "http://via.placeholder.com/350x250",
numOfRooms: 1,
shared: false,
description: "Very nice apartment in center of the city.",
dailyPrice: 11
},
{
_id: 3,
title: "Old house in nature",
city: "Patna",
category: "house",
image: "http://via.placeholder.com/350x250",
numOfRooms: 5,
shared: true,
description: "Very nice apartment in center of the city.",
dailyPrice: 23
}]
}
}
render(){
const {rentals} = this.state;
return(
<div className="card-list">
<div className="container">
<h1 className="page-title">Your Home All Around the World</h1>
<div className="row">
{
rentals.map((rental)=>{
return(
<div key={rental._id} className="col-md-3">
<div className="card bwm-card">
<img
className="card-img-top"
src={rental.image}
alt={rental.title} />
<div className="card-body">
<h6 className="card-subtitle mb-0 text-muted">
{rental.shared} {rental.category} {rental.city}
</h6>
<h5 className="card-title big-font">
{rental.title}
</h5>
<p className="card-text">
${rental.dailyPrice} per Night · Free Cancelation
</p>
</div>
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
}
export default RentalHome;
Try this:
class First extends React.Component {
constructor (props){
super(props);
}
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>;
return (
<div>
{listItems}
</div>
);
}
}

Render a string which contains components

I am newbie at reactJs and i am trying to build an app in which i get some results after searching .
My problem is that i have a component called ResultEntity and I am trying create a dynamically page without defined number of ResultEntity components.
I tried something like this
for(var i=0 ; i<result.length ; i++)
{
results += "<div> <ResultEntity/> </div>";
};
console.log(results);
this.setState({result: results});
And i tried to return it like ,
return (
<div>
<div dangerouslySetInnerHTML={{ __html: this.state.result }} />
</div>
);
and
return (
<div>
<div dangerouslySetInnerHTML={{ __html: this.state.result }} />
</div>
);
but both didnt work . Any idea will be appreciated . Thank you in advance
So you want to render a list of components dynamically. Here's how you can do it using .map function:
// This also can be a functional component, instead of a class
class ResultEntity extends React.Component {
render() {
const { item } = this.props
return <div>{ `${item.id} - ${item.name}` }</div>
}
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
items: [
{ id: 1, name: 'Bulgaria' },
{ id: 2, name: 'Germany' },
]
}
}
renderItems() {
const { items } = this.state
// Great explanations, why this work and it's rendered correctly:
// https://medium.com/byte-sized-react/component-arrays-in-react-a46e775fae7b
return items.map(item => <ResultEntity key={item.id} item={item} />)
}
render() {
// From React >= 16 it's possible to skip the wrapper `div`:
// https://stackoverflow.com/a/32157488/4312466
return <div>{ this.renderItems() }</div>
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>

Resources