Button click renders multiple unwanted components React - javascript

My problem is this: I have a component that must be rendered 3 times in the app, and the component has a button which should update the component with a new component. I'm getting the placeholder component in each instance instead of just the component that triggered the event. My code:
class App extends Component {
constructor(){
super()
this.state = {showCard : false,cards : []}
this.buttonClick = this.buttonClick.bind(this)
}
buttonClick(ev){
console.log(ev.target)
const nextId = this.state.cards.length + 1
this.setState({cards: this.state.cards.concat([nextId])})
this.setState({showCard: true,})
}
render() {
console.log(this)
return (
<div className="App">
<h2>React Demo</h2>
<ul className="container">
<Contact key="0" text={"Contact 1"} buttonClick={this.buttonClick} showCard={this.state.showCard} cards={this.state.cards}/>
<Contact key="1" text={"Contact 2"} buttonClick={this.buttonClick} showCard={this.state.showCard} cards={this.state.cards}/>
<Contact key="2" text={"Contact 3"} buttonClick={this.buttonClick} showCard={this.state.showCard} cards={this.state.cards}/>
</ul>
</div>
);
}
}
function Contact(props){
return <li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.text} type="button" className="block" onClick={e =>props.buttonClick(e)}>+</button></li>
{props.cards.map(cardId => <Card key={props.text+cardId}/>)}
</ul>
</li>
}
function Card(){
return <li><div className="card">Place Holder</div></li>
}
export default App;
I have tried conditional rendering with showCard and mapping as seen here, in both cases all three of instances of the component are updated instead of the correct one. I know it's something stupid I'm doing, I just can't see it. Thanks in advance.
J

Updated code:
const list = [
{
id : 0,
title : "Contact 1",
showCard : false,
addCard : ()=> <Card key={"x"+count++}/>,
cards : []
},
{
id : 1,
title : "Contact 2",
showCard : false,
addCard : ()=> <Card key={"y"+count++}/>,
cards : []
},
{
id : 2,
title : "Contact 3",
showCard : false,
addCard : ()=> <Card key={"z"+count++}/>,
cards : []
}
]
let count = 0
class App extends Component {
constructor(){
super()
this.state = {list : list}
this.buttonClick = this.buttonClick.bind(this)
}
buttonClick(ev,id){
let a0 = null
for(let obj of this.state.list){
if(obj.id === id){
a0 = obj.addCard()
obj.cards.push(a0)
}
}
this.setState({list:this.state.list})
}
render() {
return (
<div className="App">
<h2>React Demo</h2>
<ul className="container">
{this.state.list.map((item) =>
<Contact key={item.title+item.id} text={item.title} buttonClick={this.buttonClick} showCard={item.showCard} id={item.id} cards={item.cards}/>
)}
</ul>
</div>
);
}
}
function Contact(props){
return <li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.text} type="button" className="block" onClick={e =>props.buttonClick(e,props.id)}>+</button></li>
{props.cards.map((card)=> {
return card || null
})}
</ul>
</li>
}
function Card(){
return <li><div className="card">Place Holder</div></li>
}
export default App;
As Jonas H suggested I was sharing state with all three instances of the Contact component. I've only been doing React for a couple of weeks hence the time it took to solve this. My solution may not be optimal, but it works, although it did break my UI... but that's another mission. Thanks to all.
J
Thanks to #Jonas H

shouldComponentUpdate() method will solve your problem
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior. more
shouldComponentUpdate(nextProps, nextState) {
// should return either true or fase
// component should render on state changes or initial render
if((JSON.stringify(nextState) !== JSON.stringify(this.state)) || (JSON.stringify(this.state) === (JSON.stringify({showCard : false,cards : []})) ) {
return true;
}
return false;
}
put above method to your code

Related

How do you update state in functional components in React?

I'm running into the issue where I have created a functional component to render a dropdown menu, however I cannot update the initial state in the main App.JS. I'm not really sure how to update the state unless it is in the same component.
Here is a snippet of my App.js where I initialize the items array and call the functional component.
const items = [
{
id: 1,
value:'item1'
},
{
id: 2,
value:'item2'
},
{
id: 3,
value:'item3'
}
]
class App extends Component{
state = {
item: ''
}
...
render(){
return{
<ItemList title = "Select Item items= {items} />
And here is my functional componenet. Essentially a dropdown menu from a YouTube tutorial I watched (https://www.youtube.com/watch?v=t8JK5bVoVBw).
function ItemList ({title, items, multiSelect}) {
const [open, setOpen] = useState (false);
const [selection, setSelection] = useState([]);
const toggle =() =>setOpen(!open);
ItemList.handleClickOutside = ()=> setOpen(false);
function handleOnClick(item) {
if (!selection.some(current => current.id == item.id)){
if (!multiSelect){
setSelection([item])
}
else if (multiSelect) {
setSelection([...selection, item])
}
}
else{
let selectionAfterRemoval = selection;
selectionAfterRemoval = selectionAfterRemoval.filter(
current =>current.id == item.id
)
setSelection([...selectionAfterRemoval])
}
}
function itemSelected(item){
if (selection.find(current =>current.id == item.id)){
return true;
}
return false;
}
return (
<div className="dd-wraper">
<div tabIndex={0}
className="dd-header"
role="button"
onKeyPress={() => toggle(!open)}
onClick={() =>toggle(!open)}
onChange={(e) => this.setState({robot: e.target.value})}
>
<div className="dd-header_title">
<p className = "dd-header_title--bold">{title}</p>
</div>
<div className="dd-header_action">
<p>{open ? 'Close' : 'Open'}</p>
</div>
</div>
{open && (
<ul className ="dd-list">
{item.map(item =>(
<li className="dd-list-item" key={item.id}>
<button type ="button"
onClick={() => handleOnClick(item)}>
<span>{item.value}</span>
<span>{itemSelected(item) && 'Selected'}</span>
</button>
</li>
))}
</ul>
)}
</div>
)
}
const clickOutsideConfig ={
handleClickOutside: () => RobotList.handleClickOutside
}
I tried passing props and mutating the state in the functional component, but nothing gets changed. I suspect that it needs to be changed in the itemSelected function, but I'm not sure how. Any help would be greatly appreciated!
In a function component, you have the setters of the state variables. In your example, you can directly use setOpen(...) or setSelection(...). In case of a boolean state variable, you could just toggle by using setOpen(!open). See https://reactjs.org/docs/hooks-state.html (Chapter "Updating State") for further details.
So you need to do something like below . Here we are passing handleChange in parent Component as props to the child component and in Child Component we are calling the method as props.onChange
Parent Component:
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
value :''
}
}
handleChange = (newValue) => {
this.setState({ value: newValue });
}
render() {
return <Child value={this.state.value} onChange = {this.handleChange} />
}
}
Child Component:
function Child(props) {
function handleChange(event) {
// Here, we invoke the callback with the new value
props.onChange(event.target.value);
}
return <input value={props.value} onChange={handleChange} />
}

Controlled Input onChange Event Fires Only Once - React

Only one key press is registered, then the input loses focus however I can click back into the component and add ... one character. State is updated.
Since the child components are state-less I assumed that just passing the handlers down as props as described in the docs would be sufficient, but everything from changing the binding for the method to adding a constructor have yielded the same results. I can usually find an answer on the site but no luck this time.
const list = [
{
id : 0,
title : "Went well",
showCard : false,
cards : [],
color : "pink"
},
{
id : 1,
title : "To Improve",
showCard : false,
cards : [],
color : "yellow"
},
{
id : 2,
title : "Action Items",
showCard : false,
cards : [],
color : "blue"
}
]
class App extends Component {
state = {list : list, usrInpt : ""}
handleChange = e => {this.setState({usrInpt:e.target.value})}
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
this.setState({list:updatedList})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}
render() {
return (
<div className="App">
<h2>Metro Board</h2>
<ul className="container">
{this.state.list.map((item) =>
<List key={(Math.random() * 1)} text={item.title}
id={item.id} cards={item.cards} color={item.color}
usrInpt={this.state.usrInpt} add={this.add} handleChange={this.handleChange}/>
)}
</ul>
</div>
)
}
}
const List = (props) =>{
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <ListItem key={(Math.random() * 1)}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
}
const ListItem = (props) =>{
console.log("card props and value",props)
return <li>
<div className="card" style={{backgroundColor: props.color}}>
<textarea type="text"
className="card"
placeholder="Enter text here"
value={props.usrInpt}
onChange={e => props.handleChange(e)}>
</textarea>
<div><a className="ltCtl" href="./logo" onClick={e => console.log(e)}><</a>
<a className="clCtl" href="./logo" onClick={e => console.log(e)}>x</a>
<a className="rtCtl" href="./logo" onClick={e => console.log(e)}>></a>
</div>
</div>
</li>
}
Both List && ListItem are separate files... Any help would be great. Thanks.
UPDATE:
I was able to reach out to a full time developer and it seems I screwed up by trying to make unique keys :
The key needs to be consistent, but in this case it is a different value every time
React uses the key when it IDs which element is focusing on, but in this case, it is different than the last render. So React does not know what to focus on. You can have unique keys if you use a string with the index of the loop in it, or if you use an ID that you store outside in the loop, like in state
It does need to be unique, but it also needs to be consistent.
So the code:
return (
<Card
key={Math.random() * 1} // <--- Don't!!
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}
/>
);
was causing React to lose track of what to render since the keys where changing with each render. The preferred method is using a string interpolation with an identifier and an index if a loop is used:
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <Card key={`card-column-${props.id}-${i}`}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
Which was also a comment made by #miyu ... which I did not test. Listen to your peers and mentors... you will not lose 12 hours chasing bugs. Thanks.
state is non-hierarchical. Meaning, when you update a child object of your state but the parent object is not updated, then react will not trigger componentDidChange.
Try adding a counter which gets updated when the list is updated.
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
let counter = this.state.counter || 0;
this.setState({list:updatedList, counter: counter++})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}

Toggle Two Things Separately React Constructor

I'm trying to toggle two different dropdown menus and can't seem to get it working. New to react and have probably been looking at it too long and it's something simple. The problem is when I toggle one the other gets toggled as well, so they both show.. Here is what I have:
import React from "react";
import { Link } from "./component/link";
import styles from "./header.module.css";
class Header extends React.Component {
constructor(props) {
super ( props )
this.state = {
show : false
}
this.toggleBusiness = this.toggleBusiness.bind(this);
this.state = {
show : false
}
this.togglePersonal = this.togglePersonal.bind(this);
}
toggleBusiness = () => {
const { show } = this.state;
this.setState( { show : !show } )
}
togglePersonal = () => {
const { show } = this.state;
this.setState( { show : !show } )
}
render() {
return (
<div className={ styles.topNav} >
<div className="grid">
<div className="grid-cell">
<div className={ styles.logoText }>
Logo
</div>
</div>
<nav>
<div className="grid-cell">
<ul className="list-unstyled">
<li><Link to={'/design'}>About</Link></li>
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.toggleBusiness}>Personal</a></li>
<li><Link to={'/posts'}>Blog</Link></li>
<li><Link to={'/contact'}>Contact</Link></li>
<li className={styles.menuButton}><a className="button button-secondary" href="tel:2252931086">File a Claim</a></li>
<li className={styles.menuButton}><a className="button" href="/">Get Insurance</a></li>
</ul>
</div>
</nav>
</div>
{ this.state.show && <BusinessDropdown /> }
{ this.state.show && <PersonalDropdown /> }
</div>
)}
}
class BusinessDropdown extends React.Component {
render () {
return (
<div className="dropdown">BusinessTest</div>
)
}
}
class PersonalDropdown extends React.Component {
render () {
return (
<div className="dropdown">PersonalTest</div>
)
}
}
export default Header;
So basically I want it to toggle the Business Dropdown one when I click Business and the Personal Dropdown when I press Personal. Also, if you have something that would work better than this approach let me know!
Change your toggleBusiness and togglePersonal to this:
toggleBusiness = () => {
const { show } = this.state;
this.setState({ show: show === "business" ? null : "business" });
};
togglePersonal = () => {
const { show } = this.state;
this.setState({ show: show === "personal" ? null : "personal" });
};
then in the conditional rendering, do this:
{this.state.show === "business" && <BusinessDropdown />}
{this.state.show === "personal" && <PersonalDropdown />}
...also in your links, you have this:
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.toggleBusiness}>Personal</a></li>
Where you should have this:
<li><a onClick={this.toggleBusiness}>Business</a></li>
<li><a onClick={this.togglePersonal}>Personal</a></li>
Edit: I realise this is not quite what you asked for - this toggles business off when personal is switched on. Personally I think this approach would actually suit better given the fact you're opening a new dropdown menu, you will probably want the other one to close.
You are using this.state.show for both Business and Personal.
Adding a second state variable like showBusiness and showPersonal
should solve your problem.
Also you can/should declare your state only once with
this.state = {
showBusiness: false,
showPersonal: false
};
In your constructor you set this.state.show to false 2 times separate this into two separate variables, perhaps this.state.showBuisness and this.state.showPersonal?
My approach would be something like this.
1. set initial state
constructor(props){
super(props);
this.state={
business: false,
personal: false
}
}
2. Create ONE function to update both status.
handleClick = (e) => {
this.setState(prevState => {
[e.target.id]: !prevState[e.target.id]
}
}
3. Add function to the onclick AND an id
<button id="personal" onClick={this.handleClick}>SHOW PERSONAL</button>

ReactJS selecting an element uniquely from a map

I am doing a todo app to practice React. I hit a blocker and now I'm trying to figure out how to uniquely edit a card.
Currently when I click on edit, all my cards are set to isEditing == true. I've tried adding a key and index, but doesn't seem to uniquely identify the selected card.
As seen in my gif:
Obviously the expected outcome is that it should only set isEditing == true to the selected card.
See Code below.
For more context: there is stateful component that passes the props to this component, I'm using react-bootstrap (hence Panel, Button), and I removed some code for brevity (construct and whatnot).
edit() {
this.setState({
isEditing: true
})
}
renderEditDoneButtons() {
return (
<div>
<Button onClick={this.edit}>edit</Button>
</div>
)
}
renderNote(note) {
return (
<p> {note} </p>
)
}
renderCard(note, i) {
return (
<Panel key={i}
index={i}>
{
this.state.isEditing ?
this.renderForm() :
this.renderNote(note.note)
}
</Panel>
)
}
render() {
return (
<div>
{this.props.notes.map(this.renderCard)}
</div>
)
}
All three are changing based on your single isEditing state, which is why you're seeing all three being shown when you click any of the "Edit" buttons. Instead of a single isEditing key in state, use an array to maintain all three states like so:
constructor(props) {
super(props);
// Sets a true/false editing state for all three panels
this.state = {
editingPanels: Array(3).fill(false)
}
}
edit(i) {
// Switches editing state to false/true for given i
const editingPanels = this.state.editingPanels.slice();
editingPanels[i] = !editingPanels[i];
this.setState({
editingPanels: editingPanels
})
}
renderEditDoneButtons(i) {
return (
<div>
<Button onClick={()=>this.state.edit(i)}>edit</Button>
</div>
)
}
renderNote(note) {
return (
<p> {note} </p>
)
}
renderCard(note, i) {
return (
<Panel key={i}
index={i}>
{
this.state.editingPanels[i] ?
this.renderForm() :
this.renderNote(note.note)
}
</Panel>
)
}
render() {
return (
<div>
{this.props.notes.map(this.renderCard)}
</div>
)
}
You can use a separate component for each todo list item and use it inside the map method.The following example gives an idea on how to implement this.I am using another example as you have not provided the full code.
class EditText extends React.Component {
constructor(props) {
super(props)
this.state = {value:props.data,newValue:'hi'}
this.editValue = this.editValue.bind(this)
}
editValue() {
this.setState({value:this.state.newValue})
}
render() {
return(
<div>
{this.state.value}
<button onClick={this.editValue}>Change text to Hi</button>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {tempDate : ['hello','how']}
}
render() {
return (
<div className="App">
{this.state.tempDate.map(data=>(<EditText data={data}/>))}
</div>
);
}
}
You need to have state variable isEditing for each particular card.
If there are 3 cards, you need to have 3 variables.
Edit 1 :-
Example is already shared by Kody R.
One Thing i noticed is instead of hard-coding array size to 3,we could assign array size by number of notes recieved in props.
this.state = {
editingPanels: Array(3).fill(false)
}
To
this.state = {
editingPanels: Array(this.props.notes.length).fill(false)
}
Hope this helps,
Cheers !!

rendering multiple elements after onClick event in React

I'm having problems trying to render two react elements inside a react component after a onClick event. Wondering if that's even possible? I'm sure I'm messing up the ternary operator, but I cannot think on another way to do what I'm trying to do ?
TL;DR: "When I click a button I see elementA and elementB"
Here is a snippet of the code:
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = { showElement: true };
this.onHandleClick = this.onHandleClick.bind(this);
}
onHandleClick() {
console.log(`current state: ${this.state.showElement} and prevState: ${this.prevState}`);
this.setState(prevState => ({ showElement: !this.state.showElement }) );
};
elementA() {
<div>
<h1>
some data
</h1>
</div>
}
elementB() {
<div>
<h1>
some data
</h1>
</div>
}
render() {
return (
<section>
<button onClick={ this.onHandleClick } showElement={this.state.showElement === true}>
</button>
{ this.state.showElement
?
null
:
this.elementA() && this.elementB()
}
</section>
)
}
}
export default MyComponent;
You just inattentive.
elementA() {
return ( // You forget
<div>
<h1>
some data
</h1>
</div>
)
}
And the same in element B.
And if You want to see both components you should change Your ternary to
{ this.state.showElement
?
<div> {this.elementA()} {this.elementB()}</div>
:
null
}
Another "and", for toggling showElement in state just enough
this.setState({showElement: !this.state.showElement });
Try this instead, (I will add comments into the code trying to explain what's going on):
function SomeComponentName() { // use props if you want to pass some data to this component. Meaning that if you can keep it stateless do so.
return (
<div>
<h1>
some data
</h1>
</div>
);
}
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = { showElement: false }; // you say that initially you don't want to show it, right? So let's set it to false :)
this.onHandleClick = this.onHandleClick.bind(this);
}
onHandleClick() {
this.setState(prevState => ({ showElement: !prevState.showElement }) );
// As I pointed out in the comment: when using the "reducer" version of `setState` you should use the parameter that's provided to you with the previous state, try never using the word `this` inside a "reducer" `setState` function
};
render() {
return (
<section>
<button onClick={ this.onHandleClick } showElement={this.state.showElement === false}>
</button>
{ this.state.showElement
? [<SomeComponentName key="firstOne" />, <SomeComponentName key="secondOne" />]
: null
}
</section>
)
}
}
export default MyComponent;

Categories