I'm trying to render component onClick and the problem I faced is that my onClick opens component in every row not only in the one I clicked button.
It's probably any index issue. Could you take a look at my code? Thanks!
class ItemView extends Component {
constructor(props) {
super(props);
this.state = {
isFormOpen: false,
parentId: null,
}
}
handleAddItem = (value) => {
this.setState({
parentId: value.parentId,
isFormOpen: !this.state.isFormOpen,
})
}
render() {
return (
<div className="ui relaxed divided list">
{data.items.map((item, index) => {
return (
<div className="item" key={index}>
<Button
label={'Add Item'}
onClick={() => this.handleAddItem({ parentId: item.parentId })}
/>
{isFormOpen && (
<AddItem parentId={parentId} />
)}
</div>
)
})}
</div>
)
}
}
add check if parentId is equal to item.parentId:
{isFormOpen && parentId === item.parentId && (
<AddItem parentId={parentId} />
)}
You are right.
For this you have to use Other Component. In this component because you are using map to render the element so it renders it in every time or row.
That's why when isFormOpen will be true so it'll be true on all items/rows.
Please use other component to render the conditional element.
Related
I want to have 3 buttons and 3 related components . So that when button click show its related component . Here is my code :
Main Component :
class Main extends Component { constructor(props) {
super(props);
this.state = {
show: false,
}}
onClick(e) {
e.preventDefault()
console.log(e.target.id)
this.setState({ show: !this.state.show }); }
renderTabs() {
var child = this.props.child;
var items = [];
__.forEach(child, (item, index) => {
items.push(
<div>
<Button id={index} onClick={(e) => this.onClick(e)}>{item.data}</Button>
{this.state.show ? (
<CropComponent title={item.data} opts={item.opts} data={item.child} />
) : null}
</div>
);
});
return items;
}
render() {
var opts = this.props.opts;
return (
<div>
{this.renderTabs()}
</div>
)}
}
export default Main;
And here is my CropComponent :
class CropComponent extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div>hello {this.props.data}</div>
);
}
}
export default CropComponent;
Here are my buttons how can i show its related component when click on a button and hide it on re-click the same button ?
Maintain a show state with initial value as -1. Supply event and index to onClick. In onClick do setState of show as index.
Check if show === index and render the corresponding component.
Like this
this.state = {
show: -1
}
onClick(e,index) {
e.preventDefault()
this.setState({ show: show===index? -1 : index});
}
__.forEach(child, (item, index) => {
items.push(
<div>
<Button id={index} onClick={(e) => this.onClick(e,index)}>{item.data}</Button>
{this.state.show === index ? (
<CropComponent title={item.data} opts={item.opts} data={item.child} />
) : null}
</div>
);
});
Edit:
updated the answer based on helpful comment by #Tony Nguyen
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
I have 4 different divs each containing their own button. When clicking on a button the div calls a function and currently sets the state to show a modal. Problem I am running into is passing in the index of the button clicked.
In the code below I need to be able to say "image0" or "image1" depending on the index of the button I am clicking
JS:
handleSort(value) {
console.log(value);
this.setState(prevState => ({ childVisible: !prevState.childVisible }));
}
const Features = Array(4).fill("").map((a, p) => {
return (
<button key={ p } onClick={ () => this.handleSort(p) }></button>
)
});
{ posts.map(({ node: post }) => (
this.state.childVisible ? <Modal key={ post.id } data={ post.frontmatter.main.image1.image } /> : null
))
}
I would suggest:
saving the button index into state and then
using a dynamic key (e.g. object['dynamic' + 'key']) to pick the correct key out of post.frontmatter.main.image1.image
-
class TheButtons extends React.Component {
handleSort(value) {
this.setState({selectedIndex: value, /* add your other state here too! */});
}
render() {
return (
<div className="root">
<div className="buttons">
Array(4).fill("").map((_, i) => <button key={i} onClick={() => handleSort(i)} />)
</div>
<div>
posts.map(({ node: post }) => (this.state.childVisible
? <Modal
key={ post.id }
data={ post.frontmatter.main.[`image${this.state.selectedIndex}`].image }
/>
: null
))
</div>
</div>
);
}
}
This is a good answer which explains "Dynamically access object property using variable": https://stackoverflow.com/a/4244912/5776910
I'd like to add a new input everytime the plus icon is clicked but instead it always adds it to the end. I want it to be added next to the item that was clicked.
Here is the React code that I've used.
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add</i>
<i>Remove</i>
</div>
</div>
);
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [Input]
};
}
addInput = index => {
this.setState(prevState => ({
choices: update(prevState.choices, { $splice: [[index, 0, Input]] })
}));
};
render() {
return (
<div>
{this.state.choices.map((Element, index) => {
return (
<Element
key={index}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
<div id="app"></div>
<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>
I must admit this get me stuck for a while but there was a problem with how react deals with key props. When you use an index as a key it doesn't work. But if you make sure inputs will always be assigned the same key even when the list changes it will work as expected:
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add </i>
<i>Remove</i>
</div>
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [],
nrOfElements: 0
};
}
addInput = index => {
this.setState(prevState => {
const choicesCopy = [...prevState.choices];
choicesCopy.splice(index, 0, `input_${prevState.nrOfElements}`);
return {
choices: choicesCopy,
nrOfElements: prevState.nrOfElements + 1
};
});
};
componentDidMount() {
this.addInput(0);
}
render() {
return (
<div>
{this.state.choices.map((name, index) => {
return (
<Input
key={name}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
Some reference from the docs:
Keys should be given to the elements inside the array to give the
elements a stable identity...
...We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state.
Let's say I have a series of static divs that create rows with a handful of children. One of those children has a click handler, which should fire an event specific to its parent div. The event needs to target the parent div because we're changing some styling only in the parent.
Am I correct in how I've targeted each child's parentNode, in my code below? (Basically, is this best practice?) Just curious.
Thanks!
class ClickExample extends Component {
handleClick = (e) => {
const parentDiv = e.target.parentNode;
parentDiv.classList.toggle('someClass');
}
render() {
return (
<div>
<div>
<h1 onClick={(e) => { this.handleClick(e) }}>Click Me!</h1>
</div>
<div>
<h1 onClick={(e) => { this.handleClick(e) }}>No, Click Me!</h1>
</div>
</div>
);
}
}
export default ClickExample
This approach is against React philosophy. You should define a component for this purpose. You should read a little bit more of React philosophy but the correct approach would be something like this:
class ClickableComponent extends React.Component {
render() {
return (
<div onClick={this.props.onClick}>
{ this.props.children }
</div>
);
}
}
class ClickExample extends React.Component {
handleClick() {
this.setState({
active: null
});
}
render() {
return (
<div>
<div className={this.state.active === 1 ? 'someClass' : null}>
<ClickableComponent onClick={() => this.setState({ active: 1 })}>
ClickMe
</ClickableComponent>
</div>
<div className={this.state.active === 2 ? 'someOtherClass' : null}>
<ClickableComponent onClick={() => this.setState({ active: 2 })}>
No, ClickMe!
</ClickableComponent>
</div>
</div>
);
}
}
export default ClickExample