I need to control a list of list of input like this:
import React from "react";
import "./styles.css";
const testData = [
{
name: "list 1",
content: [
{
id: 1,
value: " this is value 1 of list 1"
},
{
id: 2,
value: " this is value 2 of list 1"
},
{
id: 3,
value: " this is value 3 of list 1 "
}
]
},
{
name: "list 2",
content: [
{
id: 1,
value: " this is value 1 of list 2"
},
{
id: 2,
value: " this is value 2 of list 2"
},
{
id: 3,
value: " this is vainput'liste 3 of list 2"
}
]
}
];
class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
data: this.props.list
};
}
handleChange = (e, index) => {
const newData = this.state.data;
newData[index] = e.target.value;
this.setState({
data: newData
});
};
updateData = newData => {
this.setState({
data: newData
});
};
add = () => {
const newData = this.state.data;
newData.push({
id: newData.length + 1,
value: "new data"
});
this.setState({
data: newData
});
};
remove = (e, index) => {
const newData = this.state.data;
newData.splice(index, 1);
this.setState({
data: newData
});
};
save = (e, index) => {
//TODO
};
render() {
return (
<div className="App">
<button onClick={this.add}> Add </button>
<button onClick={this.save}> save </button>
<tr>input'list
{this.state.data.map((item, index) => (
<td>
<input
value={item.value}
onChange={e => this.handleChange(e, index)}
/>
title
<button onClick={e => this.remove(e, index)}> remove </button>
</td>
))}
</tr>
</div>
);
}
}input'list
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: testData[0].name,
data: testData[0].content
};
this.ref = React.createRef();
}
changeList = (e, index) => {
this.setState({
title: testData[index].name,
data: testData[index].content
});
this.ref.current.updateData(testData[index].content);
};
render() {
return (
<>
{testData.map((item, index) => (
<button onClick={e => this.changeList(e, index)}>
{" "}
{item.name}{" "}
</button>
))}
this is {this.state.title}
<MyList ref={this.ref} list={this.state.data}>
{" "}
</MyList>{" "}
</>
);
}
}
export default App;
I need some basic method like show list, save, add more input in list, remove ... And with my code, everything works fine. But I feel it's not clean since there's many ways to do 1 thing in React. Can someone read it and give me some feedback? There's 2 things I thought are not good, but I don't know how to improve:
function handleChange of component MyList: I have to calculator index of input to re-render.
I use A Ref to update and get Data from component MyList.
Related
I want to change the text of a specific button when I click on that button in React. But the issue is when I click the button the title will change for all buttons!
class Results extends Component {
constructor() {
super();
this.state = {
title: "Add to watchlist"
}
}
changeTitle = () => {
this.setState({ title: "Added" });
};
render() {
return (
<div className='results'>
{
this.props.movies.map((movie, index) => {
return (
<div className='card wrapper' key={index}>
<button className='watchListButton' onClick={this.changeTitle}>{this.state.title}</button>
</div>
)
})
}
</div>
)
}
}
You would need to come up with a mechanism to track added/removed titles per movie. For that, you would have to set your state properly. Example:
this.state = {
movies: [
{id: 1, title: 'Casino', added: false},
{id: 2, title: 'Goodfellas', added: false}
]
This way you can track what's added and what's not by passing the movie id to the function that marks movies as Added/Removed. I have put together this basic Sandbox for you to get you going in the right direction:
https://codesandbox.io/s/keen-moon-9dct9?file=/src/App.js
And here is the code for future reference:
import React, { Component } from "react";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
movies: [
{ id: 1, title: "Casino", added: false },
{ id: 2, title: "Goodfellas", added: false }
]
};
}
changeTitle = (id) => {
this.setState(
this.state.movies.map((item) => {
if (item.id === id) item.added = !item.added;
return item;
})
);
};
render() {
const { movies } = this.state;
return (
<div className="results">
{movies.map((movie, index) => {
return (
<div className="card wrapper" key={index}>
{movie.title}
<button
className="watchListButton"
onClick={() => this.changeTitle(movie.id)}
>
{movie.added ? "Remove" : "Add"}
</button>
</div>
);
})}
</div>
);
}
}
export default App;
I'm now leaning react and having a lot of fun with, but also having my first issue than I cannot solved by myself.
I would like to use a child component to filter on his parent list. I mean I would like to use a child component to filter a list from his parent.. I hope my code would be more understandable than my explications...
Does anyone can explain how should I use onclick in both class and maybe optimize my code ? Thanks
product.jsx
class Items extends Component {
state = {
count: 0,
carbondioxide: [
{ key: 90, name: "All", value: "All" },
{ key: 91, name: "Still Water", value: "Still Water" },
{ key: 92, name: "Sparkling Water", value: "Sparkling Water" },
],
brands: [
{
key: 0,
name: "Fountain",
category: "Still Water",
},
{
key: 1,
name: "Kitchen",
category: "Sparkling Water",
},
{
key: 2,
name: "Shower",
category: "Still Water",
},
],
filterBrands: [],
};
componentDidMount() {
this.setState({
filterBrands: this.state.brands,
});
}
handleClick = (name) => {
let filterBrands = [];
if (name === "All") {
filterBrands = this.state.brands;
} else {
filterBrands = this.state.brands.filter(
(brands) => brands.category === name
);
}
this.setState({ filterBrands });
};
}
render() {
return (
<div className="ctg-flex">
<GroupButtonFilter
carbondioxide={this.state.carbondioxide}
onClick={this.handleClick.bind(this, name)} //filterBrands onclick
//onClick={() => {
// this.handleClick.bind(this, this.state.carbondioxide.name);
//}}
/>
{this.state.filterBrands.map((id, brands) => (
<Item
key={id.key}
dataImg={id.imageUrl}
dataText={id.name}
dataPrice={id.price}
/>
))}
</div>
);
}
}
export default Items;
and GroupeButtonFilter.jsx
class GroupButtonFilter extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="group-button">
{this.props.carbondioxide.map(({ name, value }) => (
<Button
key={this.props.carbondioxide.key}
value={this.props.carbondioxide.value}
// onClick={this.props.handleClick.bind(this, this.name)} //filterBrands onclick
onClick={this.props.onClicktest} //filterBrands onclick
>
{name}
</Button>
))}
</div>
);
}
}
There are couple of issue on your code.
You are binding your:
onClick={() => {
this.handleClick.bind(this, this.state.carbondioxide.name);
}}
and your handleClick is using arrow function so you can remove bind from this i.e
onClick={() => {
this.handleClick( this.state.carbondioxide.name);
}}
Still its not correct. which is our second point
In GroupButtonFilter you are expecting onClicktest and while passing in GroupButtonFilter you are sending onClick this can reduce to this:
<Button
key={this.props.carbondioxide.key}
value={this.props.carbondioxide.value}
// onClick={this.props.handleClick.bind(this, this.name)} //filterBrands onclick
onClick={this.props.onClicktest} //filterBrands onclick
>
{name}
</Button>
You need to pass the value that button is clicked but in onClick you are sending carbondiooxide name. You need to do this. Inside GroupFilter:
<button
key={this.props.carbondioxide.key}
// onClick={this.props.handleClick.bind(this, this.name)} //filterBrands onclick
onClick={() => this.props.onClicktest(name)} //filterBrands onclick
>
{name}
</button>
Here is complete code:
import React, { Component } from "react";
import "./styles.css";
class GroupButtonFilter extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="group-button">
{this.props.carbondioxide.map(({ name, value }) => (
<button
key={this.props.carbondioxide.key}
// onClick={this.props.handleClick.bind(this, this.name)} //filterBrands onclick
onClick={() => this.props.onClicktest(name)} //filterBrands onclick
>
{name}
</button>
))}
</div>
);
}
}
class Items extends Component {
state = {
count: 0,
carbondioxide: [
{ key: 90, name: "All", value: "All" },
{ key: 91, name: "Still Water", value: "Still Water" },
{ key: 92, name: "Sparkling Water", value: "Sparkling Water" }
],
brands: [
{
key: 0,
name: "Fountain",
category: "Still Water"
},
{
key: 1,
name: "Kitchen",
category: "Sparkling Water"
},
{
key: 2,
name: "Shower",
category: "Still Water"
}
],
filterBrands: []
};
componentDidMount() {
this.setState({
filterBrands: this.state.brands
});
}
handleClick = (name) => {
console.log(name);
let filterBrands = [];
if (name === "All") {
filterBrands = this.state.brands;
} else {
filterBrands = this.state.brands.filter(
(brands) => brands.category === name
);
}
this.setState({ filterBrands });
};
render() {
return (
<div className="ctg-flex">
<GroupButtonFilter
carbondioxide={this.state.carbondioxide}
onClicktest={this.handleClick}
// onClicktest={this.handleClick.bind(
// this.state.carbondioxide.name,
// this.name
// )}
/>
{this.state.filterBrands.map((id, brands) => (
<div key={id.name}>{id.name}</div>
))}
</div>
);
}
}
export default function App() {
return (
<div className="App">
<Items />
</div>
);
}
here is the demo:https://codesandbox.io/s/affectionate-fire-zcqc1?file=/src/App.js:0-2168
write a function in your parent component
handleClick = () => {
/// do your filtering
}
pass this handleClick to your child component
<GroupButtonFilter handleClickInParent ={this.handleClick} />
....
</GroupButtonFilter>
then inside your child component
<Button. onClick={() => this.props.handleClick())}>
</Button>
I am trying to add draggable attributes to a component that is rendered out, however, I don't know where to add them in exactly. So they at least render out but the array does no update on OnDragEnd when the item is dropped. The component:
const Item = props => {
const finishedButton = <button onClick={props.handleComplete} className="finishedButton">✔</button>
return (
<li className="background"
draggable
onDragStart={props.dragStart}
onDragEnd={props.dragEnd}
>
{finishedButton}{props.item}
</li>
and in the render in App
<ul onDragOver={e => this.dragOver(e)}>
{this.state.items.map((item, i)=> (
<Item
data-id={i}
key={i}
dragStart={e => this.dragStart(e)}
dragEnd={e => this.dragEnd(e)}
item={item.text}
/>
))}
</ul>
Codepen: https://codepen.io/Matt-dc/pen/zbYKNv
In your dragEnd function this.draggedElem is not defined. And therefore prevIndex & newIndex is undefined as well, which leads to data.splice returning the old state.
I am not sure about your logic, but seems it works
let placeholder = document.createElement("li");
placeholder.className = "placeholder";
const Item = props => {
const finishedButton = <button onClick={props.handleComplete} className="finishedButton">✔</button>
return (
<li className="background"
draggable
onDragStart={props.dragStart}
onDragEnd={props.dragEnd}
onDragOver={props.dragOver}
>
{finishedButton}{props.item}
>
</li>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{id: 1, text: "clean car", complete: false},
{id: 2, text: "wash dog", complete: false},
{id: 3, text: "water plants", complete: false},
{id: 4, text: "prune shrubs", complete: false},
{id: 5, text: "tidy house", complete: false}
],
input: ""
}
this.dragStart = this.dragStart.bind(this);
this.dragOver = this.dragOver.bind(this);
this.dragEnd = this.dragEnd.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange = e => {
this.setState({
input: e.target.value
});
}
handleSubmit = () => {
let newItem = {};
newItem.id = uuid.v4();
newItem.text = this.state.input;
newItem.complete = false;
let newItems = this.state.items.concat(newItem);
this.setState({
items: newItems,
});
this.setState({input: ''})
}
handleComplete = e => {
}
dragStart = e => {
this.draggedElem = e.target;
e.dataTransfer.setData('text/html', this.draggedElem);
}
dragOver = e => {
e.preventDefault();
//this.draggedElem.style.display = "none";
if(e.target.className === 'placeholder') return;
this.newIndex = e.target
}
dragOverItem = (id,e) => {
this.to = id;
}
dragEnd = (from,e) => {
let data = this.state.items;
let prevIndex = from;
let newIndex = this.to;
console.log(`from ${prevIndex} to ${newIndex}`)
//if(prevIndex < newIndex) newIndex --;
data.splice(newIndex, 0, data.splice(prevIndex, 1)[0]);
this.setState({ items: data });
}
render() {
return(
<div>
<input onChange={this.handleChange} value={this.state.input} />
<button onClick={this.handleSubmit}>Add item</button>
<ul onDragOver={this.dragOver}>
{this.state.items.map((item, i)=> (
<Item
data-id={i}
key={i}
dragOver={this.dragOverItem.bind(this,i)}
dragStart={e => this.dragStart(e)}
dragEnd={this.dragEnd.bind(this, i)}
item={item.text}
/>
))}
</ul>
<p>{this.state.input}</p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
I have an issue with my reactjs code where the render() function won't print an array in the order that it is stored in my state object.
Here's my code which is pretty simple:
import React from "react";
export default class DonationDetail extends React.Component {
constructor(props) {
super(props);
this.state = { content: [] };
}
componentDidMount() {
let state = this.state;
state.content.push({ food: "burger" });
state.content.push({ food: "pizza" });
state.content.push({ food: "tacos" });
this.setState(state);
}
addPaymentItem() {
const item = { food: "" };
let state = this.state;
state.content.unshift(item);
this.setState(state);
}
render() {
console.log(this.state);
let ui = (
<div>
<button type="button" onClick={() => this.addPaymentItem()}>
add to top
</button>
{this.state.content.map((item, key) => (
<input type="text" key={key} defaultValue={item.food} />
))}
</div>
);
return ui;
}
}
When you press the button add to top, a new item is placed to the front of the state.content array, which you can verify from the console.log(this.state) statement. But what's unusual is that the HTML that is generated does NOT add this new item to the top of the user interface output. Instead, another input field with the word taco is placed at the bottom of the list in the user interface.
Why won't ReactJS print my state.content array in the order that it is actually stored?
You can use the array index as key when the order of the elements in the array will not change, but when you add an element to the beginning of the array the order is changed.
You could add a unique id to all your foods and use that as key instead.
Example
class DonationDetail extends React.Component {
state = { content: [] };
componentDidMount() {
const content = [];
content.push({ id: 1, food: "burger" });
content.push({ id: 2, food: "pizza" });
content.push({ id: 3, food: "tacos" });
this.setState({ content });
}
addPaymentItem = () => {
const item = { id: Math.random(), food: "" };
this.setState(prevState => ({ content: [item, ...prevState.content] }));
};
handleChange = (event, index) => {
const { value } = event.target;
this.setState(prevState => {
const content = [...prevState.content];
content[index] = { ...content[index], food: value };
return { content };
});
};
render() {
return (
<div>
<button type="button" onClick={this.addPaymentItem}>
add to top
</button>
{this.state.content.map((item, index) => (
<input
type="text"
key={item.id}
value={item.food}
onChange={event => this.handleChange(event, index)}
/>
))}
</div>
);
}
}
ReactDOM.render(<DonationDetail />, 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>
Instead of:
componentDidMount() {
let state = this.state;
state.content.push({ food: "burger" });
state.content.push({ food: "pizza" });
state.content.push({ food: "tacos" });
this.setState(state);
}
Try
componentDidMount() {
this.setState(prevState => ({
content: [
...prevState.content,
{ food: "burger" },
{ food: "pizza" },
{ food: "tacos" },
]
}));
}
and
addPaymentItem() {
const item = { food: "" };
let state = this.state;
state.content.unshift(item);
this.setState(state);
}
to
addPaymentItem() {
this.setState(prevState => ({
content: [
{ food: "" },
...prevState.content,
]
}));
}
I am having 4 buttons each button have name id and selected boolean flag.
What I am trying to achieve is, on click of button, boolean button flag should be changed of that particular button. For this, I need to setState in map function for that particular button Id.
My issue is I am unable to setState in map function for that particular clicked button, its btnSelected should be changed
My aim is to create a multi-select deselect button.Its kind of interest selection for the user and based on that reflect the UI as well my array. Here is my code.
Thanks in anticipation.
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
export default class Test extends Component {
constructor(props, context) {
super(props, context);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: "",
numbers: [1, 2, 3, 4, 5],
posts: [
{
id: 1,
topic: "Animal",
btnSelected: false
},
{
id: 2,
topic: "Food",
btnSelected: false
},
{
id: 3,
topic: "Planet",
btnSelected: false
},
{ id: 4, topic: "Nature", btnSelected: false }
],
allInterest: []
};
}
handleChange(e) {
//console.log(e.target.value);
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value });
}
getInterest(id) {
this.state.posts.map(post => {
if (id === post.id) {
//How to setState of post only btnSelected should change
}
});
console.log(this.state.allInterest);
if (this.state.allInterest.length > 0) {
console.log("Yes we exits");
} else {
console.log(id);
this.setState(
{
allInterest: this.state.allInterest.concat(id)
},
function() {
console.log(this.state);
}
);
}
}
render() {
return (
<div>
{this.state.posts.map((posts, index) => (
<li
key={"tab" + index}
class="btn btn-default"
onClick={() => this.getInterest(posts.id)}
>
{posts.topic}
<Glyphicon
glyph={posts.btnSelected === true ? "ok-sign" : "remove-circle"}
/>
</li>
))}
</div>
);
}
}
Here's how you do something like this:
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (e) => {
const { posts } = this.state;
const { id } = e.target;
posts[id].selected = !this.state.posts[id].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="radio" id={i} key={i} checked={p.selected} onClick={this.handleClick} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here.
You can do this by passing the index from the map into each button's handleClick function, which would then return another function that can be triggered by an onClick event.
In contrast to Colin Ricardo's answer, this approach avoids adding an id prop onto each child of the map function that is only used for determining the index in the handleClick. I've modified Colin's example here to show the comparison. Notice the event parameter is no longer necessary.
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (index) => () => {
const { posts } = this.state;
posts[index].selected = !this.state.posts[index].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="checkbox" key={i} checked={p.selected} onClick={this.handleClick(i)} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here