How to hide all the div while showing the one which is clicked, in React? - javascript

I'm generating some identical div through a list. Below is the sample code.
I'm toggling this div
class App extends React.Component {
state = { showKitten: false };
handleToggleKitten = () => {
this.setState((prevState, props) => ({
showKitten: !prevState.showKitten,
}));
};
render() {
return (
<About
data={datalist}
showKitten={this.state.showKitten}
handleToggleKitten={this.handleToggleKitten}
/>
);
}
}
const About = ({ datalist, showKitten, handletogglekitten }) => {
return (
<div>
{datalist.map((item, index) => {
return (
<div key={index}>
<div onClick={handletogglekitten} />
showKitten ? <div /> : null
</div>
);
})}
</div>
);
};
I have defined tooglefunction and the flag state variable in parent and passing them to children and in children component, I'm creating this divs by iterating over a list. Right now I am able to achieve the toggle functionality for the individual div set but I want to hide all the div and show the one which is clicked.

You could use the index value. Here's a working example.
const datalist = ["cat 1", "cat 2", "cat 3"];
class App extends React.Component {
state = { showKittenIndex: null };
render() {
return (
<About
datalist={datalist}
showKittenIndex={this.state.showKittenIndex}
toggleKitten={index => this.setState({ showKittenIndex: index })}
/>
);
}
}
const About = ({ datalist, showKittenIndex, toggleKitten }) => (
<div className="about">
{datalist.map((item, index) => (
<div key={index}>
<button onClick={() => toggleKitten(index)}>toggle {index}</button>
{showKittenIndex === index && <div>{item}</div>}
</div>
))}
</div>
);

I have a very similar approach than #Kunukn.
But I don't see the need to wrap it in a functional component.
import React, { Component } from 'react';
const elements = ['DIV #1', 'DIV #2', 'DIV #3', 'DIV #4', 'DIV #5', 'DIV #6'];
class App extends Component {
constructor(props) {
super(props);
this.state = {
activeElement: null,
allElements: elements,
};
}
render() {
return (
<About
elements={this.state.allElements}
showIndex={this.state.activeElement}
toggleIndex={index => this.setState({ activeElement: index })}
/>
);
}
}
const About = ({ elements, showIndex, toggleIndex }) => (
<div className="about">
{elements.map((element, index) => (
<div key={index}>
<div onClick={() => toggleIndex(index)}>toggleIndex {index}</div>
{showIndex === index && <div>{element}</div>}
</div>
))}
</div>
);
export default App;
I did write a little clickHandler ... I know that it is not needed at the moment, but when you would want to alter the data received with the click-event this could be handled there as well.
EDIT
According to the comment I improved the code a bit by making a functional component showing the DIVs. I did also dismiss the clickHandler() function.

Related

React JS pass the data or child component to parent component

Is it possible to pass the data from the child component to the parent component using props?
-Parent component
--- ItemList component.
--- DisplatSelect component from the itemList component
I have a list of item in the child component which came from to the parent component, then I want to send the index of the selected data to the other child component located in the parent component.
Can't example well, kindly see the attached screenshot for other references.
Thanks a lot!
enter image description here
You can keep the data in the Parent component and use a function to pass the props from the child to the Parent. This concept is called Lifting State Up where you define the state at the highest common ancestor so all the child components are using the same data which in this case is the selecetd item
function Parent() {
const [selectedItem, setSelectedItem] = useState(null);
const data = []; // Your Data
return (
<>
<h1>Your selected Item = {selectedItem}</h1>
{data.map((item) => {
<Child item={item} setSelectedItem={setSelectedItem} />;
})}
</>
);
}
function Child({ item, setSelectedItem }) {
return <Button onClick={() => setSelectedItem(item.id)}> {item} </Button>;
}
The simplest way, I think, is for the child component where the selection is made to accept a function properly, something like onSelectionChanged. If you had a button for each item passed to the child you could do something like:
Child Component A
const ChildA = ({ items, onSelectionChanged }) => {
return (
<div>
{items.map((item, index) => (
<button onClick={() => onSelectionChanged(index)}>Item</button>
))}
</div>
)
}
Child Component B
const ChildB = ({ selectedItem }) => {
return (
<div>
Selected {selectedItem}
</div>
)
}
Parent Component
const Parent = () => {
const [selection, sets election] = useState({});
const onSelectionChanged = index => {
console.log(`ChildA selection changed: ${index}`);
}
return (
<div>
<ChildA items={items} onSelectionChanged={onSelectionChanged} />
<ChildB selectedItem={selection} />
</div>
)
}
So when your child component handles a change in the selection, it invokes the function passed as a prop onSelectionChanged. You can pass whatever data you want from ChildA to that function.
Note that the parent Component keeps the selected value (from ChildA) in local state, then passes that value to ChildB via a prop.
You can have a state variable in the parent component and pass it to child components to share data between them. I'll post a sample code block on how you can do this for your case.
export default function ParentComponent (props) {
const data = ['image_1_url', 'image_2_url', ...] // Data for the images
const [selectedIndex, setSelectedIndex] = useState(-1); // Selected index (-1 represents no selection)
return (
<ImageList data={data} selectImage={setSelectedIndex} />
{(selectedIndex !== -1) ? (<SelectedImage data={data[selectedIndex]} />) : (<No ImageSelected/>)}
);
}
And the image list component can then use the selectImage prop to select the image
export default function ImageList (props) {
return (
<div>
props.data.map((imageUrl, index) => (
<div onClick={() => {props.setSelected(index)}}>
<img src={imageUrl}/>
</div>
))
</div>
);
}
Yes it's possible. We have one parent state value and update every on click child component to the component.
import React, { useState } from "react";
const Child1 = (props) => {
return (
props.items.map( (item, index) => (
<button key={index.toString()} onClick={() => { props.updateIndex(item.id) }}>
{item.name}
</button>
) )
)
}
const Child2 = (props) => {
return (
<h1>Item selected: {props.selectItem}</h1>
)
}
const ParentComponent = () => {
const listItems = [
{
id:1,
name: "sample name 1"
},
{
id:2,
name: "sample name 2"
}
]
const [selectItem, setSelectItem] = useState('None');
return (
<>
<Child1 items={listItems} updateIndex={setSelectItem}/>
<Child2 selectItem={selectItem}/>
</>
)
}
export default function App() {
return (
<div className="App">
<ParentComponent/>
</div>
);
}

Reactjs send data through pages and components

I have a page called: List.js which renders the component: Box for every item on the List page. My List.js also consists of a component called: MainSpace. Now every Box component will consists of a button with an onClick function. Depending on which Box component you click, I want to fill the MainSpace with data coming from that specific Box component, but I can't seem to figure out how to do this.
render() {
for (let index in data) {
list.push(
<Box data={data[index]}/>
);
}
return (
<div>
{list}
<MainSpace>
</div>
);
}
Now the Box component looks something like this:
class Box extends Component {
fillMainSpace(){
//Send this.prop.data to the list.js file and fill the MainSpace component from there I think?
}
render(){
return (
<div>
<span>{this.props.data.text}</span>
<button onClick={this.fillMainSpace}></button>
</div>
);
}
}
Mainspace doesn't have much. I just want to load for example the this.props.data.image from the clicked button from the Box component. Even console.log(this.props.data); would be sufficient. Calling the <MainSpace> component in every <Box> component is not an option, since that would render a lot of unnecessary extra components.
So my question being:
How would I be able to access the this.props.data of the clicked button of a <Box> component and use it in my <MainSpace> component?
EDIT:
Adding the MainSpace Component:
class MainSpace extends Component {
render(){
return (
<div>
{this.props.data.image}
</div>
);
}
}
You can save selected item in state like selectedItem and then pass that data to MainSpace
const List = props => {
const [selectedItem, setSelectedItem] = useState(props.data[0]);
const onSelect = item => {
setSelectedItem(item);
};
return (
<div calssName="list">
{props.data.map(item => <Box data={item} onSelect={onSelect} />)}
<MainSpace data={selectedItem} />
}
}
const Box = props => {
const { data, onSelect } = props;
return (
<div>
<span>{data.someKey}</span>
<button onClick={() => onSelect(data)} >Select</button>
</div>
);
};
Using class components
class Box extends Component {
fillMainSpace = () => {
const { onSelect, data } = this.props;
onSelect(data);
}
render(){
const { data } = this.props;
return (
<div>
<span>{data.text}</span>
<button onClick={this.fillMainSpace} }></button>
</div>
);
}
}
class List extends React.Component {
state = { selectedItem: this.props.data[0] };
onSelect = item => {
this.setState({ selectedItem: item });
}
render() {
const { data } = this.props;
const { selectedItem } = this.state;
const list = data.map(item => <Box data={item} onSelect={this.onSelect} />);
return (
<div>
{list}
<MainSpace data={selectedItem}>
</div>
);
}
}

Toggle one list item instead of entire list

I am making a card that lists radio stations and when you click on a station a picture drops down for that list item. all data i am pulling from a json.
I have tried building the list in the toggle.js and in the app.js files
app.js-- div with toggle (its all inside a card..many divs)
<div class="card-trip-infos">
<div>
<Toggle />
</div>
<img class="card-trip-user avatar-bordered"/>
</div>
toggle.js render block:
state = {
on: false,
}
toggle = () => {
this.setState({
on: !this.state.on
})
}
render() {
return (
<ul>
<div>
<p>{PostData.radios[0].name}</p>
{PostData.radios.map((postDetail, index) => {
return <div>
<li onClick={this.toggle}>
<span id='radioName'>{postDetail.name}</span> <span id='frequency'>{postDetail.frequency}</span>
</li>
{
this.state.on && <img src='imagesrc'></img>
}
</div>
})}
</div>
</ul>
)
}
}
I dont know what exactly is wrong but i expect to be able to toggle a picture for each list row seperately. i am confused where to iterate over my json and where to plug everything in.
many Thanks!!
Since we don't know your toggle function and all your component we can't make exact suggestions but in order to do what you want here (just toggle the selected item), you have two different approaches.
You can keep the selected state in the component itself.
class App extends React.Component {
state = {
cards: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
],
};
render() {
const { cards } = this.state;
return (
<div>
{cards.map(card => (
<Card
key={card.id}
card={card}
/>
))}
</div>
);
}
}
class Card extends React.Component {
state = {
selected: false
};
handleSelect = () =>
this.setState(state => ({
selected: !state.selected
}));
render() {
const { card } = this.props;
return (
<div
className={this.state.selected ? "selected" : ""}
onClick={this.handleSelect}
>
{card.id}-{card.name}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.selected {
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
You can keep the selected state in the parent component.
class App extends React.Component {
state = {
cards: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
],
selected: {}
};
handleSelect = id =>
this.setState(state => ({
selected: { ...state.selected, [id]: !state.selected[id] }
}));
render() {
const { cards, selected } = this.state;
return (
<div>
{cards.map(card => (
<Card
key={card.id}
card={card}
handleSelect={this.handleSelect}
selected={selected[card.id]}
/>
))}
</div>
);
}
}
const Card = ({ card, handleSelect, selected }) => {
const handleClick = () => handleSelect(card.id);
return (
<div className={selected ? "selected" : ""} onClick={handleClick}>
{card.id}-{card.name}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
.selected {
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
Create a component for individual list item, have a state there which will hold a boolean property to tell whether the image is to be shown or not. Render function will look something like this:
render (
return (
<li onClick="this.toggleShowImage()">
<div> {props.data.name} </div>
<div> {props.data.frequency} </div>
{this.state.showImage ? <img src='imageUrl' /> : null }
</li>
)
)
Then in toggle.js iterate through your data and create the list components. The render function will look something like below:
render() {
return (
<ul>
{PostData.radios.map((postDetail, index) => {
return <ListItem key={postDetail.name} data={postDetail} />
})}
</ul>
)
}

To do list using react-sortable-hoc

I'm using sortable drag and drop which works fine. The problem is that I'd like users to be able to remove items. The SortableItem component isn't accessible as it came with the code, so I can't pass an event handler that takes index as an argument. Here's what I have so far:
const SortableItem = SortableElement(
({value}) =>
<ul>{value}</ul>
);
const SortableList = SortableContainer(({items}) => {
return (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
</ul>
);
});
export class BlocksContainer extends React.Component {
constructor(props){
super(props);
this.state = {
items: [],
};
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
addBlock = (block) =>{
let arr = [...this.state.items, block];
this.setState({items: arr})
}
removeBlock = (index) => {
let remove = [...this.state.items];
remove.filter(block => block === index);
this.setState({items:remove})
}
render() {
return (<div><div onChange={console.log(this.state)} className="sortableContainer"><SortableList items={this.state.items} onSortEnd={this.onSortEnd} /></div>
<h2>Blocks</h2>
<button onClick={() => this.addBlock(<BannerImage remove={this.removeBlock} />)}>Banner Image</button>
<button onClick={() => this.addBlock(<ShortTextCentred remove={this.removeBlock}/>)}>Short Text Centred</button>
<h2>Layouts</h2>
<hello />
</div>
)
}
}
Since you dont have control over the events of the SortableItem component, you can wrap that component in a component you do have control over.
For example, if i wanted to add a click handler to the SortableItem, i would add it instead to the div wrapper:
const SortableList = SortableContainer(({ items }) => {
return (
<ul>
{items.map((value, index) => (
<div onClick={this.someEventHandler}>
<SortableItem key={`item-${index}`} index={index} value={value} />
</div>
))}
</ul>
);
});

Mapping over arrays in React

I'm mapping over each object inside the array of objects inside my state's item property. The issue is, I want to be able to attach a button to each returned item that only works with that item, and not the other items too. Here's what I have so far:
class App extends React.Component {
state = {
item: [],
}
componentDidMount() {
this.setState({
item: [
{
name: 'jacob',
hair: 'brown',
sex: 'male',
}, {
name: 'hannah',
hair: 'brown',
sex: 'female',
}
]
})
}
handleChange = (e) => {
console.log(e.target.value)
var x = Object.assign({}, this.state)
}
render() {
return(
<div>
{ this.state.item.length > 0 ?
(
<div className="row mt-5">
<Item item={ this.state.item } handleChange={ this.handleChange } />
</div>
) : null
}
</div>
)
}
}
class Item extends React.Component {
render() {
return(
<div className="col">
{ this.props.item.map(s => (
<div>
<div className="mt-5">{ s.name }</div>
<button onClick={ this.props.handleChange } value={ s.name }>Click</button>
</div>
))}
</div>
)
}
}
So for instance, if the button's fx was to change the name property of the item it was rendered with, I want it to only change that item and no other items should be affected. Whenever I iterate through it attaches the button's fx to every item, so if I click it for one, I'm really clicking it for all of them, and that's exactly what I don't want.
For those curious, I'm setting the state in componentDidMount to simulate calling an API.
Also, the fx that's currently running in the handleChange is just some messing around I was doing trying to figure out values and a solution to manipulating a nested object inside an array.
Try this refactored code on CodeSandBox,
You have to add keys when iterating Components in React, i've added it, also the Item Component could be function Component since it doesn't handle a state.
Updated: below is the code in case it wouldn't be available on codesandbox.
import React from 'react';
import { render } from 'react-dom';
class App extends React.Component {
state = {
items: [],
}
componentDidMount() {
this.setState({
items: [
{
name: 'jacob',
hair: 'brown',
sex: 'male',
}, {
name: 'hannah',
hair: 'brown',
sex: 'female',
}
]
})
}
handleChange = (e) => {
console.log(e.target.value)
}
render() {
const { items } = this.state;
return (
<div>
{items.length && (
items.map((item, index) => (
<div className="row mt-5" key={index}>
<Item item={item} handleChange={this.handleChange} />
</div>
))
)
}
</div>
)
}
}
const Item = ({ item, handleChange }) => (
<div className="col">
<div className="mt-5">{item.name}</div>
<button onClick={handleChange} value={item.name}>Click</button>
</div>
);
render(<App />, document.getElementById('root'));
I think you want to pass the individual element in to your handleChange:
{ this.props.item.map((s, i) => {
<div>
<div className="mt-5">{ s.name }</div>
<button key={i} onClick={ (s) => this.props.handleChange(s) } value={ s.name }>Click</button>
</div>
})}
This way, you will have the individual item passed in as an argument, so when you are writing your handleChange, you can do whatever you want with just that item and not the whole array.

Categories

Resources