How to add onClick button to any tabs in MUI? - reactjs

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

Related

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

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

Use onChange in Material UI tabs handleChange function in React component

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>

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 get the key on click event

I have the following react component:
type MobileNavProp = RouteTableProp
export default function MobileNav({routes}: MobileNavProp) {
const classes = useStyles()
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
const handleOnClickItem = (event: React.MouseEvent<HTMLDivElement>): void => console.log(event.target)
return (
<React.Fragment>
<IconButton
className={classes.icon}
color="inherit"
aria-label="Open navigation"
edge="end"
onClick={() => setDrawerOpen(true)}
>
<MenuRoundedIcon fontSize="large"/>
</IconButton>
<SwipeableDrawer open={drawerOpen}
onClose={() => setDrawerOpen(false)}
onOpen={() => console.log("Drawer is open")}
disableBackdropTransition={!iOS}
anchor="top"
disableDiscovery={iOS}
>
<List subheader={
<ListSubheader component="div" id="logo" className={classes.company}>
<img className={classes.logo} src={logo} alt="databaker-logo"/>
<Typography variant="h6" className={classes.title}>
DATABAKER
</Typography>
</ListSubheader>
}>
{
routes.map((rt, index) => (
<ListItem
divider
key={index}
button
onClick={handleOnClickItem}
>
<ListItemText primary={rt.name}/>
</ListItem>
))
}
</List>
</SwipeableDrawer>
</React.Fragment>
)
}
When the user click on ListItem(the handler function is handleOnClickItem), then it shows me:
<span class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock">ABOUT US</span>
However, I would like to get the clicked index that I have provided:
<ListItem
divider
key={index}...
How to get it?
You can make handleOnClickItem a higher-order function that takes the index as a parameter, and call it:
const makeHandleOnClickItem = (i: number) => (event: React.MouseEvent<HTMLDivElement>) => {
console.log(event.target);
console.log(i);
};
and change
onClick={handleOnClickItem}
to
onClick={makeHandleOnClickItem(index)}

Unit testing with Jest, expect problem receiving empty/undefined object

i am new to unit testing and i am trying to test the length of a navigation bar, below you can see the nav bar component which is within a class
<AppBar className={classes.appBar} position="static">
<Toolbar className={classes.toolbar}>
{authenticated && this.state.layoutMode ==='desktop' ? (
<Grid container
direction ="row"
justify="flex-end"
alignItems="center">
<div className={classes.root}>
<Tabs id="Tab" className = {classes.tabBar} value={value} onChange={this.handleChange}>
<Tab label="example" alt="example" onClick={() => Router.push('/example')}/>
<Tab label="example" alt="example" onClick={() => Router.push('/example')}/>
<Tab label="example" alt="example" onClick={() => Router.push('/example')}/>
<Tab label="example" alt="example" onClick={() => Router.push('/example')}/>
<Tab label="example" alt="example" onClick={() => Router.push('/example')}/>
<Tab label="example" alt="example" onClick={() => Router.push('/example')}/>
</Tabs>
</div>
</Grid>
) : (
authenticated && <BurgerMenu/>
)}
Below is the test i have written so far
describe('NavBar', () => {
const intialState = {
ui: { width: 1361 },
auth: { authenticated: true }
}
let container;
beforeEach(() => {
container = shallow(<Nav />);
});
it('should render self and subcomponents', () => {
expect(container).toMatchSnapshot();
});
it('should contain an AppBar', () => {
console.log(container.find(AppBar));
expect(container.find(AppBar)).toHaveLength(1);
});
})
however my problem is that the .find(AppBar) doesnt seem to be found, when using a console log(container.find(AppBar)) it shows the following "ShallowWrapper {}" this would mean that the object is empty and therefore not equal to 1, however i do not know where to point the find to correctly find the tabs to test
The solution is
let container;
beforeEach(() => {
container = mount(<Nav />);
});
diving to deep within the DOM with shallow.

Resources