I have some components with the same set of input fields. User could add them or remove them.
const [sectionItems, setActionItems] = useState<ISectionItems[]>([
{id: Date.now(), number: 1}
])
{sectionItems.map((sectionItem) =>
<SectionItem
key={sectionItem.id}
number={sectionItem.number}
addSectionItem={addSectionItem}
removeSectionItem={removeSectionItem}
isOnlySection={isOnlySection}
/>)}
So the fiels are inside each SectionItem component.
I use react-hook-forms to get the data from the form, but I have no idea how to deal with that in the situation when we have dynamically changing list of components
const SectionItem: FC<SectionItemProps> = (SectionItemProps) => {
const addClickHandler = (e: React.MouseEvent) => {
SectionItemProps.addSectionItem()
}
const removeClickHandler = (e: React.MouseEvent) => {
SectionItemProps.removeSectionItem(SectionItemProps.number)
}
return (
<React.Fragment>
<Typography variant="h6" gutterBottom sx={{marginTop: 3}}>
{`Участок № ${SectionItemProps.number}`}
</Typography>
<Grid container spacing={3} sx={{marginTop: 1}}>
<Grid item xs={12} sm={6}>
<TextField
required
id="insideDiameter"
name="insideDiameter"
fullWidth
variant="standard"
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
required
id="outsideDiameter"
name="outsideDiameter"
fullWidth
variant="standard"
/>
</Grid>
<Grid container spacing={3} sx={{marginTop: 1}}>
<Grid item xs={12} sm={12} style={{textAlign: "center"}}>
<Fab size="small" aria-label="add" >
<AddIcon color = 'primary' onClick={addClickHandler}/>
</Fab>
{SectionItemProps.isOnlySection
? <></>
: <Fab size="small" aria-label="add" sx={{marginLeft: 1}}>
<RemoveIcon color='primary' onClick={removeClickHandler} />
</Fab>
}
</Grid>
</Grid>
</React.Fragment>
);
I am using MUI Accordion. In the Accordion details I have multiline textfield component to capture user responses and a button at the bottom for saving the data. Now if the user is keying in multiple lines, the button moves down and then disappears. Accordion details is not adjusting the height dynamically with the increase in the height for textfield component. If the user collapse and expand the accordion again the height readjusts.
How can I adjust the height of the accordion details dynamically as the text field contents grows ?
<AccordionDetails key={"SubCatIDDetails_" + this.props.SubCategoryID} >
{quesSubCategories.map((quesSubCat) => (
<>
<Grid container spacing={2} marginTop={1} marginBottom={1} textAlign={'Left'}>
<Grid item xs={3}>
<Typography> {quesSubCat.question}
{quesSubCat.hint.length > 0 && <HtmlTooltip
title={<Typography color="inherit"><i>{quesSubCat.hint}</i></Typography>}>
<IconButton>
<InfoIcon color="primary" />
</IconButton>
</HtmlTooltip>}
</Typography>
</Grid>
<Grid item xs={3.5}>
<TextField multiline fullWidth label="Response" name="Response" defaultValue={quesSubCat.response || ''} onBlur={(event) => this.props.handler([quesSubCat.quesID, quesSubCat.responseID, event.target.value, event.target.name])} />
</Grid>
<Grid item xs={3}>
<TextField multiline fullWidth label="Notes" name="Notes" onBlur={(event) => this.props.handler([quesSubCat.quesID, quesSubCat.responseID, event.target.value, event.target.name])} />
<NotesDetails Updated={this.props.UpdatingResponse} NoteType="Question" QuestionID={quesSubCat.quesID} ApplicationID={this.props.ApplicationID} userEmail={this.props.userEmail} />
</Grid>
<Grid item xs={2.5}>
<Status currentStatus={quesSubCat.quesStatus} QuesCat={quesSubCat.quesID} ResponseID={quesSubCat.responseID} currentTargetDt={quesSubCat.targetDate} handlerChange={this.HandleStatusChange} />
</Grid>
</Grid>
<Divider style={{ background: '#c4540c' }} />
</>
))}
{this.props.UpdatingResponse && <p style={{ marginLeft: '15px' }}><em>Updating...</em></p>}
<Button style={{ marginTop: '10px', marginBottom: '10px', marginLeft: '10px' }} color="warning" variant="contained" startIcon={<SaveIcon />} onClick={this.props.handlerClick}>Save & Continue</Button>
</AccordionDetails>
I have a project and this project is in order to run a contracting and construction company, and I have a file, which is the information for each of the company’s invoices, and this file is a set of fields, but I had this error and I did not know the cause or how can I solve it?
index.js:1 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. More info:
https://reactjs.org/link/controlled-components
This file displays a set of fields
import { getInvoice } from "../../store/invoiceSlice";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import Grid from "#material-ui/core/Grid";
import TextField from "#material-ui/core/TextField";
import moment from "moment";
import { useTheme } from "#material-ui/core/styles";
import InputAdornment from "#material-ui/core/InputAdornment";
import TodayIcon from "#material-ui/icons/Today";
import { makeStyles } from "#material-ui/core/styles";
import { PDFViewer } from "#react-pdf/renderer";
import { Document, Page } from "react-pdf";
const useStyles = makeStyles((theme) => ({
root: {
"& > *": {
margin: theme.spacing(1),
},
},
input: {
display: "none",
},
button: {
margin: theme.spacing(1),
// padding: theme.spacing(4),
},
}));
const InvoiceDetails = () => {
const classes = useStyles();
const theme = useTheme();
const breakpoint = theme.breakpoints.down("sm");
const routeParams = useParams();
const [invoice, setInvoice] = useState([]);
// const defaultLayoutPluginInstance = defaultLayoutPlugin();
useEffect(() => {
getInvoice(routeParams).then((response) => {
setInvoice(response);
});
}, []);
console.log("invoice url: ", invoice?.file?.url);
console.log("invoice tara : ", invoice);
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
const onDocumentLoadSuccess = ({ numPages }) => {
setNumPages(numPages);
};
return (
<>
<Grid container>
<Grid item xs={7} sm={7} style={{ height: "100vh", width: "100vh" }}>
{/* <PDFViewer file={invoice?.file?.url}></PDFViewer> */}
<Document
file={invoice?.file?.url}
onLoadSuccess={onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber} />
</Document>
<p>
Page {pageNumber} of {numPages}
</p>
</Grid>
<Grid item xs={5} sm={5} style={{ padding: "3rem" }}>
<Grid item>
<h1 style={{ fontWeight: "bold" }}>Invoice Details</h1>
</Grid>
<Grid item style={{ marginTop: "3rem", marginBottom: "2rem" }}>
<Grid item style={{ marginBottom: 10 }}>
<h3>From</h3>
</Grid>
<Grid item>
<h3>{invoice?.submittedBy?.name}</h3>
</Grid>
<Grid item>
<h3>{invoice?.submittedBy?.email}</h3>
</Grid>
</Grid>
<Grid item>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Invoice ID</h3>
</Grid>
<Grid item xs={9} sm={9}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.id}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Issue Date</h3>
</Grid>
<Grid item xs={9} sm={9}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={moment(moment.utc(invoice.issueDate).toDate())
.local()
.format("YYYY-MM-DD HH:mm:ss")}
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<TodayIcon />
</InputAdornment>
),
}}
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Due Date</h3>
</Grid>
<Grid item xs={9} sm={9}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={moment(moment.utc(invoice.dueDate).toDate())
.local()
.format("YYYY-MM-DD HH:mm:ss")}
variant="outlined"
InputProps={{
endAdornment: (
<InputAdornment position="start">
<TodayIcon />
</InputAdornment>
),
}}
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Net Amount</h3>
</Grid>
<Grid item xs={9} sm={9}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.netAmount}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Tax Number</h3>
</Grid>
<Grid item xs={9} sm={9}>
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.taxNumber}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
<Grid container item direction={breakpoint ? "row" : "column"}>
<Grid
container
item
xs={3}
sm={3}
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<h3>Gross Amount</h3>
</Grid>
<Grid item xs={9} sm={9}>
<TextField
className="mt-8 mb-16"
// label="Size"
id="outlined-size-normal"
value={invoice.grossAmount}
variant="outlined"
fullWidth
/>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</>
);
};
export default InvoiceDetails;
If you set as undefined the value prop of your components they become uncontrolled. This is an example:
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.id} // invoice.id is initially undefined
variant="outlined"
fullWidth
/>
Then, once you run the setInvoice() to define those values the components become controlled.
What you can do to make them always controlled is to set a proper initial value of the state like this:
const [invoice, setInvoice] = useState({ // Note that the initial value is an object and not an array
id: "",
issueDate: null,
netAmount: 0,
taxNumber: 0,
grossAmount: 0
});
Or alternatively you can do this to each of your components:
<TextField
className="mt-8 mb-16"
id="outlined-size-normal"
value={invoice.id || ""}
variant="outlined"
fullWidth
/>
I believe it's caused by your invoice starting as []. Therefore, fields like invoice.id will be initially null and when u finally fetch data from API & set data into invoice, invoice.id became not null, hence the statement: This is likely caused by the value changing from undefined to a defined value
To solve the warning, you might have to declare all the properties of the invoice in useState. Eg:
useState({
id: "",
issueDate: "",
dueDate: "",
})
or perhaps, use defaultValue instead of value for the TextFields if u don't intend to control the inputs. What's the difference? with value, you have to supply onChange.
I am new to reactjs. I need to check whether if records are present, then I need to show grid using map function. Else I need to show message "No data". This is my code. I used ternary operator to do so
<Grid container spacing={2}>
{data.length > 0 ?
{data.map((activity) => (
<Grid
item
xs={6}
sm={4}
md={3}
lg={3}
onClick={() => {
setSpec(activity.spec)
setActivity(activity)
handleClickOpen(activity.spec)
}}
className={classes.thumbMain}
>
<ButtonBase focusRipple className={classes.fullwidthBtn}>
<Card className={classes.manage}>
<Box mt={2} mb={1}>
<Box
className={classes.mainIcons}
></Box>
</Box>
<Typography className={classes.cardlabel}>{(activity.name)}</Typography>
</Card>
</ButtonBase>
</Grid>
))}
: <p>No data</p>
}
</Grid>
The above code returns following error
Parsing error: ',' expected : in data.map((activity)
You shouldn't wrap data.map(...) in braces:
<Grid container spacing={2}>
{data.length ? (
data.map((activity) => (
<Grid
item
xs={6}
sm={4}
md={3}
lg={3}
onClick={() => {
setSpec(activity.spec);
setActivity(activity);
handleClickOpen(activity.spec);
}}
className={classes.thumbMain}
>
<ButtonBase focusRipple className={classes.fullwidthBtn}>
<Card className={classes.manage}>
<Box mt={2} mb={1}>
<Box className={classes.mainIcons}></Box>
</Box>
<Typography className={classes.cardlabel}>
{activity.name}
</Typography>
</Card>
</ButtonBase>
</Grid>
))
) : (
<p>No data</p>
)}
</Grid>;
You should wrap JS code in braces only one time to use it in JSX. If you add braces in other braces then you will be getting that error again.
I have this array Object in State called formRating
formRating:Array[22]
0:
{…}
condId: "C2.1(a)"
rate: "N/A"
1:
{…}
condId:"C2.2(b)"
rate:"3"
2:
{…}
3:
{…}
I also have a few RadioGroups in the render letting the user manipulate the state object above.
<Grid item xs={7} style={{marginTop:32}}> Condition 1</Grid> <Grid item ><RadioGroup name="C2.1(a)" defaultValue={this.getDefaultValue('C2.1(a)')} onChange={this.changeButton("C2.1(a)")} style={{display: 'flex', flexDirection: 'row'}}>
What would the getDefaultValue method be to change the rate of an element matching with the given condId with the defaultValue={this.getDefaultValue("C2.1(a)")}?
With #ChristopherNgo inspiration, following worked.
In the render;
<Grid item xs={12} style={{marginTop:20}}>{this.createRadioGroups(values.formRating)}</Grid>
and createRadioGroups method as follows;
createRadioGroups = (ratingState)=>{
return(ratingState.map(
item =>
<Grid container>
<Grid item xs={2} style={{marginTop:20, marginRight:0}}>{item.condId} </Grid>
<Grid item xs={6} style={{marginTop:20}}>{item.condition} </Grid>
<Grid item xs={4} style={{marginTop:10}}>
<RadioGroup defaultValue={item.rate} name={item.condId} onChange={this.changeButton(item.condId)} style={{display: 'flex', flexDirection: 'row'}}>
<FormControlLabel value="1" control={<Radio color="primary" />} label='' labelPlacement="top"/>
<FormControlLabel value="2" control={<Radio color="primary" />}label='' labelPlacement="top"/>
<FormControlLabel value="3" control={<Radio color="primary" />}label='' labelPlacement="top"/>
<FormControlLabel value="N/A" control={<Radio color="primary" />}label='' labelPlacement="top"/>
</RadioGroup>
</Grid>
</Grid>
))
}
;
It will make sense to define the defaultValue of these RadioGroups by using a .map() to iterate over the list of formRatings. Then you can pass in the rate seamlessly instead of passing a hard-coded string to a function.
Try something like:
createRadioGroups = () => {
const { formRating } = this.state;
return formRating.map(item => {
return (
<div>
<label>{item.condId}</label>
<Grid item>
<RadioGroup
defaultValue={item.rate}
value={item.rate}
name={item.condId}
onChange={() => this.changeButton(item.condId)}
/>
</Grid>
</div>
);
});
};
Then in your render you can use createRadioGroups()
render() {
return (
<div>
<Grid item xs={7} style={{ marginTop: 32 }}>
Condition 1
</Grid>
{this.createRadioGroups()}
</div>
);
}