Implementing react hooks useState in material-ui Tab container not working - reactjs

I am trying to implement react-hooks useState in material-ui/tabs component. I am able to do it with function handleChange, but I am trying to learn to implement hooks. useState is working for input field but not for material-ui tabs onChange function. Following is my code:
const [value, setValue] = useState(0)
<Tabs
value={value}
onChange={(event) => setValue(event.target.value)}
variant="scrollable"
scrollButtons="on"
indicatorColor="primary"
textColor="primary"
>
<Tab label="All" />
{subjects.map((subject) => (
<Tab label={subject.subjectName} />
))}
</Tabs>
I tried console log with useEffect, and it returns undefined onChange

The main issue I see is that
onChange={(event) => setValue(event.target.value)}
should instead be:
onChange={(event, newValue) => setValue(newValue)}
The event in this case is just a click event and the target will be the particular DOM element clicked on which could be any one of several elements (e.g. span, button) that make up the Tab. Unlike with input elements, none of the DOM elements in the Tab have a value property, so Material-UI passes the value as a separate argument to the onChange function.
Here is the relevant code from the Tab component:
handleChange = event => {
const { onChange, value, onClick } = this.props;
if (onChange) {
onChange(event, value);
}
if (onClick) {
onClick(event);
}
};
You'll find the onChange signature documented in the documentation for the Tabs props: https://material-ui.com/api/tabs/#props
onChange func Callback fired when the value changes. Signature: function(event: object, value: number) => void
Here's a working example based on your code:

Related

React Js material UI not updating the value when set

so I have this code in material UI react js which is not updating the value properly or so I thought, to explain this further I have this code
import * as React from 'react';
import Tabs from '#mui/material/Tabs';
import Tab from '#mui/material/Tab';
export function Home() {
const [value, setValue] = React.useState('Pending');
const handleChange = (event, newValue) => {
setValue(newValue);
console.log(value);
};
return (
<div>
<>
<Tabs
value={value}
onChange={handleChange}
textColor="secondary"
indicatorColor="secondary"
aria-label="secondary tabs example"
>
<Tab value="Pending" label="Pending" />
<Tab value="Received" label="Received" />
<Tab value="Prepared" label="Prepared" />
<Tab value="Cancelled" label="Cancelled" />
</Tabs>
</>
</div>
);
}
If I run this code and click on the Tab with the label "Pending" the console.log won't be triggered. if I click again on the Tag with the label "Received" the console.log with displays "Pending" instead of "Received".
This happens all the time I thought when you set the value and console log it should show the latest value you selected.
sample output:
As you can see am currently selected the "Received" Tag but on the display its showing "Pending"
Is this how react js/Material UI behaves or am I just missing something here?
thanks
Like Dave Newton said in the comments, setting the state is asynchronous as mentioned in the React Docs here:
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
If you want to console.log the value, you can update your handler to log the new value instead
const handleChange = (event, newValue) => {
setValue(newValue);
console.log(newValue);
};
Or you can use useEffect outside the handler to always log value when it changes
useEffect(() => {
console.log(value);
}, [value]);

click event is not triggering on input element from ref

I am using react 17.0.2 and material ui 4.11.4
I want to customize the select element appearnce (like Chip component of material ui). For this purpose I am using Autocoomplete component which renders an input element. I have rendered Chip component below the input element.
I am also getting the ref in the renderInput callback function which I am ustlising to trigger input click from Chip component.
When I log the params.inputProps.ref.current I am indeed getting the input element but calling click function does not show the dropdown but just focus the input element. When I click the input directly then it shows the dropdown.
I have created a sandbox of this behaviour.
CodeSandBox
I would recommend using state as a controlled component.
export default function App() {
const classes = useStyles();
const statusDropdownInput = useRef(null);
let [val, setVal] = useState(false) // <- Store the open state
const handleStatusDropdownClick = (params,e) => {
setVal(!val) // <- Will toggle the dropdown
};
let inputElement = null
return (
<Autocomplete
className={classes.statusDropdown}
id="status"
open={val} // <- Control the input elements state here
options={["Option 1", "Option 2"]}
renderInput={(params) => (
<div ref={params.InputProps.ref}>
<input type="text" {...params.inputProps} />
<Chip
size="small"
avatar={<DoneOutlinedIcon />}
label="Published"
clickable
onDelete={() => {
console.log(params.inputProps);
}}
onClick={(e) => handleStatusDropdownClick(params)}
deleteIcon={<ExpandMoreIcon />}
/>
</div>
)}
/>
);
}

MUI Autocomplete and react-hook-form not displaying selected option with fetched data

I have a MUI Autocomplete inside a form from react hook form that works fine while filling the form, but when I want to show the form filled with fetched data, the MUI Autocomplete only displays the selected option after two renders.
I think it's something with useEffect and reset (from react hook form), because the Autocompletes whose options are static works fine, but the ones that I also have to fetch the options from my API only works properly after the second time the useEffect runs.
I can't reproduce a codesandbox because it's a large project that consumes a real api, but I can provide more information if needed. Thanks in advance if someone can help me with this.
The page where I choose an item to visualize inside the form:
const People: React.FC = () => {
const [show, setShow] = useState(false);
const [modalData, setModalData] = useState<PeopleProps>({} as PeopleProps);
async function showCustomer(id: string) {
await api
.get(`people/${id}`)
.then((response) => {
setModalData(response.data);
setShow(true);
})
.catch((error) => toast.error('Error')
)
}
return (
<>
{...} // there's a table here with items that onClick will fire showCustomer()
<Modal
data={modalData}
visible={show}
/>
</>
);
};
My form inside the Modal:
const Modal: React.FC<ModalProps> = ({data, visible}) => {
const [situations, setSituations] = useState<Options[]>([]);
const methods = useForm<PeopleProps>({defaultValues: data});
const {reset} = methods;
/* FETCH POSSIBLE SITUATIONS FROM API*/
useEffect(() => {
api
.get('situations')
.then((situation) => setSituations(situation.data.data))
.catch((error) => toast.error('Error'));
}, [visible]);
/* RESET FORM TO POPULATE WITH FETCHED DATA */
useEffect(() => reset(data), [visible]);
return (
<Dialog open={visible}>
<FormProvider {...methods}>
<DialogContent>
<ComboBox
name="situation_id"
label="Situação"
options={situations.map((item) => ({
id: item.id,
text: item.description
}))}
/>
</DialogContent>
</FormProvider>
</Dialog>
);
};
export default Modal;
ComboBox component:
const ComboBox: React.FC<ComboProps> = ({name, options, ...props}) => {
const {control, getValues} = useFormContext();
return (
<Controller
name={`${name}`}
control={control}
render={(props) => (
<Autocomplete
{...props}
options={options}
getOptionLabel={(option) => option.text}
getOptionSelected={(option, value) => option.id === value.id}
defaultValue={options.find(
(item) => item.id === getValues(`${name}`)
)}
renderInput={(params) => (
<TextField
variant="outlined"
{...props}
{...params}
/>
)}
onChange={(event, data) => {
props.field.onChange(data?.id);
}}
/>
)}
/>
);
};
export default ComboBox;
I think you simplify some things here:
render the <Modal /> component conditionally so you don't have to render it when you are not using it.
you shouldn't set the defaultValue for your <Autocomplete /> component as RHF will manage the state for you. So if you are resetting the form RHF will use that new value for this control.
it's much easier to just use one of the fetched options as the current/default value for the <Autocomplete /> - so instead of iterating over all your options every time a change is gonna happen (and passing situation_id as the value for this control), just find the default option after you fetched the situations and use this value to reset the form. In the CodeSandbox, i renamed your control from "situation_id" to "situation". This way you only have to map "situation_id" on the first render of <Modal /> and right before you would send the edited values to your api on save.
I made a small CodeSandbox trying to reproduce your use case, have a look:
mui#v4
mui#v5
Another important thing: you should use useFormContext only if you have deeply nested controls, otherwise just pass the control to your <ComboBox /> component. As with using FormProvider it could affect the performance of your app if the form gets bigger and complex. From the documentation:
React Hook Form's FormProvider is built upon React's Context API. It solves the problem where data is passed through the component tree without having to pass props down manually at every level. This also causes the component tree to trigger a re-render when React Hook Form triggers a state update

How to get the ref.current dom element when using react-select and react refs?

I have a group of react selects using the react-select package – https://react-select.com/home.
I have a component that wraps three react-selects – something basically like this:
import Select from "react-select"
function SelectGroup(){
const ref1 = useRef(null);
const ref2 = useRef(null);
const ref3 = useRef(null);
return (
<div>
<Select ref={ref1} />
<Select ref={ref2} />
<Select ref={ref3} />
</div>
)
}
I need to perform some checks to see what's in focus. The reason I am doing this is because the group of selects is a single component that needs to be able to allow the user to navigate in multiple ways through the keyboard. Spacebar, and enter keys should allow the user to shift the focus to the next select element. Arrow keys should allow the user to go to the next or previous select. So, focus needs to be managed somehow, and this means knowing what's currently in focus.
Normally, I would do that like this:
function isActiveElement(ref){
return ref?.current === document.activeElement
}
However, for ref.current react-select returns an object called StateManager – https://react-select.com/props#statemanager-props
So, ref.current === document.activeElement always returns false.
How, can I check to see which react-select is in focus? I was unable to find anything about this in the react-select docs. Maybe, I'm missing it? I have solved this problem others ways, but I was curious if there is a way to do it this "simpler way" I describe above, which may be the more common approach.
You can listen to the focus and the blur event to keep track of the currently focused Select:
export default function App() {
const [focus, setFocus] = useState(-1);
const onBlur = () => setFocus(-1);
return (
<div>
<div>Current focus: {focus}</div>
<Select
onFocus={() => setFocus(0)}
onBlur={onBlur}
options={colourOptions}
/>
<Select
onFocus={() => setFocus(1)}
onBlur={onBlur}
options={colourOptions}
/>
<Select
onFocus={() => setFocus(2)}
onBlur={onBlur}
options={colourOptions}
/>
</div>
);
};
Live Demo

Need help capturing the user choice from Autocomplete (multiple) in state (React)

Thanks for reading. I'm using Material-UI Autocomplete and trying to figure out how to capture a user choice. Here's the code. For now, I'm trying to console.log the user choice or deletion of a movie from the selection list. If I can get this to work I'll probably use a state settor in handleChange to capture the movie and read the state value for processing once the dialog is submitted. Since this is a multiple Autocomplete there may be several selected values.
Currently event.target.value in handleChange displays a zero when selecting a movie from the list, and undefined when removing. I need it to display the actual title.
Happy to consider any suggestion to get to the objective of being able to process all autocomplete selections upon dialog submission. Thanks in advance!
React version: 16.8
Resolved by sending the parameters (event, value) from onChange. Please see the onChange value in the below snippet.
export default function App() {
const classes = useStyles();
const handleChange = (value) =>
{
const x = value.map(function(a){return {title:a.title};});
console.log(x);
};
return (
<div className={classes.root}>
<Autocomplete
multiple
id="tags-outlined"
options={top100Films}
onChange={(event, value) => handleChange(value)}
getOptionLabel={(option) => option.title}
defaultValue={[top100Films[14]]}
filterSelectedOptions
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label="filterSelectedOptions"
placeholder="Favorites"
/>
)}
/>
</div>
);
}

Resources