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"));
Related
based on simple To-Do app, I'd like to understand how I can modify text of list item in place. List item contains 2 divs, first one holds todo's text and second one contains icons (delete, edit). I tried to conditionally render either first div or input inside the li, file ListItem.js, but that didn't work for me.
App.js
class App extends React.Component {
state = {
items: [],
currentValue: '',
clearValue: false
};
submitFormHandler = event => {
event.preventDefault();
if (this.state.currentValue === '') return;
const updatedItems = [...this.state.items];
if (
updatedItems.filter(udtItem => udtItem.value === this.state.currentValue)
.length === 0
) {
updatedItems.push({
id: uuidv4(),
value: this.state.currentValue,
completed: false
});
}
this.setState({ items: updatedItems, clearValue: true });
localStorage.setItem('todos', JSON.stringify(updatedItems));
};
changeInputHandler = event => {
this.setState({
currentValue: event.target.value,
clearValue: false
});
};
deleteItem = id => {
const updatedItems = [...this.state.items].filter(item => item.id !== id);
this.setState({ items: updatedItems });
localStorage.setItem('todos', JSON.stringify(updatedItems));
};
editItem = (event, id) => {
event.stopPropagation();
//do something here
};
deleteAll = () => {
this.setState({ items: [] });
localStorage.removeItem('todos');
};
componentDidMount() {
let todos = localStorage.getItem('todos');
if (todos) {
this.setState({ items: JSON.parse(todos) });
}
}
render() {
const itemList = this.state.items.map(item => (
<ListItem
key={item.id}
data={item}
deleted={this.deleteItem}
edited={this.editItem}
></ListItem>
));
return (
<div className="App">
<img src={trashIcon} alt="Delete" onClick={this.deleteAll} />
<header className="header">To-Do List</header>
<div className="items">
<ul>{itemList}</ul>
</div>
<form onSubmit={this.submitFormHandler}>
<Input
val={this.state.currentValue}
changed={e => this.changeInputHandler(e)}
clear={this.state.clearValue}
/>
</form>
</div>
);
}
}
export default App;
ListItem.js
class ListItem extends Component {
state = {
crossCheck: false,
hidden: true
};
toggleCrossCheck = () => {
const storageItems = JSON.parse(localStorage.getItem('todos'));
storageItems.forEach(item => {
if (item.id === this.props.data.id) {
item.completed = !item.completed;
this.setState({ crossCheck: item.completed });
}
});
localStorage.setItem('todos', JSON.stringify(storageItems));
};
componentDidMount() {
this.setState({ crossCheck: this.props.data.completed });
}
render() {
let classList = 'icon-container';
if (!this.state.hidden) classList = 'icon-container open';
return (
<li
className={this.state.crossCheck ? 'item cross-check' : 'item'}
onClick={this.toggleCrossCheck}
onMouseEnter={() => this.setState({ hidden: false })}
onMouseLeave={() => this.setState({ hidden: true })}
>
<div className="item-text">{this.props.data.value}</div>
<div className={classList}>
<Icon
iconType={trashIcon}
altText="Delete"
clicked={() => this.props.deleted(this.props.data.id)}
></Icon>
<Icon
iconType={editIcon}
altText="Edit"
clicked={event => this.props.edited(event, this.props.data.id)}
></Icon>
</div>
</li>
);
}
}
export default ListItem;
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.
I have this.state with an array, and in array I want to change title, but it is not working. Help me please
Code:
this.state =
{
list: [{title: "1"}, {title: "2"}],
}
// changing title
this.setState({
this.state.list.title: "newTitle"
}],
try this
this.setState({
list: [{title: 'new title'}, { title: 'other title' }]
});
Example
class App extends React.Component {
state = { list: [{title: "1"}, {title: "2"}] };
updateTitle = (index) => {
var { list } = this.state;
list[index].title = `Updated title ${index}`;
this.setState({list});
}
updateTitles = () => {
var { list } = this.state;
list.forEach((item, k) => {
item.title = `Title ${k}`;
});
this.setState({list});
}
render(){
var { list } = this.state;
return (
<div>
<button onClick={this.updateTitles}>Update Title</button>
<ul>
{list.map((item, k) => (
<li onClick={() => this.updateTitle(k)} key={item.title}>{item.title}</li>
))}
</ul>
</div>
);
}
}
I can't get your question correctly. If you want to change value of title key of every child of list array just declare a function like this
function changeValue () {
let newList = this.state.list
newList.forEach(item => {
item.title = 'newTitle'
})
this.setState = ({
list: newList
})
}
My Project is, an array of objects where i get only the names and render on screen of form 3 in 3, with button next and previous change the names, and can to filter for letters.
I would want add a new value, typped on input and clicked in the button add.
My code button:
addItem = () => {
const inValue = {
id: 0,
name: this.state.input
}
this.setState({
filtered: this.state.filtered.concat(inValue),
currentPage: 0
})
}
I would want the value inserted in the filtered array.
My code all:
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
const peoples =[{id:0, name:"Jean"},
{id:1, name:"Jaha"},
{id:2, name:"Rido"},
{id:3, name:"Ja"},
{id:4, name:"Letia"},
{id:5, name:"Di"},
{id:6, name:"Dane"},
{id:7, name:"Tamy"},
{id:8, name:"Tass"},
{id:9, name:"Ts"},
{id:10, name:"Abu"},
{id:11, name:"Ab"}];
this.state = {
elementsPerPage:3,
currentPage:0,
peoples,
input: "",
filtered: peoples,
teste: '',
};
}
getValueInput = (evt) => {
const inputValue = evt.target.value;
this.setState({ input: inputValue });
this.filterNames(inputValue);
}
filterNames = (inputValue)=> {
const { peoples } = this.state;
this.setState({
filtered: peoples.filter(item =>
item.name.includes(inputValue)),
currentPage:0
});
const Oi = this.state.filtered.map(item=>item.name);
if(Oi.length<=0){
alert('Você está adicionando um nome')
}
console.log(Oi)
}
elementsOnScreen = () => {
const {elementsPerPage, currentPage, filtered} = this.state;
return filtered
.map((item) => <li key={item.id}> {item.name} <button onClick={() => this.remove(item.name)}> Delete </button> </li>)
.slice(currentPage*elementsPerPage, currentPage*elementsPerPage + elementsPerPage);
if(this.state.filtered.length < 1){
this.setState({currentPage: this.state.currentPage - 1})
}
}
remove = (id) => {
console.log(this.state.filtered.length)
if(this.state.filtered.length < 0){
this.setState({currentPange: this.state.currenPage - 1})
}
this.setState({filtered: this.state.filtered.filter(item => item.name !== id) })
}
nextPage = () => {
console.log(this.state.filtered)
const {elementsPerPage, currentPage, filtered} = this.state;
if ((currentPage+1) * elementsPerPage < filtered.length){
this.setState({ currentPage: this.state.currentPage + 1 });
}
}
previousPage = () => {
const { currentPage } = this.state;
if(currentPage - 1 >= 0){
this.setState({ currentPage: this.state.currentPage - 1 });
}
}
addItem = () =>{
const inValue = {id:0 ,name: this.state.input}
this.setState({filtered: this.state.filtered.concat(inValue), currentPage: 0})
}
render() {
return (
<div>
<button onClick={this.addItem}> Add </button>
<input type="text" onChange={ this.getValueInput }></input>
<button onClick={this.previousPage}> Previous </button>
<button onClick={this.nextPage}> Next </button>
<h3>Current Page: {this.state.currentPage}</h3>
<ul>Names: {this.elementsOnScreen()}</ul>
</div>
);
}
}
export default App;
You would have the array of objects contained within your state, then use setState
this.state = {
elementsPerPage:3,
currentPage:0,
peoples,
input: "",
filtered: peoples,
teste: '',
peoples: [
{id:0, name:"Jean"},
{id:1, name:"Jaha"},
{id:2, name:"Rido"},
{id:3, name:"Ja"},
{id:4, name:"Letia"},
{id:5, name:"Di"},
{id:6, name:"Dane"},
{id:7, name:"Tamy"},
{id:8, name:"Tass"},
{id:9, name:"Ts"},
{id:10, name:"Abu"},
{id:11, name:"Ab"}];
};
To update the peoples array, you would first need to create a copy of the peoples array, modify the copy, then use setState to update.
let { peoples } = this.state;
peoples.push({ id:12, name:"Jean"})
this.setState({peoples: peoples})
Looks like you are already updating your state with the typed input.
So in your add button you can get the state value and push it to your people array. Something like this:
addItem = () => {
const { inputValue, people } = this.state;
if (!inputValue) return; // check if inputValue has any value
people.push({ id: people.length+1, name: inputValue )} // I don't recommend using sequencial ids like this, you'd better have a handler to generate it for you
this.setState({ people, inputValue: '' }); // update people and clear input
}
Hope it helps
The following code creates a simple movie rating app. Everything works except that when an up or down vote is clicked in one of the array items, the votes state for all items in the array update, rather than just for the item that was clicked. How do I code this so that the vote only applies to the item where it was clicked?
class Ratings extends React.Component {
constructor(props){
super(props);
this.state = {
votes: 0
};
this.add = this.add.bind(this);
this.subtract = this.subtract.bind(this);
this.reset = this.reset.bind(this);
}
add(event){
this.setState ({
votes: this.state.votes + 1
})
}
subtract(event){
this.setState ({
votes: this.state.votes - 1
})
}
reset(event){
this.setState ({
votes: 0
})
}
render () {
this.movies = this.props.list.map(x => {
return (
<div key={x.id} className="movierater">
<MoviePoster poster={x.img}/>
<h1 className="title">{x.name}</h1>
<div className="votewrapper">
<button onClick={this.add}><i className="votebutton fa fa-thumbs-o-up" aria-hidden="true"></i></button>
<Votes count={this.state.votes} />
<button onClick={this.subtract}><i className="votebutton fa fa-thumbs-o-down" aria-hidden="true"></i></button>
</div>
<button onClick={this.reset} className="reset">Reset</button>
</div>
)
});
return (
<div>
{this.movies}
</div>
);
}
}
function MoviePoster(props) {
return (
<img src={props.poster} alt="Movie Poster" className="poster"/>
);
}
function Votes(props) {
return (
<h2>Votes: {props.count}</h2>
);
}
var movieposters = [
{id: 1, img:"http://www.impawards.com/2017/posters/med_alien_covenant_ver4.jpg", name: "Alien Covenant"},
{id: 2, img:"http://www.impawards.com/2017/posters/med_atomic_blonde_ver4.jpg", name: "Atomic Blonde"},
{id: 3, img:"http://www.impawards.com/2017/posters/med_easy_living_ver3.jpg", name: "Easy Living"},
{id: 4, img:"http://www.impawards.com/2017/posters/med_once_upon_a_time_in_venice_ver3.jpg", name: "Once Upon a Time in Venice"},
{id: 5, img:"http://www.impawards.com/2017/posters/med_scorched_earth.jpg", name: "Scorched Earth"},
{id: 6, img:"http://www.impawards.com/2017/posters/med_underworld_blood_wars_ver9.jpg", name: "Underworld: Blood Wars"},
{id: 7, img:"http://www.impawards.com/2017/posters/med_void.jpg", name: "The Void"},
{id: 8, img:"http://www.impawards.com/2017/posters/med_war_for_the_planet_of_the_apes.jpg", name: "War for the Planet of the Apes"},
]
ReactDOM.render(
<Ratings list={movieposters} />,
document.getElementById('app')
);
You need a separate vote count for each movie entity.
This can be accomplished by providing an id to each movie and setting the vote for that specific movie by id.
I would also recommend to extract a new component for a Movie.
this component will get the movieId as a prop and the handlers, it will invoke the up or down handlers and provide to them the current movieId.
See a running example:
class Movie extends React.Component {
onSubtract = () => {
const { subtract, movieId } = this.props;
subtract(movieId);
};
onAdd = () => {
const { add, movieId } = this.props;
add(movieId);
};
onReset = () => {
const { reset, movieId } = this.props;
reset(movieId);
};
render() {
const { movie, votes = 0 } = this.props;
return (
<div className="movierater">
<MoviePoster poster={movie.img} />
<h1 className="title">{movie.name}</h1>
<div className="votewrapper">
<button onClick={this.onAdd}>
<i className="votebutton fa fa-thumbs-o-up" aria-hidden="true" />
</button>
<Votes count={votes} />
<button onClick={this.onSubtract}>
<i className="votebutton fa fa-thumbs-o-down" aria-hidden="true" />
</button>
</div>
<button onClick={this.onReset} className="reset">
Reset
</button>
</div>
);
}
}
class Ratings extends React.Component {
constructor(props) {
super(props);
this.state = {
allVotes: {}
};
}
subtract = movieId => {
const { allVotes } = this.state;
const currentVote = allVotes[movieId] || 0;
const nextState = {
...allVotes,
[movieId]: currentVote - 1
};
this.setState({allVotes: nextState});
};
add = movieId => {
const { allVotes } = this.state;
const currentVote = allVotes[movieId] || 0;
const nextState = {
...allVotes,
[movieId]: currentVote + 1
};
this.setState({ allVotes: nextState });
};
reset = movieId => {
const { allVotes } = this.state;
const nextState = {
...allVotes,
[movieId]: 0
};
this.setState({ allVotes: nextState });
};
render() {
const { allVotes } = this.state;
this.movies = this.props.list.map(x => {
const votes = allVotes[x.id];
return (
<Movie
movieId={x.id}
movie={x}
votes={votes}
reset={this.reset}
subtract={this.subtract}
add={this.add}
/>
);
});
return <div>{this.movies}</div>;
}
}
function MoviePoster(props) {
return <img src={props.poster} alt="Movie Poster" className="poster" />;
}
function Votes(props) {
return <h2>Votes: {props.count}</h2>;
}
var movieposters = [
{
id: 1,
img: "http://www.impawards.com/2017/posters/med_alien_covenant_ver4.jpg",
name: "Alien Covenant"
},
{
id: 2,
img: "http://www.impawards.com/2017/posters/med_atomic_blonde_ver4.jpg",
name: "Atomic Blonde"
},
{
id: 3,
img: "http://www.impawards.com/2017/posters/med_easy_living_ver3.jpg",
name: "Easy Living"
},
{
id: 4,
img:
"http://www.impawards.com/2017/posters/med_once_upon_a_time_in_venice_ver3.jpg",
name: "Once Upon a Time in Venice"
},
{
id: 5,
img: "http://www.impawards.com/2017/posters/med_scorched_earth.jpg",
name: "Scorched Earth"
},
{
id: 6,
img:
"http://www.impawards.com/2017/posters/med_underworld_blood_wars_ver9.jpg",
name: "Underworld: Blood Wars"
},
{
id: 7,
img: "http://www.impawards.com/2017/posters/med_void.jpg",
name: "The Void"
},
{
id: 8,
img:
"http://www.impawards.com/2017/posters/med_war_for_the_planet_of_the_apes.jpg",
name: "War for the Planet of the Apes"
}
];
ReactDOM.render(<Ratings list={movieposters} />, document.getElementById("root"));
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<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>
You need to keep track of votes for each element, thus this.state.votes +/- 1 doesn't do the job, so:
Change
this.state = {
votes: 0
}
to
this.state = {
votes: {}
}
then change the functions:
add(id){
return function(event) {
this.setState ({ ...this.state.votes, [id]: parseInt(this.state.votes[id]) + 1 })
}
}
and the same for subtract. Then change your buttons to:
<button onClick={this.add(x.id)} ... (same for subtract)
and last change your Vote component:
<Votes count={this.state.votes[x.id] || 0} />
On reset just do:
reset(event){
this.setState ({ votes: {} })
}