How to add a button to MUI TextField Select component selected option? - reactjs

I have a MUI v5 Textfield select component. Each option has a play button, avatar and option text. Everything works fine in dropdown list. But how can I make play button work (do something) if it is selected? At the moment if user clicks on it then option list opens.
const StyledSelect = ({ ...props }) => {
return (
<TextField {...props} select fullWidth>
{props.children}
</TextField>
);
};
export const AudioSelect = () => {
return (
<StyledSelect>
{AUDIO_OPTIONS.map(({ id, name, avatar, voice }) => (
<MenuItem key={id} value={id}>
<Stack>
<PlayAudioButton
sound={voice}
key={id}
onPlay={() => console.log("play button " + id)}
isPlaying={false}
/>
<Avatar src={avatar} />
<Typography>{name}</Typography>
</Stack>
</MenuItem>
))}
</StyledSelect>
);
};
Here's codesandbox:
https://codesandbox.io/s/silly-monad-6z3144?file=/src/AudioSelect.tsx

Add another propagation stopping in onMouseDown did the trick
return (
<IconButton
onMouseDown={(e) => {
e.stopPropagation();
}}
onClick={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
onPlay();
}}
>
{isPlaying && !isAudioEnded ? <PauseIcon /> : <PlayArrowRoundedIcon />}
</IconButton>
);

Related

How to add onClick button to any tabs in MUI?

I wanna add onClick in button (like swap languages in google translate) to any tabs. I want like click Swap languages in google translate to other languages like en -> es to es -> en.
const [value_s, setValue_s] = useState('one');
const [value_t, setValue_t] = useState('four');
const handleChange_s = (event, newValue) => {
setValue_s(newValue);
};
const handleChange_t = (event, newValue) => {
setValue_t(newValue);
};
<Tabs
value={value_s}
onChange={handleChange_s}
>
{languages.map((item) => (
<Tab key={item} label={item.Name} onClick={() => { setSource(item.langcode) }} />
))}
</Tabs>
{/* Swap languages Button*/}
<Tooltip title="Swap languages">
<IconButton
onClick={() => { setSource(target), setTarget(source) }}
>
<SwapHorizIcon />
</IconButton>
</Tooltip>
<Tabs
value={value_t}
onChange={handleChange_t}
textColor="secondary"
indicatorColor="secondary"
>
{languages.map((item) => (
<Tab key={item} label={item.Name} onClick={() => { setTarget(item.langcode) }} />
))}
</Tabs>
You can add a value prop to the Tab component. Which will allow you to toggle on the value, instead the index.
For this we need to change the initial states of the source and target.
const [value_s, setValue_s] = useState("en");
const [value_t, setValue_t] = useState("es");
Now to can add the value prop to the Tab component, same by the target Tab
<Tabs value={value_s} onChange={handleChange_s}>
{languages.map((item) => (
<Tab
key={item}
value={item.langcode} // the value
label={item.Name}
onClick={() => {
setSource(item.langcode);
}}
/>
))}
</Tabs>
Create a handleSwap function for the button
const handleSwap = () => {
setValue_s(value_t);
setValue_t(value_s);
};
Which we can use like this
<IconButton onClick={handleSwap}>

How to keep option list open when option's button clicked in MUI v5 Autocomplete?

How can I add a play button to every option in MUI v5 Autocomplete component so that when clicked on that button no option selected and option list was not closed?
Here's codesandbox:
https://codesandbox.io/s/silly-monad-6z3144?file=/src/Autocomplete.tsx
<Autocomplete
disableCloseOnSelect={disableCloseOnSelect}
options={options}
getOptionLabel={(option: any) => option[optionLabel]}
isOptionEqualToValue={(option: any, value: any) => option.id === value.id}
renderInput={(params) => {
return <TextField {...params} placeholder={placeholder} />;
}}
PaperComponent={AutocompletePaper}
renderOption={(renderProps, option: any, { selected, inputValue }) => {
return (
<span {...renderProps}>
<Stack>
<PlayArrowRoundedIcon onClick={() => console.log('clicked')} />
<StyledAvatar src={option.avatar} />
<span>
{option.name}
</span>
</Stack>
</span>
);
}}
/>
use stopPropagation for it:
<PlayArrowRoundedIcon
onClick={(e) => {
e.stopPropagation()
console.log("click on play button")
}}
/>

function remove of react-hook-form removing last element instead of correct one

i am trying to use useFieldsArray from react-hook-form, but when I click on the button to delete, instead of deleting the correct element it always deletes the last one. can someone more experienced give me some advice?
I don't understand what I'm doing wrong.
Thanks in advance.
export const isThisAFieldsArrayContext = React.createContext(false)
export default function FieldsArray({
control,
formNamePrefix,
register,
inputsToRender,
addNewDisabled = false,
watchConfig,
...props
}) {
const classes = useStyles()
const { append, remove } = useFieldArray({
control,
shouldUnregister: false,
name: formNamePrefix,
})
const additionalProps = useWatchForAdditionalProps(watchConfig)
function handleRemove(index) {
remove(index)
}
return (
<isThisAFieldsArrayContext.Provider value={true}>
<Grid item xs={12}>
<List>
{control.getValues(formNamePrefix)?.map((item, index) => {
console.log({ item })
return (
<ListItem key={index} className={classes.listItem}>
<DynamicFormFields
{...props}
register={register}
fieldsKey={`${formNamePrefix}.${index}`}
formFields={inputsToRender}
control={control}
// defaultValue={item.defaultValue}
/>
<DeleteButton
variant="contained"
onClick={() => handleRemove(index)}
aria-label="delete"
className={classes.deleteButton}
>
<DeleteIcon color="secondary" fontSize="small" />
</DeleteButton>
</ListItem>
)
})}
</List>
<Button
variant="contained"
onClick={() => append({})}
aria-label="add"
color="secondary"
className={classes.addButton}
disabled={addNewDisabled || additionalProps.disabled}
>
<AddIcon color="primary" />
</Button>
</Grid>
</isThisAFieldsArrayContext.Provider>
)
useFieldArray returns fields
const { fields, ... } = useFieldArray(...)
Use it to map
fields.map((item, index) => {
// there is an 'id' in item. Use it for key!
return (
<ListItem key={item.id}>
...
<button onClick={() => handleRemove(index)}>Delete</button>
...
</ListItem>
)
})
The value of 'formNamePrefix' has to be an array of objects.
Example:
useForm({
defaultValues: {
[formNamePrefix]: [{name: 'name1'}, {name: 'name2'}, ...]
}
})

React Hooks - Input loses focus when adding or removing input fields dynamically

I have a form displayed in a modal window. This form is divided into several tabs. One of them has two grouped field: a dropdown list countries and a description textfield. There is an "Add button" which allows to create a new grouped field.
The problem is that each time, I filled the textfield, i lost the focus, because the form is re-rendered. I tryed to move the form outside of the default function but i still have the same issue.
I also set unique keys to each element, but still.
I know there is a lot of documentation of this, but despite this, its not working. I could set the autofocus, but when there is more than a one group field, the focus will go to the last element.
I am using Material UI (react 17)
Anyway, below is the code (which has been truncated for a better visibility) :
function GeoForm (props) {
return(
<React.Fragment>
<Autocomplete
id={"country"+props.i}
style={{ width: 300 }}
options={Object.keys(countries.getNames('fr')).map(e => ({code: e, label: countries.getNames('fr')[e]}))}
getOptionSelected={(option, value) => (option.country === value.country)}
classes={{
option: props.classes.option,
}}
defaultValue={props.x.country}
key={"country"+props.i}
name="country"
onChange={(e,v) => props.handleInputGeoCountryChange(e, v, props.i)}
getOptionLabel={(option) => (option ? option.label : "")}
renderOption={(option) => (
<React.Fragment>
{option.label}
</React.Fragment>
)}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
variant="outlined"
inputProps={{
...params.inputProps,
autoComplete: 'new-password', // disable autocomplete and autofill
}}
/>
)}
/>
<TextField
id={"destination"+props.i}
onChange={e => props.handleInputGeoDestinationChange(e, props.i)}
defaultValue={props.x.destination}
name="destination"
key={"destination"+props.i}
margin="dense"
label="Destination"
type="text"
/>
{props.inputGeoList.length !== 1 && <button
className="mr10"
onClick={() => props.handleRemoveGeoItem(props.i)}>Delete</button>}
{props.inputGeoList.length - 1 === props.i &&
<Button
onClick={props.handleAddGeoItem}
variant="contained"
color="primary"
//className={classes.button}
endIcon={<AddBoxIcon />}
>
Add
</Button>
}
</React.Fragment>
)
}
export default function modalInfo(props) {
const classes = useStyles();
const [openEditDialog, setOpenEditDialog] = React.useState(false);
const handleAddGeoItem = (e) => {
console.log(e);
setInputGeoList([...inputGeoList, { country: "", destination: "" }]);
};
// handle input change
const handleInputGeoCountryChange = (e, v, index) => {
const list = [...inputGeoList];
list[index]['country'] = v;
setInputGeoList(list);
};
const handleInputGeoDestinationChange = (e, index) => {
const { name, value } = e.target;
console.log(name);
const list = [...inputGeoList];
list[index][name] = value;
setInputGeoList(list);
console.log(inputGeoList)
};
// handle click event of the Remove button
const handleRemoveGeoItem = index => {
const list = [...inputGeoList];
list.splice(index, 1);
setInputGeoList(list);
};
const TabsEdit = (props) => {
return(
<div className={classes.root}>
<form className={classes.form} noValidate onSubmit={onSubmit}>
<Tabs
orientation="vertical"
variant="scrollable"
value={value}
onChange={handleChange}
aria-label="Vertical tabs example"
className={classes.tabs}
>
[...]
<Tab label="Geo-targeting" {...a11yProps(4)} disableRipple />
</Tabs>
[...]
</TabPanel>
<TabPanel value={value} index={4}>
{
inputGeoList.map((x, i)=>{
return(
<GeoForm
inputGeoList={inputGeoList}
x={x}
i={i}
handleRemoveGeoItem={handleRemoveGeoItem}
handleInputGeoDestinationChange={handleInputGeoDestinationChange}
handleInputGeoCountryChange={handleInputGeoCountryChange}
handleAddGeoItem={handleAddGeoItem}
handleInputGeoDestinationChange={handleInputGeoDestinationChange}
classes={classes}
/>
)
})
}
</TabPanel>
<TabPanel value={value} index={5}>
Mobile-targeting
</TabPanel>
<DialogActions>
<Button onClick={props.handleClose} color="primary">
Annuler
</Button>
<Button type="submit" color="primary">
Enregistrer
</Button>
</DialogActions>
</form>
</div>
)
}
return (
<div>
<div>
<EditIconButton onClickEdit={() => setOpenEditDialog(true)} />
</div>
<div>
<EditDialog open={openEditDialog} handleClose={() => setOpenEditDialog(false)} >
<TabsEdit/>
</EditDialog>
</div>
</div>
);
codesandbox
Any help or suggestion are welcome. Thank you
TL;DR: Your TabsEdit component was defined within another component, thus React was remounting it as a new component each time, making the focused state to be lost. This Pastebin fixes your code, it maintains the focus as you type.
NL;PR: I suffered from this same issue for months, the props are not the only reference checked for reconciliation, the component's memory ref is too. Since the component's function ref is different each time, React process it as a new component, thus unmounting the previous component, causing the state to be lost, in your case, the focus.

Update list item in material ui?

I have a project in react, and I'm building a simple todo app. When an item in the list is clicked, I want to update the value. My code looks like this:
export default function ShowTodos () {
const [todos, setTodos] = React.useState<ITodo[]>([]);
const [selectedTodo, setSelectedTodo] = React.useState<ITodo>({});
const [dialogState, setDialogState] = React.useState<boolean>(false);
const confirm = useConfirm();
useEffect(() => {
getTodos()
.then(({data: {todos}}: ITodo[] | any) => {
const todoList = todos
setTodos(todoList)
})
.catch((err: Error) => console.log(err))
})
const handleConfirmation = (id: string) => {
confirm({ description: 'This action is permanent!' })
.then(() => { deleteTodoById(id).then(r => )})
}
return (
<List>
{todos.map((todo: ITodo ) =>
(
<ListItem onClick={() => {
console.log("hh", todo);
setDialogState(!dialogState)
}
} key={todo._id}>
<ListItemText
primary={todo.text}
/>
<IconButton edge="end" aria-label="delete" onClick={() => handleConfirmation(todo._id)}>
<DeleteIcon />
</IconButton>
<Dialog open={dialogState} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Update</DialogTitle>
<DialogContent>
<TextField
defaultValue={todo.text}
autoFocus
margin="dense"
id="name"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={() => setDialogState(false)}>
Cancel
</Button>
<Button color="primary" onClick={() => updateTodo(selectedTodo)}>
Update
</Button>
</DialogActions>
</Dialog>
</ListItem>
)
)}
</List>
);
}
However the odd thing is that the defaultValue when the item is clicked is always the last item in the list. How am I to change it to be the text of the item clicked?
Thanks!
You need separate state of each todo item to new Component
The main problem in this piece of code:
<ListItem onClick={() => {
console.log("hh", todo);
setDialogState(!dialogState)
}
} key={todo._id}>
onClick you toggling dialog, but not specify selectedTodo.
try something like that:
onClick={() => {
console.log("hh", todo);
setSelectedTodo(todo);
setDialogState(!dialogState);
}
and I guess you should define updateTodo

Resources