Toggle one list item instead of entire list - javascript

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>
)
}

Related

React: Having a button select something upon click a button

I currently have 4 buttons and 4 circles with 4 numbers each. When someone clicks button 1, circle 1 would be selected. When they click button 2, circle 2 would be selected. I currently have this:
class App extends Component {
constructor() {
super();
this.state = {
selButtonIdx: 1,
};
}
handleButtonSelection = (selButtonIdx) => {
this.setState({ selButtonIdx: selButtonIdx });
console.log(selButtonIdx);
};
render() {
return (
<div className="App">
<header className="App-header">Buttons</header>
<main>
<CircleSelector
selButtonIdx={this.state.selButtonIdx}
handleButtonSelection={this.handleButtonSelection}
/>
<Circles />
</main>
</div>
);
}
}
For my CircleSelector I have:
const CircleSelector = (props) => {
return (
<div className="CircleSelector">
{[1, 2, 3, 4].map((index) => (
<button
type="button"
onClick={() => props.handleButtonSelection(index)}
>
Select Button {index} {index === props.selButtonIdx && "(selected)"}
</button>
))}
</div>
);
};
and for circles:
const Circle = (props) => {
return (
<div className="Circles">
{[1, 2, 3, 4].map((index) => (
<div type="text">{index}</div>
))}
</div>
);
};
Yoou should integrate your circle and your button in the same component, this way you can simply use a reference to select the circle. I did the following example with input since i don't know what your circle contains but the idea is here :
import React, { Component } from "react";
import { render } from "react-dom";
const App = () => {
return (
<div>
{[1, 2, 3, 4].map(el => (
<MyComponent />
))}
</div>
);
};
const MyComponent = () => {
const elRef = React.createRef();
const selectElement = () => {
elRef.current.select(); // Select the content of the input
};
return (
<div>
<button onClick={selectElement}>Select element</button>
<input ref={elRef} type="text" value="element" />
</div>
);
};
render(<App />, document.getElementById("root"));
Here is the repro on stackblitz

How to toggle only one item in react?

I am making a collapsible section using reactstrap Collapse.
The data received are nested and I am in the need to display the collapse button at second level and the third level will have the data to be displayed.
No problem in this level of display and it already display the data at third level on click of button.
Problem: On click of any button, all the collapsible section gets opened instead of the clicked one.
Requirement: Only the clicked section's token(s) needs to get displayed and on click of the button again it should be collapsed back and it should not have any relation with any other items.
Working Snippet as follows,
const data = [{"orderId":1,"orderNo":"123", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3"]}] },
{"orderId":2,"orderNo":"456", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1","Token3"]}] },
{"orderId":3,"orderNo":"789", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3", "Token4"]}] }
]
const {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
toggle = () =>
this.setState(s => ({
open: !s.open
}));
render() {
console.log();
return <div>
{
data.map((levelOneItem, i) => {
return(
<div>
<div> Order Id: {levelOneItem.orderId} </div>
{
levelOneItem.orderParts.map((levelTwoItem, j) => {
return(
<div>
<div> Order Part Id: {levelTwoItem.orderPartsId} </div>
<Button onClick={this.toggle}>Display Token</Button>
<Collapse isOpen={this.state.open}>
{
<div>
{levelTwoItem.regTokens.map((levelThreeItem, k) => {
return(<span> {levelThreeItem} </span>)
})
}
</div>
}
</Collapse>
</div>
)
})
}
</div>
)
})
}
</div>;
}
}
ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>
<div id="root"></div>
Kindly help me to achieve the result of toggling only on the selected item for the nested data provided.
Add an identifying index as params for your handler function would be fine.
this.state = {
open: ''
};
toggle = idx => () => {
this.setState(prevState => ({open: prevState.open === idx ? '' : idx}));
}
<Button onClick={this.toggle(i)}>
<Collapse isOpen={open === i}>
Update
If you want them to be independent, you need an Array to store each status instead.
this.state = {
open: [0, 2] // Initial opened item's index
};
toggle = idx => () => {
this.setState(prevState => ({
open: this.state.open.includes(idx) // check whether been expanded
? prevState.open.filter(x => x !== idx) // if yes, remove from list
: [...prevState.open, idx]} // if no, add to list
))
}
<Button onClick={this.toggle(i)}>
<Collapse isOpen={open.includes(i)}>
const data = [{"orderId":1,"orderNo":"123", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3"]}] },
{"orderId":2,"orderNo":"456", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1","Token3"]}] },
{"orderId":3,"orderNo":"789", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3", "Token4"]}] }
]
const {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
open: [0, 2] // Initial opened item's index
};
}
toggle = idx => () => {
this.setState(prevState => ({open: this.state.open.includes(idx) ? prevState.open.filter(x => x !== idx) : [...prevState.open, idx]})
)}
render() {
const { open } = this.state;
return <div>
{
data.map((levelOneItem, i) => {
return(
<div>
<div> Order Id: {levelOneItem.orderId} </div>
{
levelOneItem.orderParts.map((levelTwoItem, j) => {
return(
<div>
<div> Order Part Id: {levelTwoItem.orderPartsId} </div>
<Button onClick={this.toggle(i)}>Display Token</Button>
<Collapse isOpen={open.includes(i)}>
{
<div>
{levelTwoItem.regTokens.map((levelThreeItem, k) => {
return(<span> {levelThreeItem} </span>)
})
}
</div>
}
</Collapse>
</div>
)
})
}
</div>
)
})
}
</div>;
}
}
ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>
<div id="root"></div>

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

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.

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.

defaultValue is pulling the last value in React

If i click on a particular ToDos edit button, its value should be defaulted inside the textarea but everytime the last ToDo is defaulting, can somebody please help, whether using ref is a right choice or something else, then where i m wrong what i'm suppose to do ?
handleEdit() {
e.preventDefault();
.....
}
renderDisplay() {
return(
<div>
{
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="newText">
<li>{list}
<div>
<button className="btn btn-primary btn-xs glyphicon glyphicon-pencil"
onClick={this.handleEdit.bind(this)}
/>
</div>
<hr/>
</li>
</div>
)})
}
</div>
);
}
renderForm() {
return(
<div>
<textarea className="form-control" defaultValue={this.refs.newText.innerText} rows="1" cols="100" style={{width: 500}}/>
</div>
)
}
render() {
if(this.state.editing) {
return this.renderForm();
}else {
return this.renderDisplay();
}
}
}
First of all you are using an old ref API. You should use this one, where you set the ref to the instance of the class using this with a callback.
<input ref={ref => {this.myInput = ref}} />
And then you can access its value by just referring to this.myInput .
As for your "bug", keep in mind that you are looping over and overriding the ref. so the last ref assignment would be the last item in the array.
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="newText">
<li>{list}
There will always be 1 newText ref and it will always be the last item in the array.
You should render different ref names according to the item id and then pass the id of the item to the renderForm so it can access the relevant ref.
With that said, i really recommend to extract the todo to a different component as well as the form. I don't see a valid reason to use refs in this case.
Edit
As a follow-up to your comment, here is a small example of how you would use components instead of refs in order to get information from the child like values etc..
class Todo extends React.Component {
onClick = () => {
const { todoId, onClick } = this.props;
onClick(todoId);
}
render() {
const { value, complete } = this.props;
return (
<div
style={{ textDecoration: complete && 'line-through' }}
onClick={this.onClick}
>
{value}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ id: '1', value: 'to do this', complete: false },
{ id: '2', value: 'todo that', complete: true },
{ id: '3', value: 'will do it later', complete: false }]
}
}
toggleTodo = (todoId) => {
const { todos } = this.state;
const nextState = todos.map(todo => {
if (todo.id !== todoId) return todo;
return {
...todo,
complete: !todo.complete
}
});
this.setState({ todos: nextState });
}
render() {
const { todos } = this.state;
return (
<div >
{
todos.map((todo) => {
return (
<Todo
complete={todo.complete}
key={todo.id}
todoId={todo.id}
value={todo.value}
onClick={this.toggleTodo}
/>
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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>
<div id="root"></div>

Categories

Resources