Use onChange in Material UI tabs handleChange function in React component - reactjs

Currently I have a certain navigation setup like this:
<div>
{letters?.map((letter) => {
return (
<button
type="button"
key={item.value}
data-active={value === item.value}
onClick={() => {
if (onChange) {
onChange(item.value);
}
}}
>
{item.label}
</button>
);
})}
</div>
I want to change it, to use Material UI tabs.
I have the following:
const MyComponent = ({ firstLetters }: Props) => {
const [value, setValue] = useState(0);
const handleChange = (_event: React.ChangeEvent<{}>, newValue: string) => {
console.warn(newValue);
setValue(newValue);
};
return (
<Tabs css={styles.tabsRoot} value={value} onChange={handleChange}>
{firstLetters?.map((item) => {
return (
<Tab
key={item.value}
label={item.label}
disableRipple
/>
);
})}
</Tabs>
)
};
How do I use onChange like my previous onClick function callback into the handleChange function?

According to the MUI docs, you should have value and onChange props in the Tabs component. So, you should remove the onClick prop from the Tab components, and add the aforementioned Tabs props like so:
<Tabs value={value} onChange={handleChange}>
{letters?.map((item) => {
return (
<Tab
key={item.value}
label={item.label}
disableRipple
value={item.value}
/>
);
})}
</Tabs>

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

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.

How to use a function on onClick instead of onChange?

i have an search function for searching in the table. Now. I want to use the search function on the onClick of the icon instead of the onChange of the input field. I don't think i need the throttle for that. I try to use the function setGlobalFilter directly inside the handleClick but it won't work
function GlobalFilter({ globalFilter, setGlobalFilter }) {
const [value, setValue] = React.useState(globalFilter)
const onChange = React.useCallback(
value => {
const throttledSetGlobalFilter = throttle(
value => {
setGlobalFilter(value || undefined)
},
2000
)
throttledSetGlobalFilter(value)
},
[setGlobalFilter]
)
return (
<span className={styles.componentGlobalFilter}>
<input
className={styles.input}
value={value || ''}
onChange={(e) => {
setValue(e.target.value)
onChange(e.target.value)
}}
placeholder={`Zoek in deze tabel`}
/>
<Icon onClick={handleClick} layoutClassName={styles.icon} {...{ icon }} />
</span>
)
function handleClick() {
}
}
If I understand your question, you want the icon click to invoke the setGlobalFilter callback with the input value. Remove the onChange handler and directly update value state. Call setGlobalFilter with the state value when the icon is clicked.
function GlobalFilter({ globalFilter, setGlobalFilter }) {
const [value, setValue] = useState(globalFilter ?? '');
const handleClick = () => setGlobalFilter(value);
return (
<span className={styles.componentGlobalFilter}>
<input
className={styles.input}
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder={`Zoek in deze tabel`}
/>
<Icon onClick={handleClick} layoutClassName={styles.icon} {...{ icon }} />
</span>
);
}
If you need to throttle the setGlobalFilter then the following may help.
function GlobalFilter({ globalFilter, setGlobalFilter }) {
const [value, setValue] = useState(globalFilter ?? '');
const handleClick = throttle(
() => setGlobalFilter(value),
2000,
);
return (
<span className={styles.componentGlobalFilter}>
<input
className={styles.input}
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder={`Zoek in deze tabel`}
/>
<Icon onClick={handleClick} layoutClassName={styles.icon} {...{ icon }} />
</span>
);
}

Redux Form using Material Ui with nested MenuItem not working

I am new to React, Redux Form and Material. I would like to create a nested drop down selector component that can be dropped in a Redux Form similar to this:
Here is the renderSelectField used to create the select component.
const renderSelectField = ({
input,
label,
meta: { touched, error },
children,
...custom
}) => (
<SelectField
floatingLabelText={label}
errorText={touched && error}
{...input}
onChange={(event, index, value) => input.onChange(value)}
children={children}
{...custom}
/>
);
const MaterialUiForm = (props) => {
const { handleSubmit, pristine, reset, submitting, classes } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<Field
name="favoriteColor"
component={renderSelectField}
label="Favorite Color"
>
<MenuItem value="ff0000" primaryText="Red" />
<MenuItem value="00ff00" primaryText="Green" />
<MenuItem value="0000ff" primaryText="Blue" />
</Field>
</div>
<div>
<Field
id='chapter'
name='chapter'
component={SelectMenu}
label='Chapter'
/>
</div>
<div>
<button type="submit" disabled={pristine || submitting}>
Submit
</button>
<button type="button" disabled={pristine || submitting} onClick={reset}>
Clear Values
</button>
</div>
</form>
);
};
My SelectMenu component is
const chapterFormValues = [
{
key: "international-4",
caption: "France"
},
{
key: "international-5",
caption: "Africa"
},
{
key: "international-6",
caption: "United Kingdom"
},
{
key: "usa-key",
caption: "North America",
subMenuItems: [
{
key: "usaChapter-1",
caption: "Central"
},
{
key: "usaChapter-2",
caption: "East"
}
]
}
];
const SelectMenu = (props) => {
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((open) => !open);
};
const handleSubMenuClose = () => {
setOpen((open) => !open);
};
const { label, classes } = props;
const renderMenuItems = () => {
return (
chapterFormValues !== undefined &&
chapterFormValues.map((option) => {
if (option.hasOwnProperty("subMenuItems")) {
return (
<React.Fragment>
<MenuItem onClick={handleClick} className={classes.menuItem}>
{option.caption}
{open ? <IconExpandLess /> : <IconExpandMore />}
</MenuItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<hr />
{option.subMenuItems.map((item) => (
<MenuItem
key={item.key}
className={classes.subMenuItem}
onClick={handleSubMenuClose}
>
{item.caption}
</MenuItem>
))}
</Collapse>
</React.Fragment>
);
}
return (
<MenuItem
className={classes.menuItem}
key={option.key}
value={option.caption === "None" ? "" : option.caption}
>
{option.caption}
</MenuItem>
);
})
);
};
return (
<FormControl>
<InputLabel>{label}</InputLabel>
<MuiSelect
input={<Input id={`${props.id}-select`} />}
value={props.value}
{...props.input}
{...props.custom}
>
{renderMenuItems()}
</MuiSelect>
</FormControl>
);
};
Here is a link to the code sandbox I created. Material UI ReduxForm Select
It works except the nested drop down does not update the selector field. I have researched this and found this issue Stackoverflow redux form with nested lists but no solution.
Can anyone give me advice as to what I am missing? I believe I need to pass the event in the handleSubMenuClose function back to the Redux Form somehow but am stumped as to how to do this.
Well using Material UI MenuItem didn't work but I was able to use redux form and create a nested drop down that did.
This is a screen shot of what I created. It did not have the functionality to open/close a panel but it still gave the user a sense of a nested dropdown.
Here is the code that I changed in the SelectMenu method. The key was to use the native form of the Material UI Select component and the optgroup element.
const SelectMenu = (props) => {
const { label, classes } = props;
const renderMenuItems = () => {
return (
chapterFormValues !== undefined &&
chapterFormValues.map((option) => {
if (option.hasOwnProperty("subMenuItems")) {
return (
<React.Fragment>
<optgroup label={option.caption} className={classes.menuItem}>
{option.subMenuItems.map((item) => (
<option
key={item.key}
className={classes.subMenuItem}
>
{item.caption}
</option>
))}
</optgroup>
</React.Fragment>
);
}
return (
<option
className={classes.menuItem}
key={option.key}
value={option.caption === "None" ? "" : option.caption}
>
{option.caption === "None" ? "" : option.caption}
</option>
);
})
);
};
return (
<FormControl>
<InputLabel>{label}</InputLabel>
<Select
native
input={<Input id={`${props.id}-select`} />}
value={props.value}
{...props.input}
{...props.custom}
>
{renderMenuItems()}
</Select>
</FormControl>
);
};
Helpfully links were :
HTML / CSS: Nested <options> in a <select> field?
Redux Form Material UI: Select with Nested Lists not working

How can i call a function inside another Component with React?

I have a component (Navigation.js) which imports another component (Dialog.js). I want that if I react to a click event, call a function in the dialog component (handleClickOpen ()). But I don't know how to do that. So what i have to do ?
Navigation.js
export default function SimpleBottomNavigation() {
return (
<BottomNavigation
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
showLabels
className={classes.root}
>
<BottomNavigationAction
label="Home"
onClick={'HERE I WANT TO CALL THE FUNCTION IN THE DIALOG COMPONENT'}
icon={<RestoreIcon />}
/>
<BottomNavigationAction label="Neuer Plan" icon={<FavoriteIcon />} />
<BottomNavigationAction label="Azubis" icon={<LocationOnIcon />} />
</BottomNavigation>
);
}
Dialog.js
export default function CustomizedDialogs() {
const [open, setOpen] = React.useState(false);
*/THIS FUNCTION I WANT TO CALL FROM NAVIGATION.JS */
const handleClickOpen = () => {
setOpen(true);
};
[...]
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Open dialog
</Button>
<Dialog
onClose={handleClose}
aria-labelledby="customized-dialog-title"
open={open}
>
<DialogTitle id="customized-dialog-title" onClose={handleClose}>
Modal title
</DialogTitle>
<DialogContent dividers>
</Dialog>
</div>
);
}
There is a aimple way to use child's functions, with functional components it's forwardRef and useImperativeHandle, along those lines:
Navigation (parent)
function Navigation() {
const dialogRef = useRef();
return(
<button onClick={() => dialogRef.current.handleClickOpen()}>
Click me!
</button>
);
}
Dialog (child)
const Dialog = forwardRef((props, ref) => {
useImperativeHandle(ref, () => {
const handleClickOpen = () => {
//your implementation
};
});
return (...);
});
If I over-simplified, please let me know :)

Resources