Show and Hide specific component in React from a loop - javascript

I have a button for each div. And when I press on it, it has to show the div with the same key, and hide the others.
What is the best way to do it ? This is my code
class Main extends Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", key: "1" },
{ message: "message2", key: "2" }
]
};
}
handleClick(message) {
//something to show the specific component and hide the others
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<Button key={message.key} onClick={e => this.handleClick(message)}>
{message.message}
</Button>
)
});
let messageNodes2 = this.state.messages.map(message => {
return <div key={message.key}>
<p>{message.message}</p>
</div>
});
return <div>
<div>{messageNodes}</div>
<div>{messageNodes2}</div>
</div>
}
}

import React from "react";
import { render } from "react-dom";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", id: "1" },
{ message: "message2", id: "2" }
],
openedMessage: false
};
}
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<button key={message.id} onClick={e => this.handleClick(message.id)}>
{message.message}
</button>
);
});
let messageNodes2 = this.state.messages.map(message => {
return (
<div key={message.key}>
<p>{message.message}</p>
</div>
);
});
const { openedMessage } = this.state;
console.log(openedMessage);
return (
<div>
{openedMessage ? (
<div>
{openedMessage.map(item => (
<div>
{" "}
{item.id} {item.message}{" "}
</div>
))}
</div>
) : (
<div> Not Opened</div>
)}
{!openedMessage && messageNodes}
</div>
);
}
}
render(<Main />, document.getElementById("root"));
The main concept here is this following line of code.
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}`
When we map our messageNodes we pass down the messages id. When a message is clicked the id of that message is passed to the handleClick and we filter all the messages that do not contain the id of the clicked message. Then if there is an openedMessage in state we render the message, but at the same time we stop rendering the message nodes, with this logic {!openedMessage && messageNodes}

Something like this. You should keep in state only message key of visible component and in render method you should render only visible component based on the key preserved in state. Since you have array of message objects in state, use it to render only button that matches the key.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
//My array messages: [],
visibleComponentKey: '',
showAll: true
};
handleClick(message) {
//something to show the specific component and hide the others
// preserve in state visible component
this.setState({visibleComponentKey : message.key, showAll: false});
};
render() {
const {visibleComponentKey, showAll} = this.state;
return (
<div>
{!! visibleComponentKey && ! showAll &&
this.state.messages.filter(message => {
return message.key == visibleComponentKey ? <Button onClick={e => this.handleClick(message)}>{message.message}</Button>
) : <div /> })
}
{ !! showAll &&
this.state.messages.map(message => <Button key={message.key} onClick={e => this.handleClick(message)}>{message.message}</Button>)
}
</div>
);
}
}
I haven't tried it but it gives you a basic idea.

I cannot reply to #Omar directly but let me tell you, this is the best code explanation for what i was looking for! Thank you!
Also, to close, I added a handleClose function that set the state back to false. Worked like a charm!
onCloseItem =(event) => {
event.preventDefault();
this.setState({
openedItem: false
});
}

Related

Why can't I add any value to the array in state?

I have a lot of hits, which I want to add to an array once a hit is pressed. However, as far as I observed, the array looked like it got the name of the hit, which is the value. The value was gone in like half second.
I have tried the methods like building constructor, and doing things like
onClick={e => this.handleSelect(e)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
defaultValue={hit.name}
and so on
export default class Tagsearch extends Component {
constructor(props) {
super(props);
this.state = {
dropDownOpen:false,
text:"",
tags:[]
};
this.handleRemoveItem = this.handleRemoveItem.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
}
handleSelect = (e) => {
this.setState(
{ tags:[...this.state.tags, e.target.value]
});
}
render() {
const HitComponent = ({ hit }) => {
return (
<div className="infos">
<button
className="d-inline-flex p-2"
onClick={e => this.handleSelect(e)}
value={hit.name}
>
<Highlight attribute="name" hit={hit} />
</button>
</div>
);
}
const MyHits = connectHits(({ hits }) => {
const hs = hits.map(hit => <HitComponent key={hit.objectID} hit={hit}/>);
return <div id="hits">{hs}</div>;
})
return (
<InstantSearch
appId="JZR96HCCHL"
apiKey="b6fb26478563473aa77c0930824eb913"
indexName="tags"
>
<CustomSearchBox />
{result}
</InstantSearch>
)
}
}
Basically, what I want is to pass the name of the hit component to handleSelect method once the corresponding button is pressed.
You can simply pass the hit.name value into the arrow function.
Full working code example (simple paste into codesandbox.io):
import React from "react";
import ReactDOM from "react-dom";
const HitComponent = ({ hit, handleSelect }) => {
return <button onClick={() => handleSelect(hit)}>{hit.name}</button>;
};
class Tagsearch extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: []
};
}
handleSelect = value => {
this.setState(prevState => {
return { tags: [...prevState.tags, value] };
});
};
render() {
const hitList = this.props.hitList;
return hitList.map(hit => (
<HitComponent key={hit.id} hit={hit} handleSelect={this.handleSelect} />
));
}
}
function App() {
return (
<div className="App">
<Tagsearch
hitList={[
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" }
]}
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
additionally:
note the use of prevState! This is a best practice when modifying state. You can google as to why!
you should define the HitComponent component outside of the render method. it doesn't need to be redefined each time the component is rendered!

Accessing updated props from parent stateless function component from child stateless class component

child component
export class child extends Component {
buttonclick() {
const { pin } = this.props
if (pin === null) {
add().then(result => {
updatePin(result.data)
})
} else {
remove(pin.id).then(result => {
updatePin(result)
})
}
}
render() {
const { pin } = this.props
const label =
pin === null
? 'yes'
: 'no'
const icon =pin === null ? 'yes' : 'no'
return (
<div>
<Button
icon={icon}
label={label}
onClick={() => this.buttonclick()}
/>
</div>
)
}
}
parent component(classless component)
const parent= props =>{
const { pins = []} = props
const { pin } = Data
}
const updatePin = result => {
// here iam updating the pin
}
const renderchildComponent=()=>{
return(
<div>
<ChildComponent
pin={pin}
updatePin={result => updatePin(result)}
/>
</div>
)
}
here in the above code the pin is updating in the parent component but how to pass it to child component every time when the button click happens without refreshing the page. please help me out with this

How to toggle css class of a single element in a .map() function in React

I have a .map() function where I'm iterating over an array and rendering elements, like so:
{options.map((option, i) => (
<TachyonsSimpleSelectOption
options={options[i]}
key={i}
onClick={() => this.isSelected(i)}
selected={this.toggleStyles("item")}
/>
I am toggling the state of a selected element like so:
isSelected (i) {
this.setState({ selected: !this.state.selected }, () => { console.log(this.state.selected) })
}
Using a switch statement to change the styles:
toggleStyles(el) {
switch (el) {
case "item":
return this.state.selected ? "bg-light-gray" : "";
break;
}
}
And then passing it in my toggleStyles method as props to the className of the TachyonsSimpleSelectOption Component.
Problem
The class is being toggled for all items in the array, but I only want to target the currently clicked item.
Link to Sandbox.
What am I doing wrong here?
You're using the selected state incorrectly.
In your code, to determine whether it is selected or not, you depends on that state, but you didn't specify which items that is currently selected.
Instead saving a boolean state, you can store which index is currently selected so that only specified item is affected.
This may be a rough answer, but I hope I can give you some ideas.
on your render:
{options.map((option, i) => (
<TachyonsSimpleSelectOption
options={options[i]}
key={i}
onClick={() => this.setState({ selectedItem: i })}
selected={this.determineItemStyle(i)}
/>
))}
on the function that will determine the selected props value:
determineItemStyle(i) {
const isItemSelected = this.state.selectedItem === i;
return isItemSelected ? "bg-light-gray" : "";
}
Hope this answer will give you some eureka moment
You are not telling react which element is toggled. Since the state has just a boolean value selected, it doesn't know which element is selected.
In order to do that, change your isSelected function to :
isSelected (i) {
this.setState({ selected: i }, () => {
console.log(this.state.selected) })
}
Now, the React state knows that the item on index i is selected. Use that to toggle your class now.
In case you want to store multiple selected items, you need to store an array of indices instead of just one index
TachyonsSimpleSelectOption.js:
import React from 'react';
class Option extends React.Component {
render() {
const { selected, name } = this.props;
return(
<h1
onClick={() => this.props.onClick()}
style={{backgroundColor: selected ? 'grey' : 'white'}}
>Hello {name}!</h1>
)
}
}
export default Option;
index.js:
import React from "react";
import { render } from "react-dom";
import TachyonsSimpleSelectOption from "./TachyonsSimpleSelectOption";
const options = ["apple", "pear", "orange"];
const styles = {
selected: "bg-light-gray"
};
class Select extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
selected: []
};
this.handleClick = this.handleClick.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.isSelected = this.isSelected.bind(this);
}
handleBlur() {
this.toggleMenu(close);
}
handleClick(e) {
this.toggleMenu();
}
toggleMenu(close) {
this.setState(
{
open: !this.state.open
},
() => {
this.toggleStyles("menu");
}
);
}
toggleStyles(el, index) {
switch (el) {
case "menu":
return this.state.open ? "db" : "dn";
break;
case "item":
const { selected } = this.state;
return selected.indexOf(index) !== -1;
break;
}
}
isSelected(i) {
let { selected } = this.state;
if (selected.indexOf(i) === -1) {
selected.push(i);
} else {
selected = selected.filter(index => index !== i);
}
this.setState({ selected});
}
render() {
const { options } = this.props;
return (
<div
className="flex flex-column ba"
onBlur={this.handleBlur}
tabIndex={0}
>
<div className="flex-row pa3" onClick={this.handleClick}>
<span className="flex-grow-1 w-50 dib">Title</span>
<span className="flex-grow-1 w-50 dib tr">^</span>
</div>
<div className={this.toggleStyles("menu")}>
{options.map((option, i) => (
<TachyonsSimpleSelectOption
name={options[i]}
key={i}
onClick={() => this.isSelected(i)}
selected={this.toggleStyles("item", i)}
/>
))}
</div>
</div>
);
}
}
render(<Select options={options} />, document.getElementById("root"));
And Link to Sandbox.

React.JS - multiple elements sharing a state ( How do I modify only one of the elements without affecting the others? )

class App extends Component {
constructor(props) {
super(props);
this.state = { Card: Card }
}
HandleEvent = (props) => {
this.SetState({Card: Card.Active}
}
render() {
return (
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
<Card Card = { this.state.Card } HandleEvent={
this.handleEvent }/>
)
}
}
const Card = props => {
return (
<div style={props.state.Card} onClick={
props.HandleEvent}>Example</div>
)
}
Every time I click on one of the cards all of my elements change states, how do I program this to only change card that I clicked?
Here's a working example
import React, { Component } from 'react'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
0: false,
1: false
};
}
handleEvent(idx) {
const val = !this.state[idx];
this.setState({[idx]: val});
}
render() {
return (
<div>
<Card state={this.state[0]} handleEvent={()=>this.handleEvent(0) } />
<Card state={this.state[1]} handleEvent={()=>this.handleEvent(1) } />
</div>
);
}
}
const Card = (props) => {
return (<div onClick={() => props.handleEvent()}>state: {props.state.toString()}</div>);
}
You can also see it in action here
Obviously this is a contrived example, based on your code, in real world application you wouldn't store hardcoded state like {1: true, 2: false}, but it shows the concept
It's not completely clear from the example what is the Card in the constructor. But here the example of how you can modify clicked element.
Basically you can keep only index of clicked element in parent's state, and then pass it as some property to child component, i.e. isActive here:
const cards = [...arrayOfCards];
class App extends Component {
constructor(props) {
super(props);
this.state = { activeCardIndex: undefined }
}
HandleEvent = (index) => {
this.SetState({
activeCardIndex: index
});
}
render() {
return ({
// cards must be iterable
cards.map((card, index) => {
return (
<Card
key={index}
Card={Card}
isActive={i === this.state.activeCardIndex}
HandleEvent={this.HandleEvent.bind(this, index)}
/>
);
})
});
}
}
const Card = props => {
// style active card
const style = Object.assign({}, props.Card, {
backgroundColor: props.isActive ? 'orange' : 'white',
});
return (
<div style={style} onClick={
props.HandleEvent}>Example</div>
)
}

React remove element from onclick

When a user deletes an item from their cart, I have the item displayed with a button to add it back to the cart. This works. Once the user adds the item back to their cart, I want the item in the display component to be removed. Here is my code for reference.
CART:
class Cart extends Component {
constructor(props) {
super(props);
this.state = {differences: [],};
}
componentWillReceiveProps(nextProps){
let thisProps = this.props.cart.items;
let theNextProps = nextProps.cart.items;
if (thisProps.map(i => i.sku).some(item => !theNextProps.map(i => i.sku).includes(item))) {
let diff = [thisProps.filter(item => !theNextProps.includes(item))];
this.setState({differences: this.state.differences.concat(diff)});
}
}
...
render = () => {
<CartAddBack data={this.state.differences} onAddToCart={this.props.addToCart} />
<CheckoutSection className='Checkout-cart-items' titleKey='checkout.items.title'>
{this.props.cart.items.map((item) => {
return (
<CheckoutItem item={item} key={item.sku} onRemoveProduct={this.props.removeFromCart} onUpdateQuantity={this.props.updateCartItem}/>
);
})}
</CheckoutSection>
}
}
CartAddBack:
class CartAddBack extends Component {
constructor() {
super();
this.state = {deleted: null};
this.onDelete = this.onDelete.bind(this);
}
onDelete(id){
console.log("THE SKU SHOULD BE HERE", id);
this.setState(id);
}
render() {
let {data} = this.props;
let theData = data.map(i => parseInt(i[0].sku));
let theStated = this.state.deleted;
return (
<div>
{data &&
<div className="CartAddBack">
<div className="CartAddBack-Wrapper">
<ul className="CartAddBack-Item-ul">
{theStated != null
? theData.filter(i => !theStated.includes(i)) &&
<CartAddBackItem data={item[0]} onAddToCart={this.props.onAddToCart} onDelete={this.onDelete}/>
: data.map((item) => {
return <CartAddBackItem data={item[0]} onAddToCart={this.props.onAddToCart} onDelete={this.onDelete}/>
})
}
</ul>
</div>
</div>
}
</div>
)
}
}
CartAddBackItem:
class CartAddBackItem extends Component {
constructor() {
super();
this.onClick = this.onClick.bind(this);
}
onDelete(){
this.props.onDelete({deleted: this.props.data.sku})
}
allowSubmit() {
this.setState({
allowSubmit: true,
});
}
onClick() {
if (this.props.data) {
if (this.props.data.quantity <= this.props.data.inventory_quantity) {
const success = () => {
this.allowSubmit();
},
failure = (err) => {...};
this.props.onAddToCart({
...{sku: this.props.data.sku, quantity: this.props.data.quantity}, quantity: this.props.data.quantity}).then(success, failure);
}
else {
this.setState=({display: false});
const success = () => {
this.allowSubmit();
},
failure = (err) => {...};
this.props.onAddToCart({
...{sku: this.props.data.sku, quantity: this.props.data.quantity}, quantity: 1}).then(success, failure);
}
}
}
render() {
let {data} = this.props;
return (
<li className="CartAddBackItem">
{data &&
<div className="CartAddBackItem-Wrapper">
<Button className="CartAddBackItem-button" onClick={this.onClick}><FormattedMessage id="cart.cartAddBack"/></Button>
<Link to={`product/${data.sku}`} className="CartAddBackItem-Link">
<p className="CartAddBackItem-title">{data.title}</p>
</Link>
</div>
}
</li>
)
}
}
I want CartAddBack to remove CartAddBackItem if the item was clicked in CartAddBackItem. Only thing I havent tried that I just thought about was to make a componentWillReceiveProps inside CartAddBack. But there has to be a better way. Issue I'm running into is my mapping items into CartAddBackItem. The gross looking {theStated != Null ? theData.filter(i =>... allows me to add items to the cart. It works if it was only data.map((item)=>... but I want to show my thinking. Any advice?

Categories

Resources