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
Related
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.
I want to add to Chip an startIcon={<Icon />}
when click on a Chip.
The state of the icon is managed by chipsState.
In this code,
the state of all chips would change.
How can I change only the chipsState of the element that is clicked?
In this code, the state of all chips will change.
How can I change only the chipsState of the element that is clicked?
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
const [chipsState, setChipsState] = useState(false);
const onChipClick = (element:any) => {
setChipsState(chipsState => !chipsState);
}
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<Chip onClick={() => onChipClick(element)} startIcon={chipsState && <Icon />}>{element.description}</Chip>
</div>
))}
</div>
);
}
export default Modal;
To handle local state (and better testing), you should create a new custom Chip component with dedicated chipState.
interface CustomChipProps {
description: string
}
const CustomChip = (props: CustomChipProps) => {
const [chipState, setChipState] = useState(false);
return <Chip onClick={() => setChipState(prev => !prev)} startIcon={chipState && <Icon />}>{props.description}</Chip>;
}
const Modal:React.FC<Props>= (props) => {
const {modalData} = props;
return (
<div>
{
modalData.symtoms.map((element:any, index:number) => (
<div key={index}>
<CustomChip description={element.description} />
</div>
))}
</div>
);
}
export default Modal;
You can achieve your desired output by changing chipState state from boolean to object.
So first let's change to object state instead of boolean
const [chipsState, setChipsState] = useState({});
Now we will change onChipClick function to change value of selected chip state
const onChipClick = (element:any) => {
setChipsState({...chipsState, chipsState[element]: !chipsState[element]});
}
And finally we will read correct value of each chipsState element.
<Chip onClick={() => onChipClick(element)} startIcon={chipsState[element] && <Icon />}>{element.description}</Chip>
You can try like the following
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
const ChipSet = ({ symtomsData }) => {
const data = symtomsData.map((symtom) => ({ ...symtom, isSelcted: false }));
const [chipSets, setChipSets] = useState(data);
const onSelectChipSet = useCallback(
(e, index) => {
const updatedChipSets = chipSets.map((chip, i) =>
i === index ? { ...chip, isSelcted: e.target.checked } : chip
);
setChipSets(updatedChipSets);
},
[chipSets]
);
console.log("chipSets", chipSets);
return (
<div>
<h1>Symtoms Data</h1>
{chipSets.map((x, i) => (
<div key={i}>
<label>
<input
onChange={(e) => onSelectChipSet(e, i)}
type="checkbox"
value={x.isSelcted}
/>
{x.description}
</label>
</div>
))}
</div>
);
};
class App extends React.Component {
render() {
const symtomsData = [
{
description: "mild"
},
{
description: "cold"
}
];
return (
<Grid>
<Row>
<ChipSet symtomsData={symtomsData} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
For example. i have Feeds and Upload Components. also i have ImageFeedList component in Feeds, Upload Components
(Feed.js)
import React, {useContext, useState, useEffect} from 'react';
import {StackNavigationProp} from '#react-navigation/stack';
import {RandomUserDataContext} from '~/Context/RandomUserData';
import ImageFeedList from '~/Components/ImageFeedList';
type NavigationProp = StackNavigationProp<FeedsTabParamList, 'Feeds'>;
interface Props {
navigation: NavigationProp;
}
const Feeds = ({navigation}: Props) => {
const {getMyFeed} = useContext(RandomUserDataContext);
const [feedList, setFeedList] = useState<Array<IFeed>>([]);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
setFeedList(getMyFeed(24));
}, []);
return (
<ImageFeedList
feedList={feedList}
loading={loading}
onRefresh={() => {
setLoading(true);
setTimeout(() => {
setFeedList(getMyFeed(24));
setLoading(false);
}, 2000);
}}
onEndReached={() => {
setFeedList([...feedList, ...getMyFeed(24)]);
}}
onPress={() => {
navigation.navigate('FeedListOnly');
}}
/>
);
};
export default Feeds;
(Upload.js)
import React, {useContext, useState, useEffect} from 'react';
import {RandomUserDataContext} from '~/Context/RandomUserData';
import ImageFeedList from '~/Components/ImageFeedList';
const Upload = () => {
const {getMyFeed} = useContext(RandomUserDataContext);
const [feedList, setFeedList] = useState<Array<IFeed>>([]);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
setFeedList(getMyFeed(24));
}, []);
return (
<ImageFeedList
feedList={feedList}
loading={loading}
onRefresh={() => {
setLoading(true);
setTimeout(() => {
setFeedList(getMyFeed(24));
setLoading(false);
}, 2000);
}}
onEndReached={() => {
setFeedList([...feedList, ...getMyFeed(24)]);
}}
/>
);
};
export default Upload;
(ImageFeedList.js)
import React from 'react';
import {
FlatList,
Image,
Dimensions,
NativeSyntheticEvent,
NativeScrollEvent,
} from 'react-native';
import styled from 'styled-components/native';
interface Props {
id?: number;
bounces?: boolean;
scrollEnabled?: boolean;
feedList: Array<IFeed>;
loading?: boolean;
onRefresh?: () => void;
onEndReached?: () => void;
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
onPress?: () => void;
}
const ImageFeedList = ({
id,
bounces = true,
scrollEnabled = true,
feedList,
loading,
onRefresh,
onEndReached,
onScroll,
onPress,
}: Props) => {
const width = Dimensions.get('window').width;
const imageWidth = width / 3;
return (
<FlatList
data={feedList}
style={{width}}
keyExtractor={(item, index) => {
return `image-feed-${id}-${index}`;
}}
showsVerticalScrollIndicator={false}
scrollEnabled={scrollEnabled}
bounces={bounces}
numColumns={3}
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={0.5}
refreshing={loading}
onScroll={onScroll}
scrollEventThrottle={400}
renderItem={({item, index}) => (
<ImageContainer
style={{
paddingLeft: index % 3 === 0 ? 0 : 1,
paddingRight: index % 3 === 2 ? 0 : 1,
}}
onPress={onPress}>
<Image
source={{uri: item.images[0]}}
style={{width: imageWidth, height: imageWidth}}
/>
</ImageContainer>
)}
/>
);
};
export default ImageFeedList;
what i want to ask is that in Feeds Component i have OnPress and i can pass onPress to ImageFeedList component as Props but there is no onPress props in Upload Component. however error is not gonna happen eventhough there is no onPress in Upload because i have a
interface Props {
onPress?: () => void;
}
this code i define onPress Props in In ImageFeedList components it meanse if i don't get Props onPress then it's fine
i can use this default props in typeScript
but my question is that how can i use default props in react and react-native other than typeScript??
is there way??
You are already using default props in ImageFeedList.
const ImageFeedList = ({
id,
bounces = true, /* <<< has a default value "true"*/
scrollEnabled = true, /* <<< has a default value "true"*/
...
onPress /* <<< has no defaut value*/,
}: Props) => {...}
you could add something like that as a default for onPress
const ImageFeedList = ({
...
onPress = () => console.log(), /* a default prop as an arrow function */
}: Props) => {...
Here is another example of how to pass a function as a default parameter
function y(prop = function () {console.log("I log default")}){
prop();
};
y(); // will log "I log default"
y(function () {console.log("I am not a default");}); // will log "I am not a default"
Update:
You could also check if onPress is defined, before using it.
<ImageContainer
...
onPress={() => typeof onPress === "function" && onPress()}>
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);
}
}
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>