Warning: A component is changing an uncontrolled input to be controlled - reactjs

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.

Related

Required property from React hook form does not validate on textfields

I'm currently using React-hook-form to manage my textfields and that includes validation.
The problem right now is that the required property does not work well, because it does not show the little helpertext under the inputfield when it is empty.
Here is there a snippet of the UI, it only shows a few of the inputfields to save you the
unnecessary JSX code
This is how i have done it:
import { FragmentType, useFragment } from '#gql/fragment-masking';
import { graphql } from '#gql/gql';
import { Gender } from '#gql/graphql';
import {
Button,
Card,
CardContent,
Grid,
MenuItem,
TextField,
TextFieldProps,
Typography,
} from '#mui/material';
import { AdapterDayjs } from '#mui/x-date-pickers/AdapterDayjs';
import { DesktopDatePicker } from '#mui/x-date-pickers/DesktopDatePicker';
import { LocalizationProvider } from '#mui/x-date-pickers/LocalizationProvider';
import dayjs, { Dayjs } from 'dayjs';
import { useTranslation } from 'next-i18next';
import React, { useCallback, useState } from 'react';
import { useForm } from 'react-hook-form';
type Inputs = {
firstName: string;
lastName: string;
birthdate: string;
street: string;
postalcode: string;
city: string;
country: string;
gender: string;
email: string;
leveloftrust: string;
lastsignedin: string;
};
export const PatientInfoFragment = graphql(/* GraphQL */ `
fragment PatientInfo on Patient {
addresses {
city
lines
postalCode
}
birthDate
email
gender
id
name {
firstName
lastName
}
status {
lastSignInAt
levelOfTrust
}
}
`);
interface PatientInfoProps {
patient: FragmentType<typeof PatientInfoFragment>;
}
export function PatientInfo(props: PatientInfoProps) {
const patient = useFragment(PatientInfoFragment, props.patient);
const [value, setValue] = React.useState<Dayjs | null>(
dayjs(patient.birthDate)
);
const genderValueArray = Object.values(Gender);
const [disabled, setDisabled] = useState(true);
const { register, handleSubmit } = useForm<Inputs>({
defaultValues: {
firstName: patient.name?.firstName ?? '',
lastName: patient.name?.lastName ?? '',
birthdate: patient.birthDate ?? '',
country: '',
gender: patient.gender,
email: patient.email ?? '',
leveloftrust: patient.status?.levelOfTrust,
lastsignedin: patient.status?.lastSignInAt,
postalcode: patient.addresses[0]?.postalCode ?? '',
city: patient.addresses[0]?.city ?? '',
street: patient.addresses[0]?.lines[0] ?? '',
},
shouldUseNativeValidation: true,
});
const handleChange = React.useCallback(
(newValue: Dayjs | null) => setValue(newValue),
[]
);
const { t } = useTranslation();
const handleEditClick = useCallback(() => setDisabled(!disabled), [disabled]);
const renderInputField = React.useCallback(
(params: JSX.IntrinsicAttributes & TextFieldProps) => {
return <TextField {...params} />;
},
[]
);
const onSubmit = (data: any) => console.log(data);
return (
<Card>
<CardContent>
<form onSubmit={handleSubmit(onSubmit)}>
<Grid container direction="row" justifyContent="space-between">
<Grid item>
<Typography gutterBottom variant="h5" component="div">
{t('patient.info.title', 'Personal Information')}
</Typography>
</Grid>
<Grid item>
<Grid container justifyContent="space-between" sx={{ m: 1 }}>
{!disabled && (
<Button
onClick={handleEditClick}
size="large"
variant="outlined">
Cancel
</Button>
)}
<Button
onClick={handleEditClick}
size="large"
type="submit"
variant="contained">
{!disabled
? t('patient.setToComplete', 'Set to complete')
: t('patient.edit', 'Edit')}
</Button>
</Grid>
</Grid>
</Grid>
<Grid container direction="row">
<Grid
container
sx={{ margin: 1 }}
rowSpacing={3}
spacing={{ md: 4 }}>
<Grid item>
<TextField
{...register('firstName', { required: 'Field needs to be filled out' })}
label="Name"
name="firstName"
id="component-outlined"
disabled={disabled}
/>
</Grid>
<Grid item>
<TextField
{...register('lastName', {
required: 'Field needs to be filled out',
})}
label="lastname"
name="lastName"
id="component-outlined"
disabled={disabled}
/>
</Grid>
<Grid item sx={{ width: '274.67px' }}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DesktopDatePicker
{...register('birthdate', {
required: 'Field needs to be filled out',
})}
label="Birthdate"
inputFormat="MM/DD/YYYY"
value={value}
onChange={handleChange}
renderInput={renderInputField}
disabled={disabled}
/>
</LocalizationProvider>
</Grid>
</Grid>
<Grid
container
sx={{ margin: 1 }}
rowSpacing={3}
spacing={{ md: 4 }}>
<Grid item>
<TextField
{...register('street')}
id="component-outlined"
label="Street"
name="street"
disabled={disabled}
/>
</Grid>
<Grid item>
<TextField
{...register('postalcode')}
id="component-outlined"
label="Postal code"
name="postalCode"
disabled={disabled}
/>
</Grid>
<Grid item>
<TextField
{...register('city')}
id="component-outlined"
label="City"
name="city"
disabled={disabled}
/>
</Grid>
</Grid>
<Grid
container
sx={{ margin: 1 }}
rowSpacing={3}
spacing={{ md: 4 }}>
<Grid item>
<TextField
{...register('country')}
id="component-outlined"
label="Country"
name="country"
disabled={disabled}
/>
</Grid>
<Grid item>
<TextField
sx={{ width: '242.67px' }}
id="outlined-select-gender"
select
{...register('gender')}
label="Gender"
disabled={disabled}
value={patient.gender}>
{genderValueArray.map(option => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</TextField>
</Grid>
<Grid item>
<TextField
{...register('email')}
id="component-outlined"
label="Email"
name="email"
disabled
/>
</Grid>
</Grid>
<Grid
container
sx={{ margin: 1 }}
rowSpacing={3}
spacing={{ md: 4 }}>
<Grid item>
<TextField
{...register('leveloftrust')}
id="component-outlined"
label="Level of trust"
name="leveloftrust"
disabled
/>
</Grid>
<Grid item>
<TextField
{...register('lastsignedin')}
id="component-outlined"
label="Last signed in"
name="lastsignedin"
disabled
/>
</Grid>
</Grid>
</Grid>
</form>
</CardContent>
</Card>
);
}
If you're using React Hook Form with MUI Textfield, you'll need to use the Controller component wrapper from react-hook-form for your MUI Textfield components.
View Integrating with UI Libraries
For example, you would grab the Input component from MUI's Core library instead:
import Input from "#material-ui/core/Input";
Then in your form you'd wrap it using Controller like:
<Controller
name="firstName"
control={control}
render={({ field }) => <Input {...field} />}
/>
Then MUI Input has has error boolean and helperText string as available properties on the Input component. For example using Formik (but any form library would work with MUI):
<TextField
id="password"
name="password"
label="Password"
type="password"
value={formik.values.password}
onChange={formik.handleChange}
error={!!formik.errors.password && formik.touched.password}
helperText={formik.touched.password && formik.errors.password}
required
/>
Otherwise, reviewing their documentation on how to Handle Errors, it appears with no library they have you handle error output manually using the errors object from formState. You'd destructure that data like:
const { register, formState: { errors }, handleSubmit } = useForm();
Finally, they link a very handy CodeSandbox which uses a #hookform/error-message library that can wrap regular HTML inputs. If you want to stick with MUI, I'd go with the helperText and error properties.

Using react hook forms with dynamically added components

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>
);

Different color in a custom component React

I created a custom component called InfoCard, I passed trough props the information, the component is working properly, the only issue I have is that I want to define a diferente color for the Icon prop element when I call it into other components
For ex: I need to be red to be green
Is there any a possibility to do it without using CSS classes ?
InfoCard Component
interface Props {
icon: JSX.Element
title: string
store?: string
price: string
divider?: JSX.Element
}
const Item = styled(Paper)(({ theme }) => ({
padding: theme.spacing(1),
margin: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}))
const InfoCard = ({ icon, title, store, price, divider }: Props) => {
return (
<>
<Item>
<Stack direction="row" spacing={2} sx={{ justifyContent: 'center' }}>
<Box>{icon}</Box>
<Typography>{title}</Typography>
<Box>{divider}</Box>
<Typography>{store}</Typography>
</Stack>
<Typography variant="h5" sx={{ color: 'primary.main' }}>
{price}
</Typography>
</Item>
</>
)
}
export default InfoCard
Header Component
const Header = () => {
const { t } = useTranslation()
return (
<Box>
<Grid container>
<Grid item xs={12} sm={12} md={6} lg={4}>
<InfoCard
icon={<ArrowDownIcon className="smallIcon" />}
title={t('LOWEST_REGULAR_PRICE')}
store="store 1"
price="1.900"
divider={<Divider orientation="vertical" />}
/>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={4}>
<InfoCard
icon={<ArrowTrendingUpIcon className="smallIcon" />}
title={t('AVERAGE_REGULAR_PRICE')}
price="1.900"
/>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={4}>
<InfoCard
icon={<ArrowPathIcon className="smallIcon" />}
title={t('LAST_UPDATE')}
price="19/07/2022"
/>
</Grid>
</Grid>
</Box>
)
}
```
You can style a react element with the style prop as follows:
<MyComponent style={{color: "red"}} />
https://reactjs.org/docs/faq-styling.html
Hint: careful that passing rendered react components as props like you do in your code example, is usually not desired. icon={<ArrowTrendingUpIcon className="smallIcon" />} You may want to consider using props.children instead. https://reactjs.org/docs/react-api.html#reactchildren

how to preview the selected image upload React js

Have created a button called image upload, need to view the selected image when selected
<GridItem xs={6}>
<FormControl className={classes.formControl}>
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
/>
<label htmlFor="raised-button-file">
<Button
variant="raised"
component="span"
className={classes.button}
>
Upload Image
</Button>
</label>
</FormControl>
</GridItem>
you can set the file object URL in the state during the onChange and use it in an img tag like this,
const [file, setFile] = useState(undefined);
const handleChange = (event) => {
setFile(URL.createObjectURL(event.target.files[0]));
}
use this handleChange method in input onChange event,
<GridItem xs={6}>
<FormControl className={classes.formControl}>
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
onChange={handleChange}
/>
{/* preview of file */}
{ file && <img src={this.state.file}/>}
<label htmlFor="raised-button-file">
<Button
variant="raised"
component="span"
className={classes.button}
>
Upload Image
</Button>
</label>
</FormControl>
</GridItem>
also if you want to allow the user to crop the image selected there is this library react-image-crop. I use it and it works pretty good for me.
before select
select image
Typescript
import { Grid, IconButton, makeStyles, Tooltip } from '#material-ui/core'
import { PhotoCamera, Publish } from "#material-ui/icons"
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { IRequestState } from '../../assets/interfaces'
const FILE = 'ImageUpload.tsx'
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
},
}, right: {
float: "right"
}, w100: {
width: "100%"
}, input: {
display: "none",
},
faceImage: {
color: theme.palette.primary.light,
}, success: {
color: theme.palette.success.main
}
}))
interface IImageUploadProps {
Submit(event: React.MouseEvent<HTMLButtonElement>): void
}
type IImageUploadState = IRequestState & {
imageFile?: any
}
export default function ImageUpload(props: IImageUploadProps) {
const classes = useStyles()
const dispatch = useDispatch()
const initState: IImageUploadState = { inited: false, requesting: false }
const [values, setState] = useState<IImageUploadState>(initState)
const handleCapture = ({ target }: any) => {
if (target && target.files && target.files.length)
setState({ ...values, imageFile: target.files[0] })
}
return (
<>
<Grid
container
direction="row"
justify="space-evenly"
alignItems="center"
spacing={2}
>
<Grid item sm={6} md={3}>
<Grid
justify="center"
alignItems="center"
container
>
<Grid item>
<input
accept="image/jpeg"
className={classes.input}
id="faceImage"
type="file"
onChange={handleCapture}
/>
<Tooltip title="select image">
<label htmlFor="faceImage">
<IconButton
className={classes.faceImage}
color="primary"
aria-label="upload picture"
component="span"
>
<PhotoCamera fontSize="large" />
</IconButton>
</label>
</Tooltip>
</Grid>
</Grid>
</Grid>
<Grid item sm={6} md={3}>
<img className={classes.w100} hidden={!values.imageFile} src={values.imageFile && URL.createObjectURL(values.imageFile)} alt="Logo" />
</Grid>
<Grid item sm={6} md={3}>
<Grid
justify="center"
alignItems="center"
container
>
<Grid item>
<Tooltip title="upload image">
<IconButton
className={classes.success}
aria-label="upload"
onClick={props.Submit}
edge="end"
disabled={!values.imageFile}
>
<Publish />
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Grid>
</Grid>
</>
)
}

Material UI Grid - Textfield is being resized once an item is selected in Select element

I am using Material UI to build a simple UI. Textfield size (set to fullWidth) is getting smaller when I select one from Select element.
I reduced the code to minimum which can reproduce the weird action.
function ImportDialog(props) {
const [state, setState] = useState({
id: "",
color: ""
});
const handleChange = name => event => {
setState({ ...state, [name]: event.target.value });
};
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
useEffect(() => {
setLabelWidth(inputLabel.current.offsetWidth);
}, []);
return (
<div style={{ width: "40vw", height: "50vh", margin: "25vh auto" }}>
<Grid container spacing={2}>
<Grid item md={6}>
<Grid container direction="column">
<Grid item>
<TextField
label="ID"
margin="dense"
variant="outlined"
fullWidth
value={state.id}
onChange={handleChange("id")}
/>
</Grid>
<Grid item>
<FormControl
variant="outlined"
margin="dense"
fullWidth
>
<InputLabel ref={inputLabel} htmlFor="outlined-age-simple">
Color
</InputLabel>
<Select
value={state.color}
onChange={handleChange("color")}
labelWidth={labelWidth}
inputProps={{
name: "color",
id: "outlined-age-simple"
}}
>
<MenuItem value="">
<em>None</em>
</MenuItem>
<MenuItem value={1}>Sup1</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
</Grid>
</Grid>
</div>
);
}
export default withStyles(styles)(ImportDialog);
When I move focus to textfield after choosing one from the Select element, it gets smaller.
Please try the codesandbox in bigger window size.
https://codesandbox.io/s/material-demo-59siu
This is the url of codesandbox.

Resources