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);
Related
//child component
import React, { Component } from 'react'
const NavButton = ({ active, title, href, onSetActive }) => {
return (
<button
className={active ? "btn btn-light regular-btn active" : "btn btn-light regular-btn"}
href={href}
onClick={onSetActive} >
{title}
</button>
)
}
class Child extends Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 1,
buttons: [
{
title: "1",
key: 0,
value: 1
},
{
title: "3",
key: 1,
value: 3
}
]
}
}
//It was changing active index
handleChangeActive(newActiveIndex) {
// console.log("Working");
this.setState({ activeIndex: newActiveIndex });
}
render() {
const { activeIndex } = this.state;
return (
<div>
<nav id="navbarMain">
<div className="navbar-nav flex-row">
{this.state.buttons.map((button, buttonIndex) =>
/* determine which nav button is active depending on the activeIndex state */
<NavButton onClick={() => this.investmentHandler.bind(this)} value={button.value} onSetActive={() => this.handleChangeActive(buttonIndex)} active={buttonIndex === activeIndex} title={button.title} key={button.key} />)}
</div>
</nav>
</div>
)
}
}
export default Child
// parent component
import React, { Component } from 'react'
import Child from './Child';
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
firstValue: 1,
secondValue: 3,
result: ''
}
// this.add = this.add.bind(this);
}
//calculating the input values
Add = () => {
var { firstValue, secondValue, result } = this.state;
result = firstValue + secondValue;
console.log(result);
document.getElementById("result").innerHTML = result;
}
render() {
return (
<>
//Child component is used inside the parent component using props
<Child investmentHandler={this.add} />
<p id="result">Result</p>
</>
)
}
}
export default Parent
I need the event handler(Add) has to work inside the child component.
How to use event handler using props in class components.
How to call the parent Method in child class component in react.
I was tried using props but it was not working.
Based on the child component input button it has to get the result.
On Child class your component NavButton has an onClick attribute that calls onSetActive, so you can call investmentHandler on the onSetActive function:
//child component
import React, { Component } from "react";
const NavButton = ({ active, title, href, onSetActive }) => {
return (
<button
className={
active
? "btn btn-light regular-btn active"
: "btn btn-light regular-btn"
}
href={href}
onClick={onSetActive}
>
{title}
</button>
);
};
class Child extends Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 1,
buttons: [
{
title: "1",
key: 0,
value: 1,
},
{
title: "3",
key: 1,
value: 3,
},
],
};
}
//It was changing active index
handleChangeActive(newActiveIndex) {
// console.log("Working");
this.setState({ activeIndex: newActiveIndex });
this.props.investmentHandler();
}
render() {
const { activeIndex } = this.state;
return (
<div>
<nav id="navbarMain">
<div className="navbar-nav flex-row">
{this.state.buttons.map((button, buttonIndex) => (
/* determine which nav button is active depending on the activeIndex state */
<NavButton
value={button.value}
onSetActive={() => this.handleChangeActive(buttonIndex)}
active={buttonIndex === activeIndex}
title={button.title}
key={button.key}
/>
))}
</div>
</nav>
</div>
);
}
}
export default Child;
On the Parent class when getting the props from the Child class you was calling "this.add" when you should be calling "this.Add":
// parent component
import React, { Component } from 'react'
import Child from './Child';
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
firstValue: 1,
secondValue: 3,
result: ''
}
// this.add = this.add.bind(this);
}
//calculating the input values
Add = () => {
console.log('Im here')
var { firstValue, secondValue, result } = this.state;
result = firstValue + secondValue;
console.log(result);
document.getElementById("result").innerHTML = result;
}
render() {
return (
<>
<Child investmentHandler={this.Add} />
<p id="result">Result</p>
</>
)
}
}
export default Parent
I made this few changes and the code worked for me.
Check this might help
//child component
import React, { Component } from 'react'
const NavButton = ({ active, title, href, onSetActive }) => {
return (
<button
className={active ? "btn btn-light regular-btn active" : "btn btn-light regular-btn"}
href={href}
onClick={onSetActive} >
{title}
</button>
)
}
class Child extends Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 1,
buttons: [
{
title: "1",
key: 0,
value: 1
},
{
title: "3",
key: 1,
value: 3
}
]
}
}
//It was changing active index
handleChangeActive(newActiveIndex) {
// console.log("Working");
this.setState({ activeIndex: newActiveIndex });
}
render() {
const { activeIndex } = this.state;
return (
<div>
<nav id="navbarMain">
<div className="navbar-nav flex-row">
{this.state.buttons.map((button, buttonIndex) =>
/* determine which nav button is active depending on the activeIndex state */
<NavButton onClick={() => this.props.investmentHandler()} value={button.value} onSetActive={() => this.handleChangeActive(buttonIndex)} active={buttonIndex === activeIndex} title={button.title} key={button.key} />)}
</div>
</nav>
</div>
)
}
}
export default Child
// parent component
import React, { Component } from 'react'
import Child from './Child';
class Parent extends Component {
constructor(props) {
super(props)
this.state = {
firstValue: 1,
secondValue: 3,
result: ''
}
// this.add = this.add.bind(this);
}
//calculating the input values
Add = () => {
var { firstValue, secondValue, result } = this.state;
result = firstValue + secondValue;
console.log(result);
document.getElementById("result").innerHTML = result;
}
render() {
return (
<>
//Child component is used inside the parent component using props
<Child investmentHandler={this.Add} />
<p id="result">Result</p>
</>
)
}
}
export default Parent
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;
The question is rather simple and is not specifically connected to navigation bars, it's just a good example..
I want to create simple navigation bar with active tab highlighting by using 'active' class. Currently I have two components: NavBar and NavItem. On tab click, I would like to make it active and make sure all others become inactive.
What would be the easiest solution to this? How to reset multiple NavItems inside NavBar to default inactive state without one that was clicked?
NavBar.js
import React from 'react';
import NavItem from './NavItem';
export default class NavBar extends React.Component {
constructor() {
super();
this.menuItems = [
{ name: 'HOME', url: '#' },
{ name: 'NEWS', url: '#' },
{ name: 'CONTACT', url: '#' }
];
}
render() {
return (
<nav>
<ul>
CHOOSE:
{this.menuItems.map( (item,i) => <NavItem key={i} data={item} /> )}
</ul>
</nav>
);
}
}
NavItem.js
import React from 'react';
export default class NavItem extends React.Component {
constructor() {
super();
this.state = {
active: false
}
}
handleOnClick() {
this.setState({active: !this.state.active});
}
render() {
let item = this.props.data;
return <li><a href={item.url} className={this.state.active ? 'active' : ''} onClick={ (e) => this.handleOnClick(e) }>{item.name}</a></li>;
}
}
I don't see benefit of putting list item in a different component. In fact you would end up setting active class on 1 list item due to each item is a separate component.
Simplest thing could be to put list in the same component and pass an index in click function.
{this.menuItems.map((item,i) => <li><a href={item.url} className={this.state.activeItem == i ? 'active' : ''} onClick = {(e) => this.handleOnClick(e,i) }>{item.name}</a></li> )}
now click function would look something like -
handleOnClick = (event, index) => {
this.setState({activeItem: index});
}
So your component would look like
import React from 'react';
import NavItem from './NavItem';
export default class NavBar extends React.Component {
constructor() {
super();
this.menuItems = [
{
name: 'HOME',
url: '#'
}, {
name: 'NEWS',
url: '#'
}, {
name: 'CONTACT',
url: '#'
}
];
this.state = {
activeItem: 0
}
}
handleOnClick = (event, index) => {
this.setState({activeItem: index});
}
render() {
return (
<nav>
<ul>
CHOOSE: {this
.menuItems
.map((item, i) => <li>
<a
href={item.url}
className={this.state.activeItem == i
? 'active'
: ''}
onClick=
{(e) => this.handleOnClick(e,i) }>{item.name}</a>
</li>)}
</ul>
</nav>
);
}
}
I've setup item at 0 index default active but you could change it according to the needs.
Looks like the React Docs article, Lifting State Up , has been written exactly to the use-case you have been demonstrated.
You are managing the isActive state inside each component in isolation. Each Instance of NavItem can't know, and should not know, what is the state of the other instances of NavItem
So ... lift your state up! Manage the information about active/inactive bars, inside Navbar, and control your multiple NavItem instances through props.
It should look like this:
NavBar.js
import React from 'react';
import NavItem from './NavItem';
export default class NavBar extends React.Component {
constructor() {
super();
this.state = {
menuItems: [
{ name: 'HOME', url: '#', isActive: true },
{ name: 'NEWS', url: '#', isActive: false },
{ name: 'CONTACT', url: '#', isActive: false }
]
};
this.menuItems = [];
}
onNavItemClick = itemName => {
const nextMenuItems = this.state.menuItems.map(item => {
if (item.name === itemName) {
return { ...item, isActive: true };
}
return { ...item, isActive: false };
});
this.setState({
menuItems: nextMenuItems
});
};
render() {
return (
<nav>
<ul>
CHOOSE:
{this.state.menuItems.map((item, i) => (
<NavItem
key={item.name}
name={item.name}
isActive={item.isActive}
onClick={this.onNavItemClick}
url={item.url}
/>
))}
</ul>
</nav>
);
}
}
NavItem.js
import React from 'react';
export default class NavItem extends React.Component {
constructor() {
super()
console.log()
this.onClickFunction = () => this.props.onClick(this.props.name);
}
render() {
let item = this.props.data;
return (
<li className={this.props.isActive ? 'active' : ''}>
<a
href={this.props.url}
onClick={this.onClickFunction}
>
{this.props.name}``
</a>
</li>
);
}
}
Maybe you can use <NavLink to="" activeStyle={{color: 'red'}} /> component as links/tabs (from 'react-router-dom')
LiranC, Infinity, thanks for help and examples, I think I got it.
I knew it would end with lifting state to NavBar, only wasn't so sure how to make a reference to specific element..
I want to have only one element active at the time, with list item component for future purposes, so I mixed it and came up with such code. Looking forward to any further optimization advices.
NavBar.js
import React from 'react';
import NavItem from './NavItem';
export default class NavBar extends React.Component {
constructor() {
super();
this.menuItems = [
{ name: 'HOME', url: '#' },
{ name: 'NEWS', url: '#' },
{ name: 'CONTACT', url: '#' }
];
this.state = {
activeItem: null
}
}
handleOnClick = (e,i) => {
this.setState({activeItem: i});
}
render() {
return (
<nav>
<ul>
CHOOSE:
{this.menuItems.map( (item,i) =>
<NavItem
key={i}
idn={i}
data={item}
isActive={this.state.activeItem == i ? true : false}
onClick={this.handleOnClick} />
)}
</ul>
</nav>
);
}
}
NavItem.js
import React from 'react';
export default class NavItem extends React.Component {
render() {
let item = this.props.data;
return (
<li>
<a
href={item.url}
className={this.props.isActive ? 'active' : ''}
onClick={ (e) => this.props.onClick(e, this.props.idn) }>{item.name}
</a>
</li>
);
}
}
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} />)
I have to render several tabs on my dashboard, where I can navigate between them. Each tab must be reusable. In my sample code, I have 2 tabs and if I click on a tab, the matching tab renders. I just don't know if I am reasoning in the right way. How can I do this? My current Code is this:
import React, {Component} from 'react';
function SelectTab(props) {
var tabs = ['Overview', 'Favorites'];
return (
<ul>
{tabs.map(function(tab){
return (
<li key={tab} onClick={props.onSelect.bind(null, tab)}>
{tab}
</li>
)
}, this)}
</ul>
)
}
function Content(props) {
return (
<div>Content {props.tab}</div>
)
}
export default class MainContent extends Component {
constructor(props){
super(props);
this.state = {
selectedTab: 'Overview'
}
this.updateTab = this.updateTab.bind(this);
}
componentDidMount() {
this.updateTab(this.state.selectedTab);
}
updateTab(tab){
this.setState(function(){
return {
selectedTab: tab
}
});
}
render(){
return (
<div>
<SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>
<Content tab={this.state.selectedTab}/>
</div>
)
}
}
You did almost good. But I would rather render Content in SelectTab component as its already getting selectedTab prop, this way you can render specific content based on that prop. Also:
componentDidMount() {
this.updateTab(this.state.selectedTab);
}
is not necessary as state is already set.
Refactored example:
import React, { Component } from 'react';
function SelectTab(props) {
var tabs = ['Overview', 'Favorites'];
return (
<div>
<ul>
{tabs.map(function (tab) {
return (
<li key={tab} onClick={props.onSelect.bind(null, tab)}>
{tab}
</li>
)
}, this)}
</ul>
<Content tab={props.selectedTab}/>
</div>
)
}
function Content(props) {
return <div>Content {props.tab}</div>
}
export default class MainContent extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'Overview'
};
this.updateTab = this.updateTab.bind(this);
}
updateTab(tab) {
this.setState(function () {
return {
selectedTab: tab
}
});
}
render() {
return <SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>;
}
}
Also keep in mind that with proper babel-transpiling your code could be much simplified like this:
import React, { Component } from 'react';
const TABS = ['Overview', 'Favorites'];
const SelectTab = ({ selectedTab, onSelect }) => (
<div>
<ul>
{
TABS.map(
tab => <li key={tab} onClick={() => onSelect(tab)}> {tab} </li>
)
}
</ul>
<Content tab={selectedTab}/>
</div>
);
const Content = ({ tab }) => <div>Content {tab}</div>;
export default class MainContent extends Component {
state = {
selectedTab: 'Overview'
};
updateTab = tab => this.setState(() => ({ selectedTab: tab }));
render() {
return <SelectTab selectedTab={this.state.selectedTab} onSelect={this.updateTab}/>;
}
}