unable to only select a single material ui checkbox - javascript

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.

Related

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

How to set retrived checkbox value checked and others unchecked?

I have some posts in my database I'm trying to retrieve and edit posts. The posts had some categories which I set as a checkbox. Well, I've retrieved a single post by id successfully but the problem is I also retrieved the categories and I want to show them as checked not all of them only those ones which are set for that particular post. I have another problem I cannot check the box anymore and am not able to add another category to the category list. Help me!
Here is the Edit Post page
import React, { useEffect, useState } from 'react';
import { Alert, Button, Card, Container, Form } from 'react-bootstrap';
import ReactMarkdown from 'react-markdown';
import { useDispatch, useSelector } from 'react-redux';
import { toast, ToastContainer } from 'react-toastify';
import { listCategory } from '../actions/categoryActions';
import { listPostDetails, updatePost } from '../actions/postActions';
const EditPost = ({ history, match }) => {
const postId = match.params.id;
const [categories, setCategories] = useState([]);
const dispatch = useDispatch();
const categoryList = useSelector((state) => state.categoryList);
const { categories: cateList } = categoryList;
const postDetails = useSelector((state) => state.postDetails);
const { post } = postDetails;
useEffect(() => {
if (!userInfo) {
history.push('/login');
}
dispatch(listCategory());
if (!post || post._id !== postId) {
dispatch(listPostDetails(postId));
} else {
setCategories(post.categories);
}
}, [dispatch, history, userInfo, post, postId, categories]);
const submitHandler = (e) => {
e.preventDefault();
dispatch(updatePost(title, desc, img, categories));
history.push('/my_posts');
};
return (
<div className=" createPost mt-4 py-4">
<ToastContainer />
<Container>
<h2>EDIT POST</h2>
<Form onSubmit={submitHandler}>
<Form.Group controlId="category" className="mb-2">
<Form.Label>Select Categories</Form.Label>
<br />
{cateList?.map((cate) => (
<Form.Check
inline
key={cate._id}
type="checkbox"
label={cate.name}
onChange={(e) => {
if (e.target.checked) {
setCategories([...categories, cate.name]);
} else {
setCategories(
categories?.filter((cat) => cat !== cate.name)
);
}
}}
/>
))}
</Form.Group>
<Button
type="submit"
variant="success"
style={{ letterSpacing: '2px', fontWeight: 'bold' }}>
CREATE
</Button>
</Form>
</Container>
</div>
);
};
export default EditPost;

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

How to delete chips in Material UI

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);
}
}

How to check Material UI checkbox Manually?

I am trying to check the checkbox in componentDidUpdate by setting the "checked" to true but it's not working.
My State is stored in redux, I update the checkbox state in "onChange()" and in componentDidUpdate, I check if the state value is true or false based on which I am adding and removing the "checked" attribute. but this doesn't seem to work
import React, { Component } from "react";
import { saveOptions, getOptions } from '../api'
import { connect } from 'react-redux';
import { setUploadeSettings } from '../actions/settings'
import { Button, Checkbox, FormControl, TextField, withStyles } from '#material-ui/core/';
const styles = theme => ({
root: {
padding: theme.spacing(2)
},
button: {
marginTop: theme.spacing(1)
}
})
class UploadSettings extends Component{
async componentDidMount(){
const settingsFields = [...this.form.elements].map(i => i.name).filter(i => i)
let savedOptions = await getOptions( { option_names: settingsFields } )
savedOptions = savedOptions.data.data;
const reduxState = {}
savedOptions.forEach(i => reduxState[i.option_name] = i.option_value );
this.props.setUploadeSettings(reduxState)
}
componentDidUpdate(prevProps){
if(prevProps.uploadSettings !== this.props.uploadSettings){
[...this.form.elements].forEach(i => {
if(i.name === "convert_web_res")
this.props.convert_web_res ? i.setAttribute("checked", true) : i.removeAttribute('checked') //<- This Doesn't Seem to work
i.value = this.props.uploadSettings[i.name]
})
}
}
handleSubmit = async (e) => {
e.preventDefault();
try{
const formData = new FormData(e.target);
const elements = [...e.target.elements]
const eleIndex = elements.findIndex(i => i.name === 'convert_web_res')
const checked = elements[eleIndex].checked;
formData.set('convert_web_res', checked);
await saveOptions(formData)
} catch(e){
console.error(e)
}
}
render(){
const { classes, uploadSettings, setUploadeSettings } = this.props;
return(
<div className={classes.root}>
<form onSubmit={this.handleSubmit} ref={(form) => this.form = form}>
<FormControl>
<label>
<Checkbox
color="primary"
name="convert_web_res"
onChange={(e) => {
const state = uploadSettings;
state.convert_web_res = e.target.checked
setUploadeSettings(state)
}}
/> Convert Web Resolution
</label>
<TextField
defaultValue = {uploadSettings.resolution}
id="resolution"
name="resolution"
label="Resolution"
margin="normal"
/>
<TextField
defaultValue = {uploadSettings.dpi}
id="dpi"
name="dpi"
label="DPI"
margin="normal"
/>
<Button type="submit" variant="contained" color="primary" className={classes.button}>
Save
</Button>
</FormControl>
</form>
</div>
)
}
}
const mapStateToProps = state => {
return {
uploadSettings: state.settings.upload_settings
}
}
const mapDispatchToProps = dispatch => {
return {
setUploadeSettings: settings => dispatch(setUploadeSettings(settings))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(UploadSettings));
The Goal is to set the default value of the checkbox.
I tried adding the "checked" attribute directly on my checkbox component, Like so
<label>
<Checkbox
color="primary"
name="convert_web_res"
checked={uploadSettings.convert_web_res === "true" ? true : false}
onChange={(e) => {
const state = uploadSettings;
state.convert_web_res = e.target.checked
setUploadeSettings(state)
}}
/> Convert Web Resolution
</label>
this checks the checkbox but it gets frozen, User loses the ability to toggle the checkbox,
Use value and checked.
handleChange = event => {
// fire redux action
this.props.setUploadeSettings({ convert_web_res: event.target.checked });
};
<label>
<Checkbox
color="primary"
name="convert_web_res"
value={'Convert Web Resolution'}
checked={this.props.uploadSettings.convert_web_res}
onChange={(e) => this.handleChange(e)}
/> Convert Web Resolution
</label>

Categories