How can i add class to single li element using onClick? At the moment when i click on whatever li, all div's are getting item-active class when state is changed.
I do have index from mapped array, but i'm not sure where (i believe in handleClick()?) should i use it to make it working...
//import {cost} from '...';
export class CostFilter extends Component {
constructor(props) {
super(props);
this.state = {active: "item-not-active"};
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
let isActive = this.state.active === "item-not-active" ? "item-active" : "item-not-active";
this.setState({active: isActive});
}
render() {
return (
<ul>
{cost.map((element, index) =>
<li onClick={this.handleClick} value={`cost-${element.cost}`} key={index}>
<div className={`hs icon-${element.cost} ${this.state.active}`}></div>
</li>
)}
</ul>
);
}
}
Try something like this:
export class CostFilter extends Component {
constructor(props) {
super(props);
this.state = {activeIndex: null};
}
handleClick(index) {
let activeIndex = this.state.activeIndex === index ? null : index;
this.setState({activeIndex});
}
render() {
return (
<ul>
{cost.map((element, index) =>
<li onClick={this.handleClick.bind(this, index)} value={`cost-${element.cost}`} key={index}>
<div className={`
hs
icon-${element.cost}
${this.state.activeIndex === index && 'item-active'}
`}></div>
</li>
)}
</ul>
);
}
}
You can store index of the clicked element in the state of your component and then check in your render function in map if the current index is equal to the key in the state. Something like this :
let data = ['Item 1', 'Item 2', "Item 3"];
class Test extends React.Component {
constructor(){
this.state = {
active: null
}
}
handleClick(i){
this.setState({active: i});
}
render(){
return <ul>{this.props.data.map((item, i) => <li key={i} className={this.state.active === i ? 'active' : ''} onClick={this.handleClick.bind(this, i)}>{item}</li>)}</ul>
}
}
React.render(<Test data={data}/>, document.getElementById('container'));
Here is a fiddle.
I hope this is what are you looking for. fiddle
const data = ['Hello', 'World']
class Example extends React.Component {
constructor(){
this.state = {
isActive: -1
}
}
click(key,e){
this.setState({
isActive: key
})
}
render(){
const costsList = this.props.costs.map((item, key) => {
return <li key={key}
className={this.state.isActive === key ? 'foo' : ''}
onClick={this.click.bind(this, key)}>
{item}
</li>
})
return <ul>
{costsList}
</ul>
}
}
React.render(<Example costs={data}/>, document.getElementById('container'));
Trying to simplify this with React Hooks ( check example on codepen )
const MyApp = (props)=>{
const [activeIndex, setActiveIndex] = React.useState(null)
let activeStyle = {backgroundColor:"red", width:"400px", height:"22px", margin: "2px"}
let normalStyle = {backgroundColor:"blue", width:"400px", height:"22px", margin: "2px"}
const handleClick = (i)=>{
setActiveIndex(i)
}
let output= ["item-1", "item-2", "item-3", "item-4", "item-5", "item-6", "item-7", "item-8", "item-9", "item-10"];
return(
<div>
{output.map((v, i)=>{
return (
<li key={i} style={activeIndex === i ? activeStyle: normalStyle}>
{i} : {v}
<button onClick={()=>handleClick(i)} name={i}>Change background color</button>
</li>)
})}
</div>
)
}
Related
I try to build a to-do-list in react.
I have 2 components so far:
The first one handles the input:
import React from 'react';
import ListItems from './ListItems.js';
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
<ListItems
entries={this.state.entries}
/>
</div>
</div>
)
}
}
export default InputComponent;
The second one is about the actual entries in the list:
import React from 'react';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
this.listTasks = this.listTasks.bind(this);
}
lineThrough(item) {
console.log(item);
//item.style = {
// textDecoration: 'line-through'
//}
}
listTasks(item) {
return(
<li key = { item.index }>
<div
ref = { (r) => this._itemText = r }
style = {{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{ item.text }
</div>
<button onClick={ () => this.lineThrough(this._itemText) }>Done!</button>
<button>Dismiss!</button>
</li>
)
}
render() {
let items = this.props.entries;
let listThem = items.map( this.listTasks );
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
{ listThem }
</div>
</ul>
)
}
}
export default ListItems;
As you can see, i want to have two buttons for each entry, one for the text to be line-through, and one to delete the entry.
I am currently stuck at the point where i try to address a specific entry with the "Done!" button to line-through this entry's text.
I set a ref on the div containing the text i want to style and pass that ref to the onClick event handler.
Anyways, the ref seems to be overwritten each time i post a new entry...
Now, always the last of all entries is addressed. How can i properly address each one of the entries?
What would be the best practice to solve such a problem?
you could pass an additional prop with index/key of the todo into every item of your todo list. With passing event object to your handler lineThrough() you can now get the related todo id from the attributes of your event target.
Kind regards
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
}
this.done = this.done.bind(this);
}
done(id) {
this.state.todos[id].done();
}
render() {
return (
this.state.todos.map(t => <Todo item={t} onDone={this.done} />)
);
}
}
const Todo = ({item, onDone}) => {
return (
<div>
<h1>{item.title}</h1>
<button onClick={() => onDone(item.id)}>done</button>
</div>
)
}
You need map listItems then every list item get its own ref
import React from 'react';
import ReactDOM from 'react-dom';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
}
lineThrough(item) {
item.style.textDecoration = "line-through";
}
render() {
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
<li key={this.props.item.index}>
<div
ref={(r) => this._itemText = r}
style={{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{this.props.item.text}
</div>
<button onClick={() => this.lineThrough(this._itemText)}>Done!</button>
<button>Dismiss!</button>
</li>
</div>
</ul>
)
}
}
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
{this.state.entries.map((item, index) => {
return <ListItems key={index} item={item} />
})}
</div>
</div>
)
}
}
i am developing web application with react.
I want to dynamically set 'checked' property on checkbox.
It showed a proper screen that i wanted, but not working when i programmatically set 'checked' property.
So, i have searched about this.
some people recommended to use 'defaultChecked' property.
ant then i tried this. but failed.
It has not changed when i had given event that programmatically set checked property. It always showed defaultChecked value.
source code
render() {
list = {
resource: {[...], [...] },
...
}
return (
<ParameterContainer values={ list } />
);
}
class ParameterContainer extends React.Component {
constructor(props) {
super(props);
}
render() {
const { values } = this.props;
return (
<div className="parameter-table">
<div className="parameter-row-container">
<ul className="parameter-row title">
<li className="parameter parameter-1">valid</li>
<li className="parameter parameter-2">Key</li>
<li className="parameter parameter-3">Type</li>
<li className="parameter parameter-4">oid</li>
<li className="parameter parameter-5">rid</li>
<li className="parameter parameter-6">desc</li>
</ul>
{
values.resource.map((value, i) => {//one row
return (
<ParameterItem value={ value } index={ i } key={ i }/>
);
})
}
</div>
</div>
);
}
}
class ParameterItem extends React.Component {
constructor(props) {
super(props);
}
render() {
const { value, index } = this.props;
const items = [];
for (let o in value) {
if (o === "valid") {
continue;
}
items.push(value[o]);
}
return (
<ul className={`parameter-row parameter-row-${ index+1 }`}>
<li className="parameter parameter-1">
<input type="checkbox" defaultValue={ value.valid } checked={ value.valid } onChange={ this.toggle }/> //i tried to this.
<Checkbox isChecked={ value.valid } /> // and then tried this...
</li>
</ul>
);
}
toggle(e) {
$(e.target).prop("checked", e.target.checked ? 0 : 1);
}
}
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: !!this.props.isChecked
};
}
handleChange = (e) => {
const { target: { checked }} = e;
this.setState({ checked });
}
render() {
return (
<input type="checkbox" checked={ this.state.checked } onChange={ this.handleChange } />
);
}
}
Your onChange method in checkbox input is missing a binder.
Try this: onChange={this.handleChange.bind(this)}.
More explanation of the usage of .bind(this) can be found here: Handling events
You're looking for controlled components, where the value of the component is dictated by state and updated thru an onchange handler. Check out https://reactjs.org/docs/forms.html#controlled-components
I had a similar problem. I wanted to list a set of checkboxes, but some of them might be already checked.
I solved it like this:
{this.props.checkboxesList.map(e => (
<CustomInput
type="checkbox"
key={e.id}
id={e.id}
name={e.nombre}
label={e.nombre}
defaultChecked={
this.props.form.selected.filter(
item => item.id === e.id
).length === 0
? false
: true
}
onChange={this.props.onCheckBoxChange}
/>
))}
Note: CustomInput is from 'reactstrap'
And my "onCheckBoxChange" looks like this:
handleCheckBoxChange = e => {
let options = this.state.form.selectedOptions;
if (e.target.checked) {
options.push({ id: e.target.id, nombre: e.target.name });
} else {
options = options.filter(item => item.id !== e.target.id);
}
this.setState({
form: {
...this.state.form,
selectedOptions: options
}
});};
Have into account that "state.from.selectedOptions" must be an array.
Hope it helps
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.
I'm trying to call a simple method from the grandparent component in my child component but from some reason I can't , I tried every possible way but I think I'm missing something
here's the full code :
import React, { Component } from 'react';
import './App.css';
var todos = [
{
title: "Example2",
completed: true
}
]
const TodoItem = (props) => {
return (
<li
className={props.completed ? "completed" : "uncompleted"}
key={props.index} onClick={props.handleChangeStatus}
>
{props.title}
</li>
);
}
class TodoList extends Component {
constructor(props) {
super(props);
}
render () {
return (
<ul>
{this.props.todosItems.map((item , index) => (
<TodoItem key={index} {...item} {...this.props} handleChangeStatus={this.props.handleChangeStatus} />
))}
</ul>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos ,
text :""
}
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChangeStatus = this.handleChangeStatus(this);
}
handleTextChange(e) {
this.setState({
text: e.target.value
});
}
handleChangeStatus(){
console.log("hello");
}
handleSubmit(e) {
e.preventDefault();
const newItem = {
title : this.state.text ,
completed : false
}
this.setState((prevState) => ({
todos : prevState.todos.concat(newItem),
text : ""
}))
}
render() {
return (
<div className="App">
<h1>Todos </h1>
<div>
<form onSubmit={this.handleSubmit}>
< input type="text" onChange={this.handleTextChange} value={this.state.text}/>
</form>
</div>
<div>
<TodoList handleChangeStatus={this.handleChangeStatus} todosItems={this.state.todos} />
</div>
<button type="button">asdsadas</button>
</div>
);
}
}
export default App;
The method im trying to use is handleChangeStatus() from the App component in the TodoItem component
Thank you all for your help
This line is wrong:
this.handleChangeStatus = this.handleChangeStatus(this);
//Change to this and it works
this.handleChangeStatus = this.handleChangeStatus.bind(this);
i'm new in react and try to make a button that will remove the sibling class if i click in the current button, and also the current button can remove it self class (toggling),i already try and it seems im not find the clue. here my code below .
export default class Child extends Component {
render(){
return(
<button
onClick={this.props.onClick}
className={this.props.activeMode ? 'active' : ''}>
{this.props.text}
</button>
)
}
}
export default class Parent extends Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: null,
};
}
handleClick (selectedIndex) {
this.setState({
selectedIndex,
});
}
render () {
const array = ["Button 1","Button 2"];
return (
<div>
{array.map((obj, index) => {
const active = this.state.selectedIndex === index;
return <Child
text={obj}
activeMode={active}
key={index}
onClick={() => this.handleClick(index)} />
})}
</div>
)
}
}
I'm not shure that I understood your clearly right
Did you meen something like this?
class Child extends React.Component {
render(){
return(
<button
onClick={this.props.onClick}
className={this.props.activeMode ? 'active' : ''}>
{this.props.text}
</button>
)
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: null,
};
}
handleClick (selectedIndex) {
let selected = this.state.selectedIndex === selectedIndex ? null : selectedIndex;
this.setState({
selectedIndex: selected,
});
}
render () {
const array = ["Button 1","Button 2"];
return (
<div>
{
array.map((obj, index) => {
return <Child text={obj}
activeMode={this.state.selectedIndex === index}
key={index}
onClick={() => this.handleClick(index)} />
})
}
</div>
)
}
}
ReactDOM.render(
<Parent name="World" />,
document.getElementById('container')
);
.active {
color: green;
}
<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="container">
<!-- This element's contents will be replaced with your component. -->
</div>