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>
)
}
}
Related
I have a main component defined as App.js
import "./styles.css";
import { Component } from "react";
import Item from "./components/Item";
class App extends Component {
constructor(props) {
super(props);
this.state = { textInput: "", items: [] };
}
insertItem() {
if (this.state.textInput !== "") {
this.setState((state) => {
const list = state.items.push(state.textInput);
return {
items: list,
textInput: ""
};
});
}
}
deleteItem(index) {
this.setState((state) => {
const list = [...state.items];
list.splice(index, 1);
return {
items: list,
textInput: ""
};
});
}
handleChange(event) {
this.setState({ textInput: event.target.value });
}
render() {
const template = (
<div>
<div>
<input
type="text"
value={this.state.textInput}
onChange={(e) => this.handleChange(e)}
/>
<button onClick={this.insertItem.bind(this)}>Add</button>
</div>
<div>
{this.state.items.map((item, idx) => {
return <Item name={item} removeItem={this.deleteItem.bind(this, idx)} />;
})}
</div>
</div>
);
return template;
}
}
export default App;
and a child Component defined in Item.js
import { Component } from "react";
class Item extends Component {
render() {
return (
<div>
<span>{this.props.name}</span>
<span onClick={this.props.removeItem}>X</span>
</div>
);
}
}
export default Item;
Now my UI looks like
In the above code(App.js) Iam trying to iterate the items and then display the names using the child component. But due to some reason, on entering the text in the input and clicking add its not showing up. Also there are no errors in the console.
Please help, thanks in advance
Edited:
After the recent changes I get this error
You do not need to call this.deleteItem(idx) while passing it to the child.
import "./styles.css";
import { Component } from "react";
import Item from "./components/Item";
class App extends Component {
constructor(props) {
super(props);
this.state = { textInput: "", items: [] };
}
insertItem() {
if (this.state.textInput !== "") {
this.setState((state) => {
const list = state.items.push(state.textInput);
return {
items: list,
textInput: ""
};
});
}
}
deleteItem(index) {
this.setState((state) => {
const list = state.items.splice(index, 1);
return {
items: list,
textInput: ""
};
});
}
handleChange(event) {
this.setState({ textInput: event.target.value });
}
render() {
const template = (
<div>
<div>
<input
type="text"
value={this.state.textInput}
onChange={(e) => this.handleChange(e)}
/>
<button onClick={this.insertItem.bind(this)}>Add</button>
</div>
<div>
{this.state.items.map((item, idx) => {
return <Item name={item} removeItem={this.deleteItem.bind(this, idx)} />;
})}
</div>
</div>
);
return template;
}
}
export default App;
Updating state in react requires a new reference to objects.You're using Array#push. It will not detect your new change and the DOM will not update. You need to return a new reference.
insertItem() {
if (this.textInput === "") {
this.setState((state) => {
// const list = state.items.push(state.textInput);
const list = [...state.items, state.textInput];
return {
list,
textInput: ""
};
});
}
}
In order to track the array, you must add the key attribute:
{this.state.items.map((item, idx) => {
return <Item key={idx} name={item} removeItem={this.deleteItem(idx)} />;
})}
Here I used the index, but it would be better to use some ID of your model.
UPDATE:
I'd move the handler binding in the constructor:
constructor(props) {
super(props);
this.state = { textInput: "", items: [] };
this.insertItem = this.insertItem.bind(this);
}
Then:
<button onClick={this.insertItem}>Add</button>
UPDATE 2:
Okay, it seems you have several mistakes (and I didn't notice them at first glance).
Here is the complete working source (tested):
class App extends Component {
constructor(props) {
super(props);
this.state = { textInput: "", items: [] };
}
insertItem() {
if (this.state.textInput !== "") {
this.setState((state) => {
//const list = state.items.push(state.textInput);
const list = [...state.items, state.textInput];
return {
items: list,
textInput: ""
};
});
}
}
deleteItem(index) {
this.setState((state) => {
const list = [...state.items];
list.splice(index, 1);
return {
items: list,
textInput: ""
};
});
}
handleChange(event) {
this.setState({ textInput: event.target.value });
}
render() {
const template = (
<div>
<div>
<input
type="text"
value={this.state.textInput}
onChange={(e) => this.handleChange(e)}
/>
<button onClick={this.insertItem.bind(this)}>Add</button>
</div>
<div>
{this.state.items.map((item, idx) => {
return <Item key={idx} name={item} removeItem={this.deleteItem.bind(this, idx)} />;
})}
</div>
</div>
);
return template;
}
}
export default App;
I think you should move display of template into return statement rather than inside render
...
render() {
return (
<div>
<div>
<input
type="text"
value={this.state.textInput}
onChange={(e) => this.handleChange(e)}
/>
<button onClick={this.insertItem.bind(this)}>Add</button>
</div>
<div>
{this.state.items.map((item, idx) => {
return <Item name={item} removeItem={this.deleteItem.bind(this, idx)} />;
})}
</div>
</div>
);
}
I have built a toggle component that shows one out of two components based on whether the Purchase is true or not. Now I want to select from 3 components and I'm struggling with refactoring this so it's clean and works. What is the best way to do this if I'm adding Component3 both to the toggle selectors and the component import?
import Component1 from "./component1";
import Component2 from "./component2";
class App extends Component {
constructor(props) {
super(props);
// this.toggle = this.toggle.bind(this);
this.state = {
popoverOpen: false,
isPurchase: true,
};
this.switchState = this.switchState.bind(this);
}
switchState(flag) {
this.setState({ isPurchase: flag });
}
render() {
return (
<div className={styles.cardBody}>
<div className={styles.row}>
<div className={styles.col12}>
<div
className={`${
styles.positionRelative
} ${styles.formGroup}`}>
<div
style={{
fontWeight:
"bolder",
color: "#7192a6",
}}>
<span
className={
this.state
.isPurchase
? `${
styles.switchState
}`
: `${
styles.switchState
} ${
styles.unselected
}`
}
onClick={() => {
this.switchState(
true,
);
}}>
Component1{" "}
</span>
/
<span
className={
this.state
.isPurchase
? `${
styles.switchState
} ${
styles.unselected
}`
: `${
styles.switchState
}`
}
onClick={() => {
this.switchState(
false,
);
}}>
{" "}
Component2
</span>
</div>
</div>
</div>
</div>
<div className={styles.row}>
<div className={styles.col12}>
<Component1
isPurchase={
this.state.isPurchase
}
/>
<Component2
isPurchase={
this.state.isPurchase
}
/>
</div>
</div>
</div>
);
}
}
And in the component itself I'm checking this in the state
class Component1 extends Component {
constructor(props) {
super(props);
this.state = {
isPurshase: props.isPurchase,
popoverOpen: false,
};
}
And inside the return display/hide this way
<div style={{ display: this.props.isPurchase ? "" : "none" }}>
You can do something like this to be more clean.
class App extends Component {
state = {
condition: 'condition1',
};
handleSwitch = () => {
const {condition} = this.state;
let newCondition = '';
switch (condition) {
case 'condition1':
newCondition = 'condition2';
break;
case 'condition2':
newCondition = 'condition3';
break;
case 'condition3':
newCondition = 'condition1';
break;
default:
newCondition = 'condition1';
}
this.setState({
condition: newCondition,
});
};
renderSwitch = () => {
<button onClick={() => this.handleSwitch()}> CHANGE </button>;
};
renderComponent = () => {
const {condition} = this.state;
switch (condition) {
case 'condition1':
return (<ComponentOne/>);
case 'condition2':
return (<ComponentTwo/>);
case 'condition3':
return (
<div>
<ComponentOne/>
<ComponentOne/>
</div>
);
default:
return null;
}
}
render() {
return (
<div>
{this.renderSwitch()}
{this.renderComponent()}
</div>
);
}
}
i have created delete functionality,in which when user clicks delete[X] it has to get deleted from the respected row w.r.t datagrid view used in react
passing id as an parameter
used _find index(loadlash)
problem:
1) selected Rows are not getting deleted.
code:
onclick event
<div>
<button onClick={() => this.deleteHandler(params.value)}>X</button>
</div>
Delete code:
deleteHandler = (id) => {
const arrayPerson = this.props.rowData;
const index = _.findIndex(this.props.rowData, { id: id });
if (arrayPerson.indexOf(id) > -1) {
arrayPerson.splice(index, 1);
this.setState({ rows: arrayPerson });
}
can any one help me on this issue.
This is a working example for this what if different is that you are passing the data from parent to child for that u can do multiple thing.
The Parent Component.
class TestComp extends React.Component {
constructor() {
super();
this.state = { listItems: [{ id: 1, text: "abc" }, { id: 2, text: "bcd" }, { id: 3, text: "dec" }] }
this.handleRowClick = this.handleRowClick.bind(this);
}
handleRowClick(id) {
const listItems = this.state.listItems;
let copyListItems = [];
listItems.forEach(function (item) {
let objCopy = Object.assign({}, item);
copyListItems.push(objCopy);
});
let updatedArray = _.remove(copyListItems, function (item) { return item.id == id; });
this.setState({ listItems: copyListItems });
}
render() {
return (
<div>
<ChildComp
list={this.state.listItems}
deleteHandler={this.handleRowClick}
/>
</div>
)
}
}
The Child Component that is receiving List items as props and when clicked the parent handler is being called which will update the List items and setState of Parent which will update the list being provided to child component hence it will be rendered.
export class ChildComp extends React.Component {
render() {
let list = this.props.list.map((obj) =>
<div key={obj.id} id={obj.id} style={{ padding: "10px", backgroundColor: "grey", border: "1px solid black" }} onClick={() => { this.props.deleteHandler(obj.id) }}> {obj.text} </div>
);
return (
<div >
{list}
</div>
)
}
}
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>
)
}
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>
)
}