I am creating on drag and drop functionality using react. but my handleDrop function is not being invoked. It means my onDrop event is not working. not found any solution. thanks in advance.
I am sharing my code snippet with you guys, please have a look.:
import React, { useState, useRef } from 'react';
const App: React.FC = () => {
const [cards, setCards] = useState([
{ id: 1, title: 'Card 1', list: 'TODO' },
{ id: 2, title: 'Card 2', list: 'TODO' },
{ id: 3, title: 'Card 3', list: 'COMPLETED' },
{ id: 4, title: 'Card 4', list: 'COMPLETED' },
]);
const dragItem = useRef<HTMLDivElement>(null);
const [draggedOver, setDraggedOver] = useState<null | number>(null);
const handleDragStart = (e: React.DragEvent<HTMLDivElement>, id: number) => {
console.log("drag start");
dragItem.current = e.target as HTMLDivElement;
e.dataTransfer.setData('id', id.toString());
};
const handleDragEnter = (id: number) => {
console.log("entered");
setDraggedOver(id);
};
const handleDragLeave = () => {
console.log("leaved");
setDraggedOver(null);
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>, list: string) => {
console.log("dropped");
e.preventDefault();
const id = Number(e.dataTransfer.getData('id'));
const newCards = [...cards];
const card = newCards.find((c) => c.id === id);
card!.list = list;
setCards(newCards);
setDraggedOver(null);
};
return (
<div className="app">
<div className="lists">
<div className="list todo">
<h2>TODO</h2>
{cards
.filter((c) => c.list === 'TODO')
.map((card) => (
<div
key={card.id}
className="card"
draggable={true}
onDragStart={(e) => handleDragStart(e, card.id)}
onDragEnter={() => handleDragEnter(card.id)}
onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e, 'COMPLETED')}
ref={dragItem}
>
{card.title}
</div>
))}
</div>
<div className="list completed">
<h2>COMPLETED</h2>
{cards
.filter((c) => c.list === 'COMPLETED')
.map((card) => (
<div
key={card.id}
className="card"
draggable={true}
onDragStart={(e) => handleDragStart(e, card.id)}
onDragEnter={() => handleDragEnter(card.id)}
onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e, 'TODO')}
>
{card.title}
</div>
))}
</div>
</div>
</div>
)
}
export default App;
can anyone help me here? am I missing something?
Note:-I want to do this without using a third-party library.
Seems like this question is already answered here
Why is ondrop not working?
Thanks to azium
You will need to add onDragOver event to those divs as well
Related
I'm implementing a Like and Dislike Button, and I wanna that when I click them will be with other colors, but just the clicked component, when I click all buttons change the state, can anybody help me?
`
const indexPost = async () => {
const data = await api.get('/api/posts')
if(data.data.length !=0){
const dataArray = data.data
if(dataArray.length === 0) {
return
}else{
return(
setPost(dataArray.map( data => (
<Post key={data._id} id={data._id} title={data.title} text={data.text}>
<Like id={data._id}></Like>
</Post>
)))
)
}
}
}
export default function Like({itemId}) {
const context = useContext(notificationContext)
const {isLoved, Like, Loved, Unlike, isLike, isUnlike, setIsLike, setIsUnlike, setIsLoved } = context
return(
<div className={styles.likeContainer} key={itemId}>
{isLike ? (
<button className={styles.likeContent} onClick={() => setIsLike(false)}><Icon.ThumbsUp className={styles.Icon} fill="#5CB0BB" ></Icon.ThumbsUp></button>) :
(<button className={styles.likeContent} onClick={() => Like() }><Icon.ThumbsUp className={styles.Icon} ></Icon.ThumbsUp></button>)}
{isLoved ?
(<button className={styles.likeContent} onClick={() => setIsLoved(false)}><Icon.Heart className={styles.Icon} fill="red" ></Icon.Heart> </button>) :
(<button className={styles.likeContent} onClick={() => Loved() }><Icon.Heart className={styles.Icon} ></Icon.Heart></button>)}
{isUnlike ? (
<button className={styles.likeContent} onClick={() => setIsUnlike(false)}><Icon.ThumbsDown className={styles.Icon} fill="#702BA6" ></Icon.ThumbsDown> </button>) :
(<button className={styles.likeContent} onClick={() => Unlike()}><Icon.ThumbsDown className={styles.Icon} ></Icon.ThumbsDown></button>
)}
</div>
)
};
I have implemented the similar one in my project, it is very basic , it shows how to update the likes , you need to handle the cases of user authentication and stuff
App.js
import { useState, useEffect, createContext, useReducer } from "react";
import { updateArrayOfObj } from "./utils";
import AllPosts from "./AllPosts";
export const PostsContext = createContext();
const initialState = {
posts: [
{
_id: "1",
name: "Browny",
image: "http://placekitten.com/200/310",
likes: 0,
love: 0,
dislikes: 0
},
{
_id: "2",
name: "Blacky",
image: "http://placekitten.com/200/320",
likes: 0,
love: 0,
dislikes: 0
},
{
_id: "3",
name: "SnowWhite",
image: "http://placekitten.com/200/300",
likes: 0,
love: 0,
dislikes: 0
}
]
};
const reducer = (state, action) => {
switch (action.type) {
case "UPDATE_POST":
return {
...state,
posts: updateArrayOfObj(
state.posts,
action.payload.obj,
"_id",
action.payload._id
)
};
case "CREATE_POST":
return {
...state,
posts: [...state.posts, ...action.payload.data]
};
case "DELETE_POST":
return {
...state,
posts: state.posts.filter((ele) => ele._id !== action.payload._id)
};
default:
return state;
}
};
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<PostsContext.Provider
value={{
state,
dispatch
}}
>
<div className="App">
<AllPosts />
</div>
</PostsContext.Provider>
);
}
PostsAll.js
import Post from "./Post";
import { PostsContext } from "./App";
import { useContext } from "react";
export default function AllPosts() {
const { state } = useContext(PostsContext);
return (
<div className="allPosts">
{state.posts.map((item) => {
return (
<Post
name={item.name}
image={item.image}
likes={item.likes}
love={item.love}
dislikes={item.dislikes}
id={item._id}
key={item._id}
/>
);
})}
</div>
);
}
Post.js
import { PostsContext } from "./App";
import { useContext } from "react";
export default function Post(props) {
const { state, dispatch } = useContext(PostsContext);
const handleUserInteraction = (type, id) => {
dispatch({
type: "UPDATE_POST",
payload: {
obj: { [type]: props[type] + 1 },
_id: id
}
});
};
return (
<div className="post">
<h3>{props.name}</h3>
<img src={props.image} alt="cat" />
<br />
<button onClick={() => handleUserInteraction("likes", props.id)}>
{props.likes} Like
</button>{" "}
<button onClick={() => handleUserInteraction("love", props.id)}>
{props.love} Love
</button>{" "}
<button onClick={() => handleUserInteraction("dislikes", props.id)}>
{props.dislikes} Dislike
</button>
</div>
);
}
You can refer to this codesandbox to implement the same
You can use onClick() on each like button and attach it with a function, then you can get the value of that particular like with e.currentTarget.id and change its css/style the way you want.
const handleClick=(e)=>
{
console.log(e.currentTarget.id);
}
I created a todo list by using react. I get some problem that I want to create checkbox but my checkbox it does not work and I cannot solve :( I don't know what's wrong with that.
I set the data for each task and then I need to change the completed of some task, but it cannot click and change the completed task
This is my code
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todoData,
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if(todo.id === id) {
todo.completed = !todo.completed
// console.log(todo.completed)
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItem = this.state.todos.map(item => <TodoItem key={item.id} item={item}
handleChange={this.handleChange}/>)
return (
<div>
<h1 className="header">My Todo Lists</h1>
{todoItem}
</div>
)
}
}
function TodoItem(props) {
let textItem = props.item.completed === true ?
<del>{props.item.text}</del> : props.item.text
return (
<div className="list">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p className="item">{textItem}</p>
</div>
)
}
And this is my data
const todoData = [
{
id: 1,
text: "Practice coding",
completed: false
},
{
id: 2,
text: "Grocery shopping",
completed: true
},
{
id: 3,
text: "Wash the dishes",
completed: true
},
{
id: 4,
text: "Take out the trash",
completed: false
},
{
id: 5,
text: "Teach my brother homework",
completed: false
}
]
Thank you for helping :)
Looks like on your handleChange you are mutating the existing state on your map transformation. you must return a new state instead.
Replace your handleChange with the following code:
handleChange(id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
return {
...todo,
completed: todo.id === id ? !todo.completed : todo.completed
};
});
return {
todos: updatedTodos
};
});
}
I have set of components where it would consist of input fields along with text rows.
As given in the image the users should be able to add categories and description. After adding them they will be rendered as a list of components. like this
Inside a category there will be tags as given in the above image and to add them i have to add a input component. This input component should be available only when the user clicks on the Add tag button below each category row. When a user clicks on it,it should enable the input(should render a input component inside the selected category row) and should be able to type the tag name on it and save it. I need to make this input field enable only when i click on the add tag button. and it should enable only in the selected category row. This is the code that i have tried.
import React, { Component, Fragment } from "react";
import { Button, Header, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import ReactDOM from "react-dom";
class App extends Component {
state = {
category: "",
description: "",
categories: []
};
onChange = (e, { name, value }) => {
this.setState({ [name]: value });
};
addCategory = () => {
let { category, description } = this.state;
this.setState(prevState => ({
categories: [
...prevState.categories,
{
id: Math.random(),
title: category,
description: description,
tags: []
}
]
}));
};
addTag = id => {
let { tag, categories } = this.state;
let category = categories.find(cat => cat.id === id);
let index = categories.findIndex(cat => cat.id === id);
category.tags = [...category.tags, { name: tag }];
this.setState({
categories: [
...categories.slice(0, index),
category,
...categories.slice(++index)
]
});
};
onKeyDown = e => {
if (e.key === "Enter" && !e.shiftKey) {
console.log(e.target.value);
}
};
tags = tags => {
if (tags && tags.length > 0) {
return tags.map((tag, i) => {
return <Header key={i}>{tag.name}</Header>;
});
}
};
enableTagIn = id => {};
categories = () => {
let { categories } = this.state;
return categories.map(cat => {
return (
<Fragment key={cat.id}>
<Header>
<p>
{cat.title}
<br />
{cat.description}
</p>
</Header>
<Input
name="tag"
onKeyDown={e => {
this.onKeyDown(e);
}}
onChange={this.onChange}
/>
<Button
onClick={e => {
this.addTag(cat.id);
}}
>
Add
</Button>
{this.tags(cat.tags)}
</Fragment>
);
});
};
render() {
return (
<Fragment>
{this.categories()}
<div>
<Input name="category" onChange={this.onChange} />
<Input name="description" onChange={this.onChange} />
<Button onClick={this.addCategory}>Save</Button>
</div>
</Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
This is the codesandbox url.
Any idea on how to achieve this?.
I changed your code by using function components and react hooks and i created category component which has it own state like this:
import React, { Fragment } from "react";
import { Button, Header, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import ReactDOM from "react-dom";
const App = () => {
const [Category, setCategory] = React.useState({
title: "",
description: ""
});
const [Categories, setCategories] = React.useState([]);
return (
<div>
{console.log(Categories)}
<Input
value={Category.title}
onChange={e => setCategory({ ...Category, title: e.target.value })}
/>
<Input
value={Category.description}
onChange={e =>
setCategory({ ...Category, description: e.target.value })
}
/>
<Button onClick={() => setCategories([...Categories, Category])}>
Save
</Button>
<div>
{Categories.length > 0
? Categories.map(cat => <CategoryItem cat={cat} />)
: null}
</div>
</div>
);
};
const CategoryItem = ({ cat }) => {
const [value, setvalue] = React.useState("");
const [tag, addtag] = React.useState([]);
const [clicked, setclicked] = React.useState(false);
const add = () => {
setclicked(false);
addtag([...tag, value]);
};
return (
<Fragment>
<Header>
<p>
{cat.title}
<br />
{cat.description}
</p>
</Header>
<Input
name="tag"
value={value}
style={{ display: clicked ? "initial" : "none" }}
onChange={e => setvalue(e.target.value)}
/>
<Button onClick={() => (clicked ? add() : setclicked(true))}>Add</Button>
<div>{tag.length > 0 ? tag.map(tagname => <p>{tagname}</p>) : null}</div>
</Fragment>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
and here a sandbox
I'm trying to add and remove product when clicking a button, and each button is in different component and the data that I'm getting from is in storeData component where inside there is an object with a true/false status if the status is true the product should display in Cart component if false it will remove the product.
now in ProductList component when I click the add button the status is changing to true, but it's not changing the actual status in storeData component so the result when i go to Cart component nothing is displayed
I know I'm doing this the wrong way, so how can I perform this add and remove operation, i'm new in React.js so please any help would really be appreciated.
ProductList component
import itemlist from "../storeData/storeData";
import { Link } from "react-router-dom";
class ProductList extends Component {
state = {
items: itemlist.items,
addToCart: null
};
addItem(id) {
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
const addToCart = itemArray[0];
addToCart.status = false;
this.setState({ addToCart });
}
render() {
return (
<div className="list-wrap">
{this.state.items.map(item => {
return (
<div key={item.id}>
<Link to={{ pathname: "/productdetail", itemdetail: item }}>
<img alt="item img" src={item.posterUrl} />
</Link>
<h2>{item.title}</h2>
<h3>${item.price}</h3>
<button onClick={() => this.addItem(item.id)}>Add to Cart</button>
</div>
);
})}
</div>
);
}
}
export default ProductList;
Cart component
import itemlist from "../storeData/storeData";
class Cart extends Component {
state = {
cart: itemlist.cartItems,
remove: null
};
removeItem(id) {
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
let remove = itemArray[0];
remove.status = false;
this.setState({ remove });
}
render() {
return (
<div>
{this.state.cart.map(itm => {
return itm.status === false ? null : (
<div key={itm.id} className="cart-layout">
<img alt="img" src={itm.posterUrl} />
<h4>{itm.title}</h4>
<h4>{itm.price}</h4>
<button onClick={() => this.removeItem(itm.id)}>Remove</button>
</div>
);
})}
</div>
);
}
}
storeData component
let itemlist = {
items: [
{
id: 1,
title: "name 1",
price: "232",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE#._V1_SX300.jpg"
},
{
id: 2,
title: "name 2",
price: "65",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY5NTc2NjYwOV5BMl5BanBnXkFtZTcwMzk5OTY0MQ##._V1_SX300.jpg"
},
],
cartItems: [
{
id: 1,
status: false,
title: "name 1",
price: "232",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjIxNTU4MzY4MF5BMl5BanBnXkFtZTgwMzM4ODI3MjE#._V1_SX300.jpg"
},
{
id: 2,
status: false,
title: "name 2",
price: "65",
posterUrl:
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY5NTc2NjYwOV5BMl5BanBnXkFtZTcwMzk5OTY0MQ##._V1_SX300.jpg"
},
]
};
I don't think you are using filter correctly here, in either component. You are confusing the filter test with the action of composing your array. All you need with the filter is a test that will return a boolean and that will construct the array for you.
Try changing:
let itemArray = [];
itemlist.cartItems.filter(target => {
return id === target.id ? itemArray.push(target) : null;
});
To
const itemArray = itemlist.cartItems.filter(target => id === target.id);
And similarly in the cart component.
For more detail on filter see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I want to filter list by alphabet and search input. On click of each alphabet it should sort the list according to the alphabet.
Attached is the image to explain better
For example : If you click on "A" then it will display list starting with "A" Like ( Apple, Air plane, Adidas ) etc. And the case should be same when you input in search input box.
Below is the render function in my component which is fetching the list by JSON API
component.js
{this.props.items.list.map((item) => (
<li key={item.id} className="celeb-item">
<div className="celeb-item-info">
<img src={item.image_url} className="img-responsive" />
<strong>{item.name}</strong>
</div>
</li>
))}
import React, { Component } from 'react';
class App extends Component {
state = { searchInput: '', alphabet: ''};
onSearchInputChange = (e) => {
this.setState({searchInput: e.target.value})
}
onAlphabetClick = (e) => {
this.setState({alphabet: e.target.value})
}
prepareAlphabets = () => {
let result = [];
for(let i=65; i<91; i++) {
result.push(
<button type="button" key={i} onClick={this.onAlphabetClick} value={String.fromCharCode(i)} >{String.fromCharCode(i)}</button>
)
}
return result;
}
elementContainsSearchString = (searchInput, element) => (searchInput ? element.name.toLowerCase().includes(searchInput.toLowerCase()) : false);
filterItems = (itemList) => {
let result = [];
const { searchInput,alphabet } = this.state;
if(itemList && (searchInput || alphabet)) {
result = itemList.filter((element) => (element.name.charAt(0).toLowerCase() === alphabet.toLowerCase()) ||
this.elementContainsSearchString(searchInput, element));
} else {
result = itemList || [];
}
result = result.map((item)=> (<li>{item.name}</li>))
return result;
}
render() {
const itemList = [{id: 1, name:'abcd'},{id: 2, name:'gfhj'}, {id: 3, name:'fh'}, {id: 4, name:'zxbv'}, {id: 5, name:'ewyur'}, {id: 6, name:'gsdjhbndf'}, {id: 7, name:'gbhfvd'}, {id: 8, name:'wgtaqe'}, {id: 1, name:'ab'}, {id: 1, name:'bcd'}, {id: 1, name:'cde'}];
// const itemList = undefined;
const filteredList = this.filterItems(itemList);
return (
<div>
<input type="search" onChange={this.onSearchInputChange} />
{this.prepareAlphabets()}
<ul>
{filteredList}
</ul>
</div>
);
}
}
export default App;
The most straightforward way to achieve what you describe is to chain an Array.filter just before your Array.map.
You can use the onChange & onClick handles on your search field and alphabet buttons, respectively, to modify a parameter kept in your components state. This parameter could then be passed into your filter, looking something like this:
{this.props.items.list
.filter(item => item.name.startsWith(this.state.searchValue)
.map(item => (
<li key={item.id} className="celeb-item">
<div className="celeb-item-info">
<a href="#">
<img src={item.image_url} className="img-responsive" />
</a>
<strong>{item.name}</strong>
</div>
</li>
))
}