Delete an element by key from Array - reactjs

I used library react-sortable-hoc for drag and drop element, but the library documentation does not have any actions for delete items. I want to delete, drag and drop item when click on close button. Which method is right for removing elements by key from object?
React
const SortableItem = SortableElement(({ value }: { value: string }, onRemove: any) =>
<div className="dragItems" style={{ background: 'gray' }}>
<img src={value} alt="" />
<button className="dragCloseBtn" onClick={() => onRemove(any)} />
</div>
);
const SortableList = SortableContainer(({ items }: { items: string[] }) => {
return (
<div className="dragAndDrop">
{items.map((value, index) => (
<SortableItem key={'item-${index}'} index={index} value={value} />
))}
</div>
);
});
constructor(props: any) {
super(props);
this.state = {
items: [
{
"id": 0,
"link": "https://via.placeholder.com/150"
},
{
"id": 1,
"link": "https://via.placeholder.com/150"
}
],
};
}
public onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
public onRemove(e: { target: { value: any; }; }) {
const array = [...this.state.items];
const index = array.indexOf(e.target.value)
if (index !== -1) {
array.splice(index, 1);
this.setState({items: array});
}
}
<SortableList items={this.state.items}
onSortEnd={this.onSortEnd}
lockAxis="xy"
axis="xy" />

UPDATED:
Hi there, I figured out what went wrong and made a successful remove event on your application.
Everything is illustrated with comments at this codesandbox.
=========
I modified this one, it should do the required using Array's filter method.
public onRemove(e: { target: { value: any; }; }) {
let array = [...this.state.items];
const intId = parseInt(e.target.value, 10);
array = array.filter(item => item.id !== intId);
this.setState({items: array});
}

So there were few problems in your code! You seemed to be confuse how react works with passing down props. You have to pass down the method required for remove. And you should bind it inside the class that you will be calling it.
onRemove should be bound to current context
onRemove should be passed down across the component tree
Check my //[NOTE]====> comments for additional explanation
Working code sandbox is here
import * as React from "react";
import * as ReactDOM from "react-dom";
import {
arrayMove,
SortableContainer,
SortableElement
} from "react-sortable-hoc";
//[NOTE]====>value contains the relevent object containing the callback. Onclick call it with the relevant id
const SortableItem = SortableElement(
({ value }: { value: any }, onRemove: any) => (
<div className="dragItems" style={{ background: "gray", margin: "20px" }}>
<img src={value.link} alt="" />
<button className="dragCloseBtn" onClick={() => value.onRemove(value.id)}>
{" "}
X{" "}
</button>
</div>
)
);
const SortableList = SortableContainer(({ items }: { items: string[] }) => {
return (
<div className="dragAndDrop">
{items.map((value, index) => (
<SortableItem key={`item-${index}`} index={index} value={value} />
))}
</div>
);
});
class SortableComponent extends React.Component<{}, { items: string[] }> {
constructor(props: {}) {
super(props);
//[NOTE]====>Send the callback on each element bound to the current context
//This is like telling the function from where exactly the function will be called
this.state = {
items: [
{
id: 0,
link: "https://via.placeholder.com/150",
onRemove: this.onRemove.bind(this)
},
{
id: 1,
link: "https://via.placeholder.com/150",
onRemove: this.onRemove.bind(this)
}
]
};
}
public render() {
return <SortableList items={this.state.items} onSortEnd={this.onSortEnd} />;
}
public onSortEnd = ({
oldIndex,
newIndex
}: {
oldIndex: number;
newIndex: number;
}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex)
});
};
//[NOTE]====>Use the id to filter out and set the new set of items
public onRemove(id) {
console.log(id);
let array = [...this.state.items];
const intId = parseInt(id, 10);
array = array.filter((item: any) => item.id !== intId);
this.setState({ items: array });
}
}
ReactDOM.render(<SortableComponent />, document.getElementById("root"));

Related

change text of a specific button when clicked in React

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;

Edit list item in place (React)

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;

How to render only 5 items in react autosuggest?

I'am using react autosuggest npm package to get the json data and display it. I want to display only 5 items. How to do it?
Form.js
import React from 'react'
import Autosuggest from 'react-autosuggest';
import cities from 'cities.json';
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
);
};
const getSuggestionValue = suggestion => suggestion.name;
const renderSuggestion = suggestion => (
<div>
{console.log('suggestion', suggestion)}
{suggestion.name}
</div>
);
class Form extends React.Component {
constructor() {
super();
this.state = {
value: '',
suggestions: []
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Search City...',
value,
onChange: this.onChange
};
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<br/>
</div>
)
}
}
export default Form;
I want to render only 5 items, otherwise, computer hangs while loading huge data. Is there any other autocomplete react npm package, since I want only cities and country list. i.e when city is inputted, automatically the city name must be suggested with its relevant country.Any solution or suggestion highly appreciated. Thanks in advance
i modified you're getSuggestions() method a little i guess this should work for you.
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
).slice(0,5);
};
Use the Slice method with start index and last Index
suggestions={suggestions.slice(0, 5)}
import {
React
,Avatar
,axiosbase
} from '../../import-files';
import Autosuggest from 'react-autosuggest';
import './autosuggest.css';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Paper from '#material-ui/core/Paper';
import MenuItem from '#material-ui/core/MenuItem';
let suggestions = [ { label: 'Afghanistan' } ];
function renderInputComponent(inputProps) {
const { classes, inputRef = () => {}, ref, ...other } = inputProps;
return (
<TextField
className={classes.textField}
fullWidth
variant="outlined"
InputProps={{
inputRef: node => {
ref(node);
inputRef(node);
},
classes: {
input: classes.input,
},
}}
{...other}
/>
);
}
function renderSuggestion(suggestion, { query, isHighlighted }) {
return (
<MenuItem selected={isHighlighted} component="div">
<div>
<strong key={String(suggestion.id)} style={{ fontWeight: 300 }}>
<span className="sugg-option">
<span className="icon-wrap">
<Avatar src={suggestion.Poster}></Avatar>
</span>
<span className="name">
{suggestion.Title}
</span>
</span>
</strong>
</div>
</MenuItem>
);
}
function initSuggestions(value) {
suggestions = value;
}
function getSuggestionValue(suggestion) {
return suggestion.Title;
}
function onSuggestionSelected(event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) {
console.log('HandleSuggestion() '+suggestionValue);
}
const styles = theme => ({
root: {
height: 50,
flexGrow: 1,
},
container: {
position: 'relative',
},
suggestionsContainerOpen: {
position: 'absolute',
zIndex: 998,
marginTop: theme.spacing.unit,
left: 0,
right: 0,
overflowY: 'scroll',
maxHeight:'376%'
},
suggestion: {
display: 'block',
},
suggestionsList: {
margin: 0,
padding: 0,
listStyleType: 'none',
},
divider: {
height: theme.spacing.unit * 2,
},
});
class IntegrationAutosuggest extends React.Component {
state = {
single: '',
popper: '',
suggestions: [],
};
componentDidMount() {
initSuggestions(suggestions);
}
// Filter logic
getSuggestions = async (value) => {
const inputValue = value.trim().toLowerCase();
var _filter = JSON.stringify({
filter : inputValue,
});
return await axiosbase.post(`${apiCall}`, _filter);
};
handleSuggestionsFetchRequested = ({ value }) => {
this.getSuggestions(value)
.then(data => {
if (data.Error) {
this.setState({
suggestions: []
});
} else {
const responseData = [];
data.data.itemsList.map((item, i) => {
let File = {
id: item.idEnc,
Title: item.englishFullName +' '+item.arabicFullName,
englishFullName: item.englishFullName,
arabicFullName: item.arabicFullName,
Poster: item.photoPath,
}
responseData.push(File);
});
this.setState({
suggestions: responseData
});
}
})
};
handleSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
});
};
handleChange = name => (event, { newValue }) => {
this.setState({
[name]: newValue,
});
if(event.type=='click'){
if(typeof this.props.handleOrderUserFirstNameChange === "function"){
this.props.handleOrderUserFirstNameChange(newValue);
}
this.state.suggestions.filter(f=>f.Title===newValue).map((item, i) => {
//id
//Title
// Poster
if(typeof this.props.handleUserIDChange === "function"){
this.props.handleUserIDChange(item.id);
}
});
}
};
render() {
const { classes } = this.props;
// console.log('Re-render!!');
// console.log(this.props);
// console.log(this.state.suggestions);
const autosuggestProps = {
renderInputComponent,
suggestions: this.state.suggestions,
onSuggestionsFetchRequested: this.handleSuggestionsFetchRequested,
onSuggestionsClearRequested: this.handleSuggestionsClearRequested,
onSuggestionSelected: this.props.onSelect,
getSuggestionValue,
renderSuggestion,
};
return (
<div className={classes.root}>
<Autosuggest
{...autosuggestProps}
inputProps={{
classes,
placeholder: this.props.placeHolder,
value: this.state.single,
onChange: this.handleChange('single'),
}}
theme={{
container: classes.container,
suggestionsContainerOpen: classes.suggestionsContainerOpen,
suggestionsList: classes.suggestionsList,
suggestion: classes.suggestion,
}}
renderSuggestionsContainer={options => (
<Paper {...options.containerProps} square>
{options.children}
</Paper>
)}
/>
<div className={classes.divider} />
</div>
);
}
}
export default withStyles(styles)(IntegrationAutosuggest);

How to add right click menu to react table row, and access its properties?

I've added react-table package to my project and everything is fine, but I also wanted to have a possibility to right click on a row and perform some actions on it (cancel, pause etc). I'm using React with Typescript but I hope it doesn't add any complexity.
My initial idea was to use react-contextify, however I can't find any working examples that would combine react-table and react-contextify together.
The only "working" example I have found is this one:
React Context Menu on react table using react-contexify
I ended up not using react-contextify and it "kind of works" but I'm not totally certain about this one as I sometimes keep getting exceptions like this:
Uncaught TypeError: Cannot read property 'original' of undefined
The code I have now is this:
const columns = [
{
Header: "Name",
accessor: "name"
},
{
Header: "Age",
accessor: "age",
Cell: (props: { value: React.ReactNode }) => (
<span className="number">{props.value}</span>
)
},
{
id: "friendName", // Required because our accessor is not a string
Header: "Friend Name",
accessor: (d: { friend: { name: any } }) => d.friend.name // Custom value accessors!
},
{
Header: (props: any) => <span>Friend Age</span>, // Custom header components!
accessor: "friend.age"
}
];
return (
<div>
<ContextMenuTrigger id="menu_id">
<ReactTable
data={data}
columns={columns}
showPagination={false}
getTdProps={(
state: any,
rowInfo: any,
column: any,
instance: any
) => {
return {
onClick: (e: any, handleOriginal: any) => {
const activeItem = rowInfo.original;
console.log(activeItem);
},
onContextMenu: () => {
console.log("contextMenu", rowInfo);
this.setState({
showContextMenu: true,
rowClickedData: rowInfo.original
});
}
};
}}
/>
</ContextMenuTrigger>
{this.state.showContextMenu ? (
<MyAwesomeMenu clickedData={this.state.rowClickedData} />
) : null}
</div>
);
}
}
const MyAwesomeMenu = (props: { clickedData: any }) => (
<ContextMenu id="menu_id">
<MenuItem
data={props.clickedData}
onClick={(e, props) => onClick({ e, props })}
>
<div className="green">ContextMenu Item 1 - {props.clickedData.id}</div>
</MenuItem>
</ContextMenu>
);
const onClick = (props: {
e:
| React.TouchEvent<HTMLDivElement>
| React.MouseEvent<HTMLDivElement, MouseEvent>;
props: Object;
}) => console.log("-------------->", props);
What is the best (and simplest) way to add a context menu to react-table so I can use clicked row's props? I really like react-contextify but haven't found any examples.
Thanks
React Hooks exmaple on dev.to
Class Based Compnent example on codepen
class App extends React.Component {
constructor() {
super();
this.state = {
value: ''
};
}
render() {
return(
<div>
{
['row1', 'row2', 'row3'].map((row) => {
return (
<ContextMenu
key={row}
buttons={[
{ label: 'Editovat', onClick: (e) => alert(`Editace ${row}`) },
{ label: 'Smazat', onClick: (e) => alert(`Mažu ${row}`) }
]}
>
<div className="row">{row}</div>
</ContextMenu>
);
})
}
</div>
);
}
}
class ContextMenu extends React.Component {
static defaultProps = {
buttons: []
};
constructor() {
super();
this.state = {
open: false
};
}
componentDidMount() {
document.addEventListener('click', this.handleClickOutside);
document.addEventListener('contextmenu', this.handleRightClickOutside);
}
handleClickOutside = (e) => {
if (!this.state.open) {
return;
}
const root = ReactDOM.findDOMNode(this.div);
const context = ReactDOM.findDOMNode(this.context);
const isInRow = (!root.contains(e.target) || root.contains(e.target));
const isInContext = !context.contains(e.target);
if (isInRow && isInContext) {
this.setState({
open: false
});
}
}
handleRightClickOutside = (e) => {
if (!this.state.open) {
return;
}
const root = ReactDOM.findDOMNode(this.div);
const isInRow = !root.contains(e.target);
if (isInRow) {
this.setState({
open: false
});
}
}
handleRightClick = (e) => {
e.preventDefault();
console.log(e.nativeEvent, window.scrollY);
this.setState({
open: true,
top: window.scrollY + e.nativeEvent.clientY,
left: e.nativeEvent.clientX,
});
}
render() {
return (
<div
onContextMenu={this.handleRightClick}
ref={(node) => this.div = node}
>
{this.props.children}
{
!this.state.open
? null
: <div
className="context"
ref={(div) => this.context = div}
style={{ top: this.state.top, left: this.state.left }}
>
<ul>
{
// button - name, onClick, label
this.props.buttons.length > 0 &&
this.props.buttons.map((button) => {
return <li key={button.label}>
<a href="#" onClick={button.onClick}>
{button.label}
</a>
</li>
})
}
</ul>
</div>
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));

How to update state in map function in reactjs

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

Resources