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
Related
I want to make a button check which add a new calssName to my list. I use a function to update a state and take the string. If you want to help me be more specific because I am a beginner. Thanks !
const [check, setCheck] = useState({
id: '',
status: false
});
This is the function. With 'on' I take the string to add to id.
let complet = (on) =>{
if(check.status == false){
setCheck({
id: on,
status: true
})
}
else {
setCheck({
id: on,
status: false
})
}
}
And how I Display the list and how I check correspond to the string.
return(
<div className='display'>
{ list.map( (list,index) => (
<div className={ check.status && check.id == list ? 'lista complet' : 'lista'} key= {index} id='lista' >
{list}
<button className='btnCheck' onClick={complet.bind(this, list)}> <FcCheckmark/> </button>
<button className='btnRemove' onClick={remove.bind(null, list)}> <BsTrash/> </button>
</div>
))}
</div>
)
If you want to store the checked ids and the unchecked ids, you must change your state variable because currently it can only stores a single element. However, it seems you are rendering a list of elements that can be checked individually
Here is a possible solution :
function App({list}) {
const [checkIds, setCheckIds] = useState(() => {
const item = localStorage.getItem('checkIds');
return item ? JSON.parse(item) : {};
});
// reset the checkIds when the list is updated
useEffect(() => setCheckIds({}), [list]);
// save the checkIds into the local storage
useEffect(() => {
localStorage.setItem('checkIds', JSON.stringify(checkIds));
}, [checkIds]);
function checkId(id) {
setCheckIds({...checkIds, [id]: true);
}
function uncheckId(id) {
setCheckIds({...checkIds, [id]: false);
}
return (
<div className='display'>
{list.map(id => (
<div key={id} id={id} className={check[id] ? 'lista complet' : 'lista'}>
{id}
<button className='btnCheck' onClick={() => checkId(id)}>
<FcCheckmark/>
</button>
<button className='btnRemove' onClick={() => uncheckId(id)}>
<BsTrash/>
</button>
</div>
))}
</div>
)
}
I have been attempting to toggle a class on click so that when I click on one of the mapped items in my Tasks component, I add the 'complete' class and put a line through that item (crossing items off of a todo list). However with my current code set up, when I click on one element to add the class, all the other elements get crossed out as well and vice versa.
Here is my current setup. The class 'complete' is what will add a line through one of the mapped items in the Tasks component.
import { Container, Row} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import axios from 'axios';
const List = (props) =>{
return(
<div>
<Link style={{textDecoration:'none'}} to={`/lists/${props.listId}`} > <p className="list-item">{props.item}</p></Link>
</div>
)
}
const Tasks = (props) =>{
return(
<div onClick={props.onClick} className={props.className} >
<div className='task-item' >
<p >{props.item}</p>
</div>
</div>
)
}
export default class Display extends Component {
constructor(props){
super(props)
this.onCompletedTask = this.onCompletedTask.bind(this);
this.state = {
list: [],
tasks:[],
complete:false
}
}
componentWillUpdate(nextProps){
axios.get(`http://localhost:8080/lists/${this.props.match.params.listId}`)
.then(response =>{
this.setState({
tasks:response.data
})
})
}
componentDidMount(){
axios.get('http://localhost:8080/lists')
.then(response=>{
this.setState({
list:response.data
})
})
.catch(error =>{
console.log(error)
});
}
onCompletedTask(item){
this.setState({ complete: !this.state.complete});
}
listCollection(){
return(
this.state.list.map(item=>{
return(<List item = {item.title} listId={item._id} key = {item._id} />)
})
)
}
taskCollection(){
return(
this.state.tasks.map((item, index) =>{
return(<Tasks onClick = {()=>this.onCompletedTask(item)} className={this.state.complete ? 'complete': ''} item={item.task} key={index}/>)
})
)
}
render() {
return (
<div id='main' >
<Container>
<Row>
<div className="sidebar">
<h1 style={{fontSize:"25pt"}}>Lists</h1>
<div className="list-menu">
{this.listCollection()}
</div>
<form action='/new-list' method='GET'>
<div style={{textAlign:'center'}}>
<button className='list-button' style={{fontSize:'12pt', borderRadius:'5px'}}>
+ New List
</button>
</div>
</form>
</div>
<div className='tasks'>
<h1 style={{fontSize:'25pt'}}>Tasks</h1>
{this.taskCollection()}
<form action={`/lists/${this.props.match.params.listId}/new-task`} method='GET'>
<button className='task-button'>
+
</button>
</form>
</div>
</Row>
</Container>
</div>
)
}
}
Your state holds only a single completed value, which OFC toggle all tasks. You could instead store a map of completed tasks.
this.state = {
list: [],
tasks: [],
complete: {}, // <--- use empty object as simple map object
}
Update onCompletedTask to store some uniquely identifying property of a task, like an id field
onCompletedTask(item){
this.setState(prevState => ({
completed: {
...prevState.completed, // <--- spread existing completed state
[item.id]: !prevState.completed[item.id] // <--- toggle value
},
}));
}
Update. taskCollection to check the completed map by id
taskCollection = () => {
const { completed, tasks } = this.state;
return tasks.map((item, index) => (
<Tasks
onClick={() => this.onCompletedTask(item)}
className={completed[item.id] ? "complete" : ""} // <--- check completed[item.id]
item={item.task}
key={index}
/>
))
};
I will change style a part of string when click. example "TEXT" then click at "T" after that it will change style from black color to red color just T only
In my code, I split text and keep at "split" array when I click at text, it will call handleClick function and send index of character that I click is parameter. For example ("EXAMPLE") when I click E it will send 0 is parameter of handleClick function.
import React,{Component} from 'react'
export default class Test extends Component {
handleClick = (index) => {
console.log(index)
}
render() {
return(
<div>
{this.state.table.map((text) => {{this.state.split
&& this.state.split.map((item, index) => {
return(
<span key={index} onClick={() =>
this.handleClick(index)}>{item}
</span>
);
})}
</div>
)
}
}
You need a state which will maintain the clicked index. Then use that index while rendering your split spans to set different colored className.
You could then apply your style to that class.
export default class Test extends Component {
handleClick = (index) => {
this.setState({ clickedIndex: index });
}
render() {
return (
<div>
{this.state.table.map((text) => {
this.state.split && this.state.split.map((item, index) => {
return (
<span key={index} style={clickedIndex === index ? {color: 'red'} : {}} onClick={() =>
this.handleClick(index)}>{item}
</span>
);
})
})}
</div>
)
}
}
I am trying to create a component where I have a bunch of boxes from an array, that can be turned 'on' and 'off' when each one is individually clicked.
Currently, only a single item from the array can be switched 'on' (shown by the item turning green), however, I would like to be able to turn each item on/off individually.
Interacting with one element should not affect any of the others.
How do I achieve this?
My click event:
handleOnClick = (val, i) => {
this.setState({active: i}, () => console.log(this.state.active, 'active'))
}
Rendering the boxes:
renderBoxes = () => {
const options = this.state.needsOptions.map((val, i) => {
return (
<button
key={i}
style={{...style.box, background: i === this.state.active ? 'green' : ''}}
onClick={() => this.handleOnClick(val, i)}
>
{val}
</button>
)
})
return options
}
Here's a Codepen
What I would do is to create a Box component with its own active state, and pass this to the map in renderBoxes. The benefit of doing it this way is that each Box component will have its own state independent of the parent. That way you can have more than one component as active.
so...
class Box extends React.Component {
constructor(props){
super(props)
this.state={
active: false
}
}
clickHandler = () => {
this.setState({active: !this.state.active})
}
render(){
const { key, children }= this.props
return (
<button
key={key}
style={{...style.box, background: this.state.active ? 'green' : ''}}
onClick={() => this.clickHandler()}
>
{children}
</button>
)
}
}
then have renderBoxes be...
renderBoxes = () => {
const options = this.state.needsOptions.map((val, i) => {
return (
<Box
key={i}
>
{val}
</Box>
)
})
return options
}
here is the codepen I forked off yours.
i'm using Meteor/ReactJS and i'm building a TodoList with lists. When a user click on a list item, I display the tasks associated to this list.
I can get the items, I'm calling the onClick with a parent function.
The problem is I would like to add an "active" class to the list item I just clicked. But I don't know how to do it on the parent class or neither in the child..
Here is the parent class (i'm calling this function in the render():
selectList(listId) {
this.setState({ listSelected: listId });
}
renderLists() {
return this.props.lists.map((list) => (
<List
selectList={() => this.selectList(list._id)}
key={list._id}
list={list}
/>
));
}
and here is the child:
render() {
return (
<ListGroupItem onClick={this.props.selectList}>
{this.props.list.name}
<span className="pushRight">
<Glyphicon
onClick={() => this.deleteThisList()}
glyph="glyphicon glyphicon-remove"
/>
</span>
</ListGroupItem>
);
}
How could I get the result of selectList in parent and then give the class to the child, or How could I handle the click in the child and give the class directly in the child ?
Thanks a lot for help :)
Parent
1.pass selected id to List comp
selectList(listId) {
this.setState({ listSelected: listId });
}
renderLists() {
return this.props.lists.map(list => (
<List
selectedItemId={this.state.listSelected}
selectList={() => this.selectList(list._id)}
key={list._id}
list={list}
/>
));
}
Child
2.Compare list.id with passed selectedItemId, if true, so this item is active
render() {
const { selectList, list, selectedItemId} = this.props;
return (
<ListGroupItem isActive={list.id==selectedItemId} onClick={selectList}>
{list.name}
<span className="pushRight">
<Glyphicon
onClick={() => this.deleteThisList()}
glyph="glyphicon glyphicon-remove"
/>
</span>
</ListGroupItem>
);
}
3.then in ListGroupItem
render(){
const {isActive}=this.props;
return (
<div className={"someclass "+(isActive? 'active':'')}>
.......
</div>
);
}