I have this checkbox that when clicked doesn't show the text area I want to implement in collapse such that when the checkbox is clicked I want to see the content. the checkbox does not show the content when it's clicked
import React from 'react';
import { Grid, FormControlLabel, Checkbox} from '#mui/material';
import { Collapse } from 'react-collapse';
import { Formik } from 'formik';
import { useFormik } from 'formik';
import MyTextField from './MyTextField';
let defaultInitialIngestObject = {
pushEnabled: true
}
export default function App(props) {
const { initialValues } = props
const formik = useFormik({
initialValues: Boolean(initialValues) ? initialValues : defaultInitialIngestObject,
});
return (
<div>
<Grid item xs={12}>
<Grid item xs={4}>
<FormControlLabel
style={{ margin: 0, padding: 0, marginTop: '8px', verticalAlign: 'top' }}
label="Submit"
id="sub"
control={
< Checkbox size="large"
onClick={(evt) => formik.setFieldValue("sub", evt.target.checked)}
checked={formik.values['sub']}
/>
}
/>
</Grid>
<Collapse in={formik.values.sub}>
<Grid item xs={4}>
<MyTextField
disabled={formik.values.sub}
id="firs"
label="First"
formik={formik}
/>
</Grid>
</Collapse>
</Grid>
</div>
)
}
There are a few things needing adjustment.
First, you are trying to change an uncontrolled input to be controlled
causing this warning in the console
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
Let's address the issues.
The reason the warning it's happening is that you are mounting the input with a value of undefined, then later when you click on it you are setting a value of true [checked].
This will cause the error above, so lets predefine the value to be
false [unchecked]
checked={formik.values.sub || false}
Second, according to MUI Checkbox docs, the proper API is not onClick but onChange https://mui.com/material-ui/api/form-control-label/ , so, lets update that
onChange={
(evt) => formik.setFieldValue("sub", evt.target.checked)
}
Third, the property in doesn't seem to be in the API according to the library docs. https://www.npmjs.com/package/react-collapse
I changed it to isOpened and now it works.
<Collapse
// in={formik.values.sub}
isOpened={formik.values.sub}
>
I've created a sandbox here with a working version.
https://codesandbox.io/s/stackoverflow-example-checkbox-expand-8o2wzk?file=/src/App.js
PS. for your next issue, please provide a sandbox to make it easier for other to help out.
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 trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.
I am developing a React functional component for a model CRUD operations, this component will render save and delete buttons on the model form, and I am trying to show a waiting indicator when the user clicks the save or delete button and hide the indicator when the process completes.
I am using the material-ui React components library, and for the waiting indicator I am using the Backdrop component.
the component props are the save and delete callbacks and set by the parent component.
I added a boolean state to show/hide this backdrop, but the waiting indicator is not showing as the setState in react is asynchronous. so how can I achieve this?
here is my component:
export default function ModelControls({onSave, onDelete}) {
const [wait, setWait] = useState(false);
const saveClick = () => {
setWait(true);
const retId = onSave();
setWait(false);
...
};
return (
<Container maxWidth={"xl"}>
<Grid container spacing={2}>
<Grid item xs={6}>
<Box display="flex" justifyContent="flex-end">
<Box component="span">
<Button size="small" color="primary" onClick={saveClick}>
<SaveIcon />
</Button>
</Box>
</Box>
</Grid>
</Grid>
<Backdrop open={wait}>
<CircularProgress color="primary" />
</Backdrop>
</Container>
);
}
Just make the function async and add await in front of the save function.
const saveClick = async () => {
setWait(true);
const retId = await onSave();
setWait(false);
};
Thanks #Dipansh, you inspired me to the following solution.
now the onSave callback from parent must return a promise object
const saveClick = () => {
setWait(true);
onSave().then((retId) => {
...
setWait(false);
});
};
this way it is working as needed.
I have a simple controlled input with an onChange event handler.
Everything works as it should with handleChange firing on every keystroke, the problem is it is very slow.
There is a very noticeable lag when using the input. Is there some extra code I have to right to get this to work like a normal input?
Do I have to debounce the input?
There is no mention of this issue in the docs as far as I can tell, and I don't know if there's something extra I have to do or if I'm using the onChange callback incorrectly.
handleChange = (event) => {
this.setState({ itemNumber: event.target.value })
}
<TextField
id="Part #"
label="Part #"
value={this.state.itemNumber}
onChange={this.handleChange}
margin="normal"
/>
The component:
export class Dashboard extends Component {
state = {
report: '',
selectedDate: new Date(),
itemNumber: '',
}
static propTypes = {
classes: object,
headerTitle: string,
userInfo: object,
}
static defaultProps = {
classes: {},
headerTitle: undefined,
userInfo: {},
}
reportSelected = (event) => {
this.setState(() => {
return {
report: event.target.value,
}
})
}
handleDateChange = (date) => {
this.setState({ selectedDate: new Date(date) })
}
handleChange = (event) => {
this.setState({ itemNumber: event.target.value })
}
render () {
const { classes, headerTitle, userInfo } = this.props
return (
<div className={classes.dashboard}>
<HeaderTitle title="Dashboard" />
<Helmet>
<title>{headerTitle}</title>
</Helmet>
{ userInfo.isAuthorized &&
<Grid container direction={'row'} justify={'center'} className={classes.formContainer}>
<Grid item xs={12} sm={12} md={12} lg={6} xl={5}>
<form className={classes.form}>
<FormControl className={classes.presetReportsInput}>
<InputLabel htmlFor="reports">Preset Reports</InputLabel>
<Select
value={this.state.report}
onChange={this.reportSelected}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
{presetReports.getReportList().map(report => (
<MenuItem value={report.name} key={report.name}>
{report.name}
</MenuItem>
))}
</Select>
</FormControl>
{ (this.state.report === 'Inventory Snapshot' ||
this.state.report === 'Weekly Fill Rate' ||
this.state.report === 'Open Orders' ||
this.state.report === 'Weekly Shipments') &&
<div>
<Grid container spacing={8} direction={'row'}>
<Grid item>
<MuiPickersUtilsProvider utils={MomentUtils}>
<DatePicker
className={classes.datePicker}
margin="normal"
keyboard
format="DD/MM/YYYY"
disableFuture
autoOk
mask={value => (value ? [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/] : [])}
value={this.state.selectedDate}
onChange={this.handleDateChange}
disableOpenOnEnter
animateYearScrolling={false}
/>
</MuiPickersUtilsProvider>
</Grid>
<Grid item>
<TextField
id="Part #"
label="Part #"
value={this.state.itemNumber}
onChange={this.handleChange}
margin="normal"
/>
</Grid>
</Grid>
<Button variant="raised" color="primary" style={{ marginTop: 10 }}>
Search
</Button>
</div>
}
{ this.state.report === '' &&
<div>
<TextField
id="queryField"
label="Run a Query"
className={classes.queryField}
helperText=""
margin="normal"
multiline
rows="5"
/>
<Grid container direction={'row'} justify={'flex-end'}>
<Grid item>
<Button variant="raised" color="primary">
Export
</Button>
</Grid>
<Grid item>
<Button variant="raised" color="primary">
Save Query
</Button>
</Grid>
</Grid>
</div>
}
</form>
</Grid>
{ this.state.report === 'Inventory Snapshot' &&
<Grid container className={classes.table}>
<Grid item xs={12} sm={12} md={12} lg={12} xl={12}>
<InventoryReport />
</Grid>
</Grid>
}
</Grid>
}
</div>
)
}
}
const styles = {
dashboard: {},
formContainer: {
margin: 0,
width: '100%',
},
presetReportsInput: {
width: '100%',
margin: '20% 0 0 0',
},
queryField: {
width: '100%',
margin: '20% 0 0 0',
},
table: {
margin: '50px 0 10px 0',
},
datePicker: {
marginTop: 32,
},
}
const mapStateToProps = state => {
const { layout } = state
const { headerTitle } = layout
return {
headerTitle: headerTitle,
}
}
export default connect(mapStateToProps)(withStyles(styles)(Dashboard))
I'm watching the state update in react devtools in chrome and there's at least a 500ms lag between a character being entered and the state updating, much longer for faster typing. Why is setState so slow? What's the workaround to getting this form to behave like a normal web form?
setState by itself is not slow, it is only when your renders get very expensive that it starts causing issues.
A few things to consider are:
Your main component seems quite large and is being re-rendered on every keystroke so that might cause performance issues. Try breaking it down to smaller components.
Ensure the child-components being rendered in the render method of your main component do not get unnecessarily re-rendered. React Developer Tools or why-did-you-render can point out those unnecessary rerenders. Switching to PureComponent, stateless components or using shouldComponentUpdate can help.
While you can't avoid rerendering here (since your form inputs need to rerender with the new state values), by breaking into smaller components you can look to use shouldComponentUpdate to let React know if a component’s output is not affected by the current change in state or props and avoid unnecessarily rerendering.
If you use functional components:
Use useMemo to prevent recomputing expensive operations or components unless some dependency changed.
Use useCallback to return a memoized version of a callback so that child components that rely on reference equality don't unnecessarily rerender
Use React.memo if your functional component renders the same result given the same props to prevent it from unnecessarily rerendering. Use the second argument to React.memo to customize the behaviour of the memoization (similar to shouldComponentUpdate)
Switch to the production build to get better performance
Switch to uncontrolled components and let the DOM handle the input components itself (this is the "normal web form" behaviour you described). When you need to access the form's values you can use ref's to get access to the underlying DOM nodes and read the values directly off that. This should eliminate the need to call setState and therefore rerender
This is especially an issue for big forms when a change in one input triggers the whole component re-render and that too in redux, although redux does not seem to be interfering here.
If you want your inputs to be controlled and have the same exact behaviour then you can always do like this
<input
className="form-control"
type="text"
name="name"
value={form.name}
onBlur={onChangeHandler}
/>
This only triggers an event on blur and prevent re-render on each change. It's useful since when you click on any other button to process the data, it's guaranteed that you'll have the updated state.
This will not be helpful if your logic requires instant validation/processing related to the input data.
Also, It's important I should mention that sometimes this fails to work with other components which are not native to HTML5 since they might prevent a re-render based on the value prop
Note: Please read the onBlur event here
This might seem like a trivial response but — make sure your console is closed. There is a noticeable lag in controlled components when the console is open!
if you're using a big parent component with too many re-render dependencies
I recommend you to handle re-renders in useEffect of child components
or if its force to use all state updates in the parent
use debounce in the child components.
Options given by #ᴘᴀɴᴀʏɪᴏᴛɪs are good but for my use case it didn't help. I solved it by adding a debounce before setting the state.
const delaySetStateWithDelay = () => {
if (lastRequest) {
window.clearTimeout(lastRequest);
}
lastRequest = setTimeout(() => {
setStateData('Data I Need To Set'
}, 500);
};
You could use https://reactjs.org/docs/perf.html to profile your app. Do you have a large number of components which could be getting re-rendered? It might be necessary to add some componentShouldUpdate() methods to your components to prevent useless re-renders.
I recently faced the same problem, I had a redux store and a description in it, after every keystroke in the description input the store had to be updated, I tried to debounce in lodash but it didn't work, so I created a set timeout function to simply update the store state like this,
setTimeout(() => {
console.log("setting");
this.props.addDescription(this.state.description);
}, 200);
I take the description input field value from the description components' own state and whenever it re-renders I use componentDidMount() to get the latest updated description value from the store.
I had similar problem with slow input in development mode, but with functional components and hooks. Production was ok, but obviously switching on productions doesn't look like approach, if it so slow in development mode there is high chance some problem with code exists. So solution was to isolate state of input from the rest components. State that used by component should be available only for this component. Actually it's not even a solution, but how things should be in react.
I was having the same problem a while ago, had to copy the state coming from a parent component to a local object and then reference the new local object to my input.
If you don't need to use the new state anywhere else prior to saving the form, I reckon you can use the solution below.
const { selectedCustomer } = props;
const [localCustomer, setLocalCustomer] = useState({ name: 'Default' });
useEffect(() => {
setLocalCustomer({ ...selectedCustomer });
}, [selectedCustomer]);
const handleChangeName = (e) => {
setLocalCustomer({ ...localCustomer, name: e.target.value });
};
and then use it in my text field.
<StyledTextField
fullWidth
type='text'
label='Name'
value={localCustomer.name}
</StyledTextField>
Just adding a quick setTimeout was enough to improve performance a lot. The concern I had about timeouts of 100ms or longer was that if submit was executed, depending on the implementation, setState data may not have been added yet.
Something like below should prevent this happening in any real situation:
const onChange = (mydata) => setTimeout(() => setState(mydata), 10);
as noted in #Sudhanshu Kumar's post above, you can use 'onBlur' to execute the setState call when the input element loses focus by passing onChange handler as a callback. To get this to work with MUI use the following...
<TextField
...props
inputProps={{
onBlur: handleChange
}}
/>
This allows for override of native browser onBlur method. Hope this helps.