How to delete chips in Material UI - javascript

So I have a bunch of chips that were rendered as a user types in a text field and selects on create. Then a chip shows up with the text. I want to have the functionality to delete the chip. This is the sandbox https://codesandbox.io/s/material-demo-xi9hr?file=/demo.js. I want to specifically know what my handleDelete function will do
import React from "react";
import Avatar from "#material-ui/core/Avatar";
import Chip from "#material-ui/core/Chip";
import TextField from "#material-ui/core/TextField";
import { useState } from "react";
import Button from "#material-ui/core/Button";
export default function OutlinedChips() {
const [hashtag, setHashtag] = useState("");
const [numberOfHashtags, setNumberOfHashtags] = useState(0);
const [arrayOfHashtags, addHashtag] = useState([]);
const handleDelete = () => {
console.log(hashtag);
};
const handleHashtagChange = event => setHashtag(event.target.value);
const handleClick = () => {
console.info("You clicked the Chip.");
};
const newHashtag = () => {
if (numberOfHashtags < 3) {
setNumberOfHashtags(numberOfHashtags + 1);
addHashtag(arrayOfHashtags => arrayOfHashtags.concat(hashtag));
} else {
console.log("Too much hashtags");
}
};
const Hashtags = arrayOfHashtags.map(h => (
<Chip
size="small"
avatar={<Avatar>#</Avatar>}
label={h}
onDelete={handleDelete}
/>
));
console.log(arrayOfHashtags);
return (
<div>
<TextField
size="small"
inputProps={{
style: { fontSize: 15 }
}}
id="outlined-multiline-static"
multiline
rows={1}
placeholder="Description"
variant="outlined"
value={hashtag}
onChange={handleHashtagChange}
/>
<Button color="primary" onClick={newHashtag}>
Create!
</Button>
{numberOfHashtags > 0 ? Hashtags : ""}
</div>
);
}

const handleDelete = (h) => () => {
addHashtag((arrayOfHashtags) =>
arrayOfHashtags.filter((hashtag) => hashtag !== h)
)
}
https://codesandbox.io/s/material-demo-8yh8t?file=/demo.js:448-589

The first think you should do is bind a key for each Chip item, so onDelete handler you can decide which chip you should delete in your array. Finally, filter your array to remove that item with match key.
I think you should read Material Docs carefully, It's already have a example about delete a chip, here is the link: https://material-ui.com/components/chips/
And here is my solution: Codesanbox

Try this.. Pass the chips which you want to delete and this.selected_search_tags is the variable in which all chips are stored..
remove(fruit: string): void {
const index = this.selected_search_tags.indexOf(fruit);
if (index >= 0) {
this.selected_search_tags.splice(index, 1);
}
}

Related

How can I add favorite list on local storage React.js

I want to implement add-to favorite list functionality. But It only works on a single item I need multiple items. I use useRef Hook and use this npm package react-use-localstorage
The problem is my local storage doesn't work properly as I expected. It always updates a single item but I need it as an Array i.e [1, 2, 4, 7, 10]
If I reload my page only 3 number id will fill the heart 🤨
import React, { useRef } from "react";
import FavoriteBorder from "#mui/icons-material/FavoriteBorder";
import Favorite from "#mui/icons-material/Favorite";
import IconButton from "#mui/material/IconButton";
import useLocalStorage from 'react-use-localstorage';
const Fv = ({ id }) => {
const [storageItem, setStorageItem] = useLocalStorage(
"favourites",
JSON.stringify([])
);
//const storagedArray = useRef(JSON.parse(storageItem));
//const isFavourited = storagedArray.current.includes(id);
const isFavourited = storageItem.includes(id)
const handleToggleFavourite = () => {
if (!isFavourited) {
setStorageItem(JSON.stringify([...JSON.parse(storageItem), id]));
} else {
setStorageItem(
JSON.stringify(
JSON.parse(storageItem).filter((savedId) => savedId !== id)
)
);
}
return (
<IconButton onClick={handleToggleFavourite}>
{isFavourited ? <Favorite color="error" /> : <FavoriteBorder color="error" />}
</IconButton>
);
};
export default Fv;
Assign Component
<Fv id={product.id} />
Okay, to be perfectly honest, I don't know why your array behaves like that. Maybe it is this external library malfunctioning. You don't need to use any external libraries to manage your favourites state. Here is a solution without react-use-localstorage:
import React, { useState } from "react";
import FavoriteBorder from "#mui/icons-material/FavoriteBorder";
import Favorite from "#mui/icons-material/Favorite";
import IconButton from "#mui/material/IconButton";
const Fv = ({ id }) => {
const [storageItem, setStorageItem] = useState(() => JSON.parse(localStorage.getItem("favourites") || "[]"))
const isFavourited = storageItem.includes(id)
const handleToggleFavourite = () => {
if (!isFavourited) {
const newStorageItem = [...storageItem, id]
setStorageItem(newStorageItem);
localStorage.setItem("favourites", JSON.stringify(newStorageItem))
} else {
const newStorageItem = storageItem.filter((savedId) => savedId !== id)
setStorageItem(newStorageItem);
localStorage.setItem("favourites", JSON.stringify(newStorageItem))
}
return (
<IconButton onClick={handleToggleFavourite}>
{isFavourited ? <Favorite color="error" /> : <FavoriteBorder color="error" />}
</IconButton>
);
};
export default Fv;

How to disable a react component (or onClick itself) with onClick event

I have seen similar questions but none of the answers are working in my case and I hope someone will be able to tell me why.
My ReactApp renders 3 card components that flip when clicked on. The cards are populated with data from an object array and is rendered with a map function (added info in case it has an impact). Here is parent component.
import React from 'react'
import FlipCard from './FlipCard'
const cards = [
{
id: 1,
text: 'NOPE',
},
{
id: 2,
text: '!!WINNER!!',
},
{
id: 3,
text: 'NOPE',
},
]
const shuffleCards = array => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
const temp = array[i]
array[i] = array[j]
array[j] = temp
}
return array
}
shuffleCards(cards)
console.log(cards)
const CardGameUI = () => {
shuffleCards(cards)
return (
<div className="cards-ui">
{cards.map(card => (
<FlipCard key={card.id} text={card.text} value={card.id} />
))}
</div>
)
}
export default CardGameUI
When one of the cards are flipped, I need the onClick for the other cards to be disabled. I tried using state and a conditional in my onClick event but it has no effect. The according to the console.log, the state of the play boolean is changed and if I manually change the conditional in the onCLick event check if play is true, then it works perfectly fine. I am clearly missing something because it seems as though the conditional is working and the state is changing.
import React, { useState } from 'react'
import ReactCardFlip from 'react-card-flip'
import FrontComponent from './FrontComponent'
import BackComponent from './BackComponent'
const FlipCard = ({ text, value }) => {
const [isFlipped, setIsFlipped] = useState(false)
const [activeCard, setActiveCard] = useState(2)
const [play, setPlay] = useState(false)
console.log(play.valueOf())
function handleFlip() {
setPlay(true)
setIsFlipped(!isFlipped)
console.log(isFlipped)
setActiveCard(value)
console.log(value)
}
if (activeCard !== 2) {
console.log('Play again?')
}
return (
<>
<ReactCardFlip isFlipped={isFlipped} flipDirection="horizontal">
<FrontComponent onClick={!play ? handleFlip : null} />
<BackComponent text={text} value={value} />
</ReactCardFlip>
</>
)
}
export default FlipCard
What am I missing?
You should manage the onClick event and the flip state on the parent instead of inside the card.
An high-level overview will be:
const CardGameUI = () => {
const [flipped, setFlipped] = useState({});
const hasFlipped = Object.values(flipped).includes(true);
const handleFlip = id => () => setFlipped(flipped => ({
...flipped,
[id]: true // can be changed to toggle in case you need it in the future
}));
return (
<div>
{cards.map(({ id, text }) => (
<FlipCard
key={id}
text={text}
value={id}
onFlip={handleFlip(id)}
flipped={flipped[id]}
disabled={hasFlipped}
/>
))}
</div>
)
};
const FlipCard = ({ text, value, onFlip, flipped , disabled}) => {
return (
<ReactCardFlip isFlipped={flipped} flipDirection="horizontal">
<FrontComponent onClick={onFlip} disabled={disabled} />
<BackComponent text={text} value={value} />
</ReactCardFlip>
)
}

Cannot select Options from Material UI's Autocomplete Component

I cannot select the options being displayed from MUI's autocomplete component. The reason I think is that I am using renderOption. I want to display the image along the title in the options of my component and without using renderOption, I have not been able to do that. But, doing so (using renderOption), I cannot select any option.
import React, { useState } from 'react';
import { fetchSearchAnimeEndpoint } from '../../redux';
import { connect } from 'react-redux';
import { debounce } from 'lodash';
import TextField from '#mui/material/TextField';
import Autocomplete from '#mui/material/Autocomplete';
import CircularProgress from '#mui/material/CircularProgress';
import './searchbar.css';
const SearchBar = (props: any) => {
const [openPopper, setOpenPopper] = useState(false);
const [inputValue, setInputValue] = useState('');
const { anime } = props;
const handleKeyPress = debounce(
(event: React.FormEvent<HTMLInputElement>) => {
const value = (event.target as HTMLInputElement).value;
props.fetchSearchAnime(value);
},
700
);
return (
<div>
{/*
Cannot select the options being displayed either by clicking or using keyboard
Want to display the title on the textfield by selecting desired option */}
<Autocomplete
id="combo-box-demo"
options={anime.results.data ? anime.results.data : []}
style={{ width: 300, marginTop: '2rem' }}
isOptionEqualToValue={(option, value) => option.title === value.title}
getOptionLabel={(option: any) => option.title}
renderOption={(option: any, optionAnime) => {
return (
<h4 key={optionAnime.mal_id} className="search-container">
<img
className="search-image"
alt="anime"
src={optionAnime.image_url}
/>
{optionAnime.title}
</h4>
);
}}
renderInput={(params) => (
<TextField
onInput={(e) => {
if ((e.target as HTMLInputElement).value.length >= 3) {
setOpenPopper(true);
} else {
setOpenPopper(false);
}
return handleKeyPress(e);
}}
{...params}
label="Anime"
/>
)}
open={openPopper}
onClose={() => {
setOpenPopper(false);
}}
/>
</div>
);
};
const mapStateToProps = (state: any) => {
return {
anime: state,
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
fetchSearchAnime: (name: string) =>
dispatch(fetchSearchAnimeEndpoint(name)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
I am getting the options and just cannot select them. What I want is to select them by clicking on them or using the keyboard and get the title value on the textfield
Hey so they changed the renderInput function as it now takes props and options
so for this example
<TextField
onInput={(e) => {
if ((e.target as HTMLInputElement).value.length >= 3) {
setOpenPopper(true);
} else {
setOpenPopper(false);
}
return handleKeyPress(e);
}}
{...props}
label="Anime"
/>
)}
open={openPopper}
onClose={() => {
setOpenPopper(false);
}}
/>
for more info on the problem, theres an example on github:
https://github.com/mui/material-ui/issues/29943

unable to only select a single material ui checkbox

I am new to react and I am making a simple todo app using react js and material ui. What I have is a separate component for taking the user input (TodoInput), and a separate component for rending each individual todo task (TodoCards). What I want to do is enable the user to click the checkbox rendered in the TodoCards component once they have completed the task. I ran into an issue where when a single checkbox is clicked, all the checkboxes for every card component is checked. I'm unsure to as why this is happening, any guidance or explanation towards the right direction would be greatly appreciated.
TodoInput.js
import React, { useState } from 'react';
import { makeStyles } from '#material-ui/core/styles';
import { TextField, Button } from '#material-ui/core';
import { TodoCards } from '../UI/TodoCards';
import { Progress } from '../UI/Progress';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
width: '25ch',
textAlign: 'center'
},
},
}));
export default function TodoInput() {
const classes = useStyles();
const [userInput, setUserInput] = useState({
id: '',
task: ''
});
const [todos, setTodos] = useState([])
//state for error
const [error, setError] = useState({
errorMessage: '',
error: false
})
//add the user todo with the button
const submitUserInput = (e) => {
e.preventDefault();
//add the user input to array
//task is undefined
if (userInput.task === "") {
//render visual warning for text input
setError({ errorMessage: 'Cannot be blank', error: true })
console.log('null')
} else {
setTodos([...todos, userInput])
console.log(todos)
setError({ errorMessage: '', error: false })
}
console.log(loadedTodos)
}
//set the todo card to the user input
const handleUserInput = function (e) {
//make a new todo object
setUserInput({
...userInput,
id: Math.random() * 100,
task: e.target.value
})
//setUserInput(e.target.value)
//console.log(userInput)
}
const loadedTodos = [];
for (const key in todos) {
loadedTodos.push({
id: Math.random() * 100,
taskName: todos[key].task
})
}
return (
<div>
<Progress taskCount={loadedTodos.length} />
<form className={classes.root} noValidate autoComplete="off" onSubmit={submitUserInput}>
{error.error ? <TextField id="outlined-error-helper-text" label="Today's task" variant="outlined" type="text" onChange={handleUserInput} error={error.error} helperText={error.errorMessage} />
: <TextField id="outlined-basic" label="Today's task" variant="outlined" type="text" onChange={handleUserInput} />}
<Button variant="contained" color="primary" type="submit">Submit</Button>
{userInput && <TodoCards taskValue={todos} />}
</form>
</div>
);
}
TodoCards.js
import React, { useState } from 'react'
import { Card, CardContent, Typography, FormControlLabel, Checkbox } from '#material-ui/core';
export const TodoCards = ({ taskValue }) => {
const [checked, setChecked] = useState(false);
//if checked, add the task value to the completed task array
const completedTasks = [];
const handleChecked = (e) => {
setChecked(e.target.checked)
//console.log('complete')
for (const key in taskValue) {
completedTasks.push({
id: Math.random() * 100,
taskName: taskValue[key].task
})
}
}
return (
< div >
<Card>
{taskValue.map((individual, i) => {
return (
<CardContent key={i}>
<Typography variant="body1">
<FormControlLabel
control={
<Checkbox
color="primary"
checked={checked}
onClick={handleChecked}
/>
}
label={individual.task} />
</Typography>
</CardContent>
)
})}
</Card>
</div >
)
}
This is because all of your checkboxes are connected to only one value (checked). There is two way you could potentially solve this.
Method one:
Instead of a single value, you create a list that consists of as many values as you have checkboxes. Eg:
const [checked, setChecked] = useState([true, false, false]) //this is your list
//...
{taskValue.map((individual, index) =>
<Checkbox
color="primary"
checked={checked[index]}
onClick={() => handleChecked(index)}
/>
}
In handleChecked you should only change that one value based on the index.
Method number two (what I would probably do:
You create a new component for the check boxes
checktask.js
import {useState} from "react";
function CheckTask(props){
const [checked, setChecked] = useState(false);
return (
<Checkbox
color="primary"
checked={checked[index]}
onClick={() => handleChecked(index)}
/>
)
}
export default CheckTask;
This way you could give each checkbox its own state.

Trying to render elements with map with text that's from an array

So I have an array state which keeps tracks of the text that the user has entered in the text field. What I want to render is the text embedded in a component so they can see what text they've previously entered. The problem I have is with my Hashtags component and when I try to render it (my attempt of it is in the ternary operator).
import React from "react";
import Avatar from "#material-ui/core/Avatar";
import Chip from "#material-ui/core/Chip";
import TextField from "#material-ui/core/TextField";
import { useState } from "react";
import Button from "#material-ui/core/Button";
export default function OutlinedChips() {
const [hashtag, setHashtag] = useState("");
const [numberOfHashtags, setNumberOfHashtags] = useState(0);
const [arrayOfHashtags, addHashtag] = useState([]);
const handleDelete = () => {
console.info("You clicked the delete icon.");
};
const handleHashtagChange = event => setHashtag(event.target.value);
const handleClick = () => {
console.info("You clicked the Chip.");
};
const newHashtag = () => {
if (numberOfHashtags < 3) {
setNumberOfHashtags(numberOfHashtags + 1);
addHashtag(arrayOfHashtags => arrayOfHashtags.concat(hashtag));
} else {
console.log("Too much hashtags");
}
};
const Hashtags = arrayOfHashtags.map(h => (
<Chip
size="small"
avatar={<Avatar>#</Avatar>}
label={h}
onDelete={handleDelete}
/>
));
console.log(arrayOfHashtags);
return (
<div>
<TextField
size="small"
inputProps={{
style: { fontSize: 15 }
}}
id="outlined-multiline-static"
multiline
rows={1}
placeholder="Description"
variant="outlined"
value={hashtag}
onChange={handleHashtagChange}
/>
<Button color="primary" onClick={newHashtag}>
Create!
</Button>
{numberOfHashtags > 0 ? <Hashtags /> : ""}
</div>
);
}
Here's the sandbox https://codesandbox.io/s/material-demo-xi9hr?file=/demo.js:0-1620
I think you should call Hashtags like this :
{numberOfHashtags > 0 ? Hashtags : ""}
your Hashtags return an array, not component, so you can't call it like a component

Categories