Checkbox when clicked does not show text fields. collapse not working - reactjs

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.

Related

How to add text dynamic in defaultValue property with TextField from MUI

I'd like to add Edited text after the message if the edit value is true.
How can I do that with TextField component from MUI?
import React, { useState } from "react";
import Box from "#mui/material/Box";
import TextField from "#mui/material/TextField";
export default function BasicTextFields() {
const [edit, setEdit] = useState(true);
const comment = "This is a comment from user.";
const message = `${comment} ${edit ? "Edited" : null}`;
return (
<Box component="form" noValidate autoComplete="off">
<TextField
id="outlined-basic"
label="Outlined"
variant="outlined"
defaultValue={message}
style={{ width: "300px" }}
/>
</Box>
);
}
Currently, I'm seeing This is a comment from user. Edited text when the edit is true and This is a comment from user. null text when the edit is false.
I just want to display Edited and don't need to show null.
Attempts
I tired
const message = `${comment} ${edit && "Edited"}`;
but this shows This is a comment from user. false text in the text input.
The null is being transformed into a string. Try instead:
const message = ${comment} ${edit ? "Edited" :""};

How to show content when click on button

I have a button that I would like when the user clicks on it to show what is inside my MaterialTextField. Inside my materialtextfield's there is: value={projectFilter.eventName}, {projectFilter.eventYear}, and others that follow the same logic. How can I make it so that I have this result?
UPDATE(here some example of what I want to show when I click the button):
return (
<Grid item xs={4}>
<MaterialTextField
autoComplete="off"
variant="outlined"
label="Name"
type="text"
name="eventName"
value={projectFilter.eventName}
sx={{ width: '100%' }}
/>
</Grid>
)
Please provide some code sample.
Basically you have to set state which will change when user clicks the button.
https://reactjs.org/docs/state-and-lifecycle.html
You need state to store the current value of the field. You can use the useState hook for this. You should really read through the React documentation. The useState hook docs in particular is what you are after but it will make much more sense if you read state and lifecycle and just understand how react works.
Here is an example assuming that this projectFilter object is passed into the component as a prop.
import React from 'react';
export const SomeComponent = ({ projectFilter }) => {
const [fieldValue, setFieldValue] = React.useState('');
const clickHandler = (e) => {
// Set the value of the state here. This will cause a re-render.
setFieldValue(projectFilter.eventName);
};
return (
<Grid item xs={4}>
<MaterialTextField value={fieldValue} />
<button onClick={clickHandler}>Click this</button>
</Grid>
);

Implement a clear button for a custom input field to trigger required error with react hook form

Expectation
React Hook form should show the error message when we clear the input field with a cross button
Issues
Required error message not shown after the value is cleared with the cross button.
After clearing value with cross button submit button is not disabled.
Code for the Custom Input Field
import React, { useRef } from 'react';
export default function MyInput(props) {
const inputRef = useRef();
const clear = () => {
if (inputRef.current) {
inputRef.current.value = '';
}
}
return (
<>
<input ref={inputRef} {...props} />
{/* I want to trigger the required error of react hook form on clear*/}
<button onClick={clear} style={{
marginLeft: '-1.2rem',
cursor: 'pointer'
}}>x</button>
</>
);
}
Usage in the form
<Controller
name="firstName"
control={control}
rules={{
required: {
value: true,
message: 'You must enter your first name'
}
}}
render={({ field: { ref, ...rest } }) => <CustomInput {...rest} />}
/>
Not sure if useRef is the right way to go, but I want to use an uncontrolled input that I want to customize with a clear button
Link to Stackblitz - Custom Input with clear Button
One way to let the form know about the change on click of the clear button is to call the setValue method from the useForm hook to register the change manually.
So, I can pass setValue as a prop to my child component i.e. the Custom Input
and set the new value on the click event of the clear button of the input field
const clear = () => {
setValue(name, '', {
shouldValidate: true,
shouldDirty: true
});
}
Also useRef is not required for this use case.
Link to Updated Stackblitz - Custom Input with clear Button

Content in reactstrap modal continues to exist after closing using enzyme/jest

I'm trying to do some testing with enzyme and jest in react, and things work fine when I open a modal e.g. input fields in the modal aren't there and the modal state is false (as intended) when I try to find them using
expect(wrapper.find("input")).toHaveLength(0);
and do exist after I've opened the modal using
const edit = wrapper.find("Button.update-button");
edit.simulate("click");
expect(wrapper.find("input")).toHaveLength(2);
which all works (including the modal state turning to true after it opens) as intended. But when I close the modal, the state gets toggled off correctly, but the modal content (e.g. the input boxes and buttons in the modal) still exist when I try:
expect(wrapper.find("input")).toHaveLength(0);
I still somehow have 2 input fields that shouldn't be there as the modal is closed.
Here is my code for the component I am trying to test if that helps:
/*
Artefact Component displays just UI for the Artefact itself and it's information.
*/
import React, { Component } from "react";
import DeleteArtefact from "../DeleteArtefact";
import UpdateArtefact from "../UpdateArtefact";
import {
Card,
CardImg,
CardTitle,
CardBody,
ButtonGroup,
Button,
CardFooter
} from "reactstrap";
class Artefact extends Component {
// Initialise State
state = {
updatemodal: false,
deletemodal: false
};
// Toggle function for toggling modal open/close
toggleUpdate = () => {
this.setState({
updatemodal: !this.state.updatemodal
});
};
toggleDelete = () => {
this.setState({
deletemodal: !this.state.deletemodal
});
};
prepareUpdateState = () => {
this.props.editUpdate(this.props.artefact);
this.toggleUpdate();
};
render() {
const {
artefact,
onChange,
onUpdateClick,
editUpdate,
onDeleteClick
} = this.props;
return (
<Card>
<CardImg
src={artefact.img}
alt={`Image for Artefact ${artefact.name}`}
/>
<CardBody>
<CardTitle>
<h6>{artefact.name}</h6>
</CardTitle>
</CardBody>
<CardFooter>
<ButtonGroup>
<Button
className="update-button"
color="dark"
onClick={this.prepareUpdateState}
>
Edit
</Button>
<Button
className="delete-button"
color="dark"
onClick={this.toggleDelete}
>
Delete
</Button>
</ButtonGroup>
<UpdateArtefact
artefact={artefact}
onChange={onChange}
onUpdateClick={onUpdateClick}
editUpdate={editUpdate}
toggle={this.toggleUpdate}
modal={this.state.updatemodal}
/>
<DeleteArtefact
_id={artefact._id}
onDeleteClick={onDeleteClick}
toggle={this.toggleDelete}
modal={this.state.deletemodal}
/>
</CardFooter>
</Card>
);
}
}
export default Artefact;
And here is the UpdateArtefact Component that has the modal I'm trying to test:
/*
UpdateArtefact Component is a child Component of ArtefactGallery and
creates a new Artefact by using functions onChange() and updateClick()
and editUpdate() which are passed as props from ArtefactGallery and
passes state back up and makes api calls using axios.
*/
import React, { Component } from "react";
import {
Button,
Modal,
ModalHeader,
ModalBody,
Form,
FormGroup,
Label,
Input
} from "reactstrap";
class UpdateArtefact extends Component {
// Passes state up to ArtefactGallery component and updates the artefact.
onSubmit = e => {
e.preventDefault();
this.props.onUpdateClick(this.props.artefact._id);
this.props.toggle();
};
// Sets state in ArtefactGallery to the initial values of the artefact
// to prepare for any edits to be made in the case that some fields have
// no change, so that there are no null fields.
prepareUpdateState = () => {
this.props.editUpdate(this.props.artefact);
this.props.toggle();
};
render() {
const { artefact } = this.props;
return (
<div style={{ marginLeft: "1rem" }}>
<Modal isOpen={this.props.modal} toggle={this.props.toggle}>
<ModalHeader toggle={this.props.toggle}>
Edit Artefact
</ModalHeader>
<ModalBody>
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label>Artefact</Label>
<Input
type="text"
name="name"
id="artefactName"
defaultValue={artefact.name}
onChange={this.props.onChange}
/>
<Label>Image</Label>
<Input
type="text"
name="img"
id="artefactImg"
defaultValue={artefact.img}
onChange={this.props.onChange}
/>
<Button
className="modal-submit-button"
color="dark"
style={{ marginTop: "2rem" }}
block
>
Submit
</Button>
</FormGroup>
</Form>
</ModalBody>
</Modal>
</div>
);
}
}
export default UpdateArtefact;
So basically I just want to know what the reason if for why the modal content is still being picked up by enzyme and how to fix this. I've tried searching all over but couldn't find an answer so I'm guessing there's something obvious that I'm missing.
See, your components does not use conditional rendering like
{someFlag && <SomeElement>}
but just pass down isOpen prop:
<Modal isOpen={this.props.modal} toggle={this.props.toggle}>
so probably Modal just hides its props.children and input is kept.
As a workaround you may validate against ModalComponentYouHaveRendered.props().isOpen instead of checking amount of inputs
You can try using:
wrapper.update()
after closing the modal.
In this way, the wrapper should get updated.

React input onChange lag

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.

Resources