I'm getting this error 'Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of', When not adding the key in child component.
Why is this key necessary include here? because without this also component render correctly.
component - (parent)
import React, { Component } from 'react';
import ChildKey from './ChildKey';
import './App.css';
class ParentKey extends Component {
constructor() {
super();
this.state = {
keyList: [{
name: 'key1',
},{
name: 'key1',
},{
name: 'key1',
},{
name: 'key1'
}]
}
}
render() {
const {keyList} = this.state;
return (
<div style={{marginLeft: '40%'}}>
<h1>Parent Component</h1>
<br/>
<div>
{
keyList && keyList.map((data, index) => <ChildKey name={data.name} />)
}
</div>
</div>
)
}
}
export default ParentKey;
component (children) -
import React, {Component} from 'react';
export default class ChildKey extends Component {
constructor() {
super();
}
getStyle() {
return {
rootStyle: {
width: '40px',
height: '40px',
display: 'block'
}
}
}
render() {
const styles = this.getStyle();
return (
<div style={styles.rootStyle}>
<div>{this.props.name}</div>
</div>
)
}
}
It is necessary in keyList && keyList.map((data, index) => <ChildKey name={data.name} />) because when one row changes React needs to know which row changed and it will update only that specific row. Otherwise React will re-render all the rows and this is not good for the performance
Example: keyList && keyList.map((data, index) => <ChildKey key={data.name} name={data.name} />)
Related
I want to pass brand.title to the child component - BrandDetail
This is my try and is not working, it simply renders the child component within the parent component and I want it to be rendered solely on the child component.
Parent component:
class BrandsList extends React.Component {
state = {
brands: [],
};
fetchBrands = () => {
axios.get('http://127.0.0.1:8000/api/brands/').then((res) => {
this.setState({
brands: res.data,
});
});
};
componentDidMount() {
this.fetchBrands();
}
render() {
return (
<div style={{ margin: 22 }}>
{this.state.brands.map((brand) => (
<div key={brand.id}>
<Link to={`/brands/${brand.id}`}>{brand.title}</Link>
<BrandDetail brandName={brand.title} />
</div>
))}
</div>
);
}
}
export default BrandsList;
Child component:
import React, { useEffect, useState } from 'react';
import MyLayout from '../MyLayout/MyLayout';
class BrandDetail extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<MyLayout>
<div>Yes this is the detail page of {this.props.brandName}</div>
</MyLayout>
);
}
}
export default BrandDetail;
UPDATE:
This is the answer I was looking for.
{ this.state.brands.map(brand =>
<div key={brand.id}>
<Link to={{
pathname: `/brands/${brand.title}`,
state: `${brand.title}`,
}}>{brand.title}
</Link>
</div>
)}
And child component:
<div>Yes this is the detail page of {props.location.state}</div>
I have a Tabbar in my Tabbar Component, Which I Change the index props in it :
class Tabbar extends Component {
state = {
index: this.props.index,
name: this.props.name,
image: this.props.image
};
changeTabs = () => {
this.setState({index: this.props.index});
}
render() {
return (
<React.Fragment>
<div id={this.state.index} className="col">
<button onClick={this.changeTabs}></button>
</div>
</React.Fragment>
);
}
}
export default Tabbar;
And Then In my Other Component, I Wanna Re-Render a fragment after props change. Here's my Code :
import Tabbar from './Tabbar';
class Tabview extends Component {
constructor(props) {
super(props);
this.state = {
tabs: [
{index: 0, name: "tab0", image:require('../Assets/profile.svg'),childView: {ProfilePage} },
{index: 1, name: "tab1", image:require('../Assets/home.svg'),childView: {HomePage}},
{index: 2, name: "tab2", image:require('../Assets/blog.svg'),childView: {BlogPage}},
],
}
}
handleRender = () => {
this.state.tabs.map(item => {
if (item.index === this.props.index) {
return <item.childView/>;
}
})
return <BlogPage/>;
}
render() {
return (
<div>
<Header/>
{this.handleRender()}
{this.state.tabs.map(item =>
<Tabbar key={item.index} index={item.index} name={item.name} image={item.image}/>
)}
</div>
);
}
}
export default Tabview;
The Method "handleRender" should handle the rendering.
I tried to use "componentDidMount" or "componentDidUpdate", But I didn't work.
How Can I Make it Work?
Thank you in advance!
You dont need to have a state in the child component for this reason
You can simply have a callback in parent and call it in child component like below.
import React, { Component } from "react";
class Tabbar extends Component {
render() {
return (
<React.Fragment>
<div id={this.props.index} className="col">
<button
onClick={() => this.props.changeTabs(this.props.index)}
></button>
</div>
</React.Fragment>
);
}
}
export default Tabbar;
And in parent you maintain the active index state
import Tabbar from "./Tabbar";
import React, { Component } from "react";
class Tabview extends Component {
constructor(props) {
super(props);
this.state = {
tabs: [
//your tabs
],
activeIndex: 0
};
}
handleRender = () => {
this.state.tabs.map((item) => {
if (item.index === this.state.activeIndex) {
return <item.childView />;
}
});
return <div />;
};
render() {
return (
<div>
{this.handleRender()}
{this.state.tabs.map((item) => (
<Tabbar
key={item.index}
index={item.index}
name={item.name}
image={item.image}
changeTabs={(index) => this.setState({ activeIndex: index })}
/>
))}
</div>
);
}
}
export default Tabview;
When I add data to the table it does not get populated with the new record. I created a list and displayed it to see if it populates the new record, it seems to have updated there. I add a console.log to the componentWillReceiveProps function to see if the list updates with the new record, the list does get updated but the table never updates. Fairly new to react, not sure what I am missing.
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/postActions';
import MaterialTable from 'material-table';
class Posts extends Component {
componentWillMount() {
this.props.fetchPosts();
}
componentWillReceiveProps(nextProps) {
if(nextProps.newPost) {
this.props.posts.unshift(nextProps.newPost);
}
}
render () {
const postItems = this.props.posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
));
return (
<div>
<div>
<h1>Posts</h1>
{postItems}
</div>
<div style={{marginLeft:'15px',marginRight:'15px'}}>
<div style={{ maxWidth: "100%" }}>
<MaterialTable
title="Users List"
columns={[
{ title: 'User Name', field: 'id' },
{ title: 'Email', field: 'title' },
]}
data={this.props.posts}
/>
<br/><br/><br/>
</div>
</div>
</div>
);
}
}
Posts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.array.isRequired,
newPost: PropTypes.object
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchPosts })(Posts);
You cannot set props from inside the component, only from outside. That's the difference between state and props (it's quite fundamental in React, read about it).
First, initialize the state in constructor:
constructor(props) {
super(props);
this.state = { posts: [] };
}
then, update the state when props change:
componentWillReceiveProps(nextProps) {
if(nextProps.newPost) {
let updatedPosts = this.state.props;
updatedPosts.unshift(nextProps.newPost);
this.setState({ posts: updatedPosts });
}
}
}
finally, use the state in your table:
...
data={this.state.posts}
...
I created a project in Reactjs where I have a list of names that I want to display in a custom list. Each item has a button to delete the item, however whenever I click the button, the last item is removed from the list no matter which list item I click.
I have already tried to debug my code using the js-console but that made the problem even stranger since the console displays the correct state wheras the component "List" renders a list item which is no longer present in the state object
import React, { Component } from 'react';
import './ListItem'
import ListItem from './ListItem';
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{name: 'Tobi'},
{name: 'Maxi'},
{name: 'David'},
{name: 'Peter'},
]
}
}
removeItem = (id) => {
let few = this.state.items;
few.splice(id,1);
//console.log(this.state.items);
this.setState({items: few}, function(){
console.log(this.state.items.map((item) => item.name));
this.forceUpdate();
});
}
render() {
return (
<div>
<ul>
{this.state.items.map((item, i) => <ListItem name={item.name} key={i} id={i} remove={this.removeItem}/>)}
</ul>
</div>
);
}
}
import React, { Component } from 'react';
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name,
id: this.props.id
}
}
test = () => {
this.props.remove(this.state.id);
}
render() {
return (
<li>{this.state.name} <button onClick={() => this.test()}>click me</button></li>
);
}
}
export default ListItem;
As is said i excpected the right list item to be removed however it is always the last item that isnt rendered anymore even though the state object says different.
The main problem is that you're using an array index as a key. When you first render the ListItems you have :
ListItem name={'Tobi'} key={0}
ListItem name={'Maxi'} key={1}
ListItem name={'David'} key={2}
ListItem name={'Peter'} key={3}
Let's say you removed the item with index 1, all other items will shift index:
ListItem name={'Tobi'} key={0}
ListItem name={'David'} key={1}
ListItem name={'Peter'} key={2}
React will only compare the keys, and because the only difference between the first and second render is that the item with key={3} is not present, this is the item that will be removed from the dom.
Also avoid mutating the state directly (few.splice(id,1)), and try to avoid this.forceUpdate()
Try using an actual id in your data :
import React, { Component } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class List extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, name: "Tobi" },
{ id: 2, name: "Maxi" },
{ id: 3, name: "David" },
{ id: 4, name: "Peter" }
]
};
}
removeItem = id => {
let few = this.state.items.filter(item => item.id !==id);
//console.log(this.state.items);
this.setState({ items: few }, function() {
console.log(this.state.items.map(item => item.name));
//this.forceUpdate();
});
};
render() {
return (
<div>
<ul>
{this.state.items.map((item, i) => (
<ListItem
name={item.name}
key={item.id}
id={item.id}
remove={this.removeItem}
/>
))}
</ul>
</div>
);
}
}
class ListItem extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name,
id: this.props.id
};
}
test = () => {
this.props.remove(this.state.id);
};
render() {
return (
<li>
{this.state.name} <button onClick={() => this.test()}>click me</button>
</li>
);
}
}
function App() {
return (
<div className="App">
<List />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
could anyone tell me why is that won't work? Proper data is displaying in the console (console.log(this.state);), but it won't be transfered to MainContainer.
Same data initialized in the constructor>state>users working without issues. Where's the problem?
App
import React, {Component} from 'react';
import logo from './logo.svg';
import './App.css';
import Header from './components/header/Header';
import MainContainer from './containers/main-container/MainContainer';
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: []
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(users => {
let u = users.map((user) => {
return {id: user.id, name: user.name, email: user.email}
})
return u;
})
.then(u => {
this.setState({users: u});
console.log(this.state);
});
}
render() {
return (
<div className="App">
<Header/>
<MainContainer users={this.state.users}/>
</div>
)
}
}
export default App;
MainContainer
import React from 'react';
import ActionBar from '../../components/action-bar/ActionBar'
import ListHeader from '../../components/list-header/ListHeader'
import ListItem from '../../components/list-item/ListItem'
import ListItemPlaceholder from '../../components/list-item-placeholder/ListItemPlaceholder'
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
users : props.users
}
}
render() {
const list = this.state.users.map(
(user) =>
{
const liStyle = {
'background-color': user % 2 == 0 ? '#fbfcfc' : 'transparent',
};
return <ListItem key={user.id} style={liStyle} id={user.id} name={user.name} email={user.email}/>
}
);
return (
<div className={'main-container'}>
<ActionBar />
<ListHeader />
{list}
</div>
)
}
}
export default MainContainer;
.................................................................................................................
Best Regards!
crova
In your <MainContainer> component you store the users in its state in the constructor but you never alter it. You only need to use state when the component needs to alter it during its lifetime. But the users come from it's parent via the users prop which you never render. So just render that prop instead:
const MainContainer = props => (
<div className="main-container">
<ActionBar />
<ListHeader />
{props.users.map(({id, name, email}) => (
<ListItem
key={id}
style={{
backgroundColor: id % 2 === 0 ? '#fbfcfc' : 'transparent'
}}
id={id}
name={name}
email={email}
/>
))}
</div>
);
When the users change in the parent it will re-render and pass the new users array to the <MainContainer>.
Also note that if your component only renders props and has no own state it can be written as a stateless functional component.