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>
)}
/>
);
}
Related
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]);
I have the following component which uses the TextField:
const BarcodeField = ({disableEnter, ...rest})=>{
return <TextField {...(disableEnter && { onKeyDown: {(e)=>e.key === 'Enter' && e.preventDefault()} })} {...rest}>
}
and I consume it somewhere else as follows:
import BarcodeField from '...etc path'
const PosCalculator = ()=>{
return (
<>
<BarcodeField disableEnter fullWidth />
</>
)
}
I want to set the BarcodeField from the PosCalculator component focused forever, even if the user clicked outside the component on the body of the page.
But how?
I want also to be able to toggle the force focus state based on a condition
I think you can try this:
const inputRef = useRef();
inputRef.current.focus() //inside useEffect
This is just an idea
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
i am using material-ui for my project and i am doing function to reset text of input to empty when clicking an outer button, it seem like not worked out
this is my code
var inputRef = useRef(null)
assign inputRef to the input field to access DOM
<TextField label="Student Name" ref={inputRef} />
an outer button to reset text field to empty when click it:
<Button variant="contained" color="primary" onClick={() => {inputRef.current.value = ""}}>
Reset
</Button>
and it unchanged, if it is possible, please modify the code in the codesandbox link here, thank you so much
You do incorrectly in step: assign inputRef to the input field to access DOM. It should be a ref of input element instead text field component (actual a div).
You should have state for value of Textfield Or using inputRef instead of ref to point to input element. Demo
import React, { useRef } from "react";
import { TextField, Button } from "#material-ui/core";
import "./styles.css";
export default function App() {
var inputRef = useRef(null);
return (
<div className="App">
<TextField label="Student Name" inputRef={inputRef} />
<Button
onClick={() => {
console.log(inputRef);
inputRef.current.value = "";
}}
variant="contained"
color="primary"
>
Reset
</Button>
</div>
);
}
useRef can be used on html DOM elements(<input/>). To pass ref to Material-UI input you should use inputRef property.
Please refer How can I use ref in TextField
var inputRef = useRef(null);
<TextField label="Student Name" inputRef={inputRef} />
demo
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: