Using react hook forms with dynamically added components - reactjs

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

Related

Display the correct CardContent in it's DialogContent

I'm trying intergrate an option to display the data of a Card it's Dialog, with there being multiple Cards.
Right now I'm getting all the data from Firebase and each record is being displayed in a Card. The problem is that the Dialog will only display the data of the last looped item in the map, not of the Card that I'm trying to display in the Dialog.
How can I get the corresponding data from a Card and put it in it's Dialog? E.g: I open the Dialog of the 4th Card and only the data of the 4th Card gets displayed
The code:
useEffect(() => {
const getEvents = async () => {
const data = await getDocs(eventsRef);
data.forEach((doc) => {
setEventList(data.docs.map((doc) => ({...doc.data(), id: doc.id})));
})
};
getEvents();
}, [])
return (
<>
<div className="stepper-title" style={{textAlign: "center"}}>
<b><h1>Aankomende afspraken</h1></b><br></br><p>Prik een afspraak die jou intereseerd</p>
</div>
<div className="event-list-container">
{
eventList && eventList.map((item, index) => {
return (
<div key={item.id}>
<Card variant="outlined" sx={{ width: 250 }}>
<CardContent>
<Typography sx={{ fontSize: 14 }} color="text.secondary" gutterBottom>
{item.date}
</Typography>
<Typography variant="h5" component="div">
{item.occasion.title}
</Typography>
<Typography sx={{ mb: 1.5 }} color="text.secondary">
{item.location}
</Typography>
<Typography variant="body2">
{item.occasion.description}
</Typography>
</CardContent>
<CardActions>
card {index}
<Button onClick={handleClickOpen} size="small" itemID={item.id}>bekijken</Button>
<Dialog open={open} onClose={handleClose} PaperProps={{elevation: 1}} hideBackdrop>
<DialogContent>
<TextField
label="Titel"
defaultValue={eventList[index].occasion.title}
fullWidth
variant="standard"
/>
<TextField
label="Description"
defaultValue={eventList[index].occasion.description}
fullWidth
variant="standard"
/>
<TextField
label="Datum"
defaultValue={eventList[index].date}
fullWidth
variant="standard"
/>
<TextField
label="Locatie"
defaultValue={eventList[index].location}
fullWidth
variant="standard"
/>
<TextField
label="Naam"
defaultValue={eventList[index].organizer.name}
fullWidth
variant="standard"
/>
<TextField
label="E-mailadres"
defaultValue={eventList[index].organizer.email}
fullWidth
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>terug</Button>
<Button onClick={handleClose}>Aanpassen</Button>
</DialogActions>
</Dialog>
</CardActions>
</Card>
</div>
)
})
}
</div>
</>
);

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

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.

Fetch API with Axios in React

const key = '618479acc2ef5ba8018516ac'
function UserPost1 () {
const [post, setPost] = useState(['']);
const handleExpandClick = () => {
setExpanded(!expanded);
};
useEffect(() =>{
axios.get('https://dummyapi.io/data/v1/post' , { headers: { 'app-id': key } })
.then(res => {
setPost(res.data.data)
console.log(res)
})
.catch(err =>{
console.log(err)
})
},[]);
return(
<div className="Post-style">
{post.map(post =>(
<Box sx={{ flexGrow: 1 }}>
<Grid container rowSpacing={0} columnSpacing={{ xs: 1, sm: 2, md: 2, lg: 2 }} >
<Grid
container
direction="row"
justifyContent="center">
<Grid item xs={4} sm={12} md={6} lg={4}>
<div className="card-Style">
<Card sx={{ width: 355}} style={{backgroundColor: "aquamarine"}} >
<CardHeader
avatar={
<Avatar
src={post.owner.picture}
/>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={post.owner.firstName + " " + post.owner.lastName}
subheader={post.publishDate}
/>
<CardMedia
component="img"
height="194"
image={post.image}
alt="Paella dish"
backgroundcolor="blue"
/>
<CardContent>
<Typography variant="body2" color="text.secondary">
{post.text}
<br></br>
{post.likes}
<br></br>
{post.tags}
</Typography>
</CardContent>
</Card>
</div>
</Grid>
</Grid>
</Grid>
</Box>
))}
</div>
)
}
export default UserPost1;
When im run this code i cant get the data from API using Axios, it says error cannot read properties of undefined (reading 'picture'). I tried to catch the error but it does not show in console log. How do i solve this problem.
should i make the axios to wait until it gets the data API or make it to false.
What should i do, its my first time with API.
Somehow, the picture property is missing inside the owner object.
Add optional chaining before picture:
<CardHeader
avatar={
<Avatar
src={post.owner?.picture}
/>
}
...
Render your post component after complete async query and set state value. Use condition rendering and change default state value from [''] to null.
If your component has a specific height and width, then create a placeholder with the same values.
To exclude layout jumps during query execution, show a placeholder. Show the placeholder while the request is running, and after the request is complete and the value is set to the state, show the post component. Good luck!
// change default useState value
const [post, setPost] = useState(null);
// add condition rendering
{post ? post.map(post => (
<Box sx={{ flexGrow: 1 }}>
<Grid container rowSpacing={0} columnSpacing={{ xs: 1, sm: 2, md: 2, lg: 2 }} >
<Grid
container
direction="row"
justifyContent="center">
<Grid item xs={4} sm={12} md={6} lg={4}>
<div className="card-Style">
<Card sx={{ width: 355 }} style={{ backgroundColor: "aquamarine" }} >
<CardHeader
avatar={
<Avatar
src={post.owner.picture}
/>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={post.owner.firstName + " " + post.owner.lastName}
subheader={post.publishDate}
/>
<CardMedia
component="img"
height="194"
image={post.image}
alt="Paella dish"
backgroundcolor="blue"
/>
<CardContent>
<Typography variant="body2" color="text.secondary">
{post.text}
<br></br>
{post.likes}
<br></br>
{post.tags}
</Typography>
</CardContent>
</Card>
</div>
</Grid>
</Grid>
</Grid>
</Box>
)) : <div> PLACEHOLDER </div>}

Component Taking space instead of float above the other components when toggled

I want to add a date range component in my existing reactjs material ui project.But problem is its taking space instead of just showing it "above other components" just like the Select component
Here's the example snippet I want to add in my app https://codesandbox.io/s/materialui-daterange-picker-2p3f1?file=/src/App.js:274-385
My actual implementation
const Wow = () => (
<DateRangePicker
open={open}
toggle={toggle}
onChange={(range) => setDateRange(range)}
/>
);
const DateRange = () => (
<>
<Grid
container
spacing={1}
>
<Grid
item
lg={10}
md={12}
xl={9}
xs={12}
>
<Button
onClick={() => setOpen(true)}
>
Show
</Button>
<Wow />
</Grid>
<Grid
item
lg={2}
md={12}
xl={9}
xs={12}
>
<Button
variant="contained"
startIcon={<CheckIcon />}
onClick={handleSubmit}
>
Go
</Button>
</Grid>
</Grid>
</>
);
return (
<>
<Helmet>
<title>Selectors | Clever Clicks</title>
</Helmet>
<Box
sx={{
backgroundColor: 'background.default',
minHeight: '100%',
py: 3
}}
>
<Container maxWidth={false}>
<Grid
container
spacing={1}
>
<Grid
item
lg={4}
md={12}
xl={9}
xs={12}
>
<TextField
id="standard-select-currency-native"
select
label="Market"
onChange={handleMarket}
SelectProps={{
native: true,
}}
size="small"
fullWidth
helperText="Please select "
>
{marketplaces.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</TextField>
</Grid>
<Grid
item
lg={4}
md={12}
xl={9}
xs={12}
>
<TextField
id="standard-select-currency-native"
select
label="Products"
onChange={handleProduct}
SelectProps={{
native: true
}}
size="small"
fullWidth
helperText="Please select products"
inputProps={{ style: { fontSize: 15, overflow: 'visible' } }}
>
<option value="All"> --All-- </option>
{products.map((option) => (
<option key={option.id} value={`${option.title},${option.seller_sku},${option.id}`}>
{option.title}
</option>
))}
</TextField>
</Grid>
<Grid
item
lg={4}
md={12}
xl={9}
xs={12}
>
<DateRange />
</Grid>
</Grid>
</Container>
</Box>
</>
);
};
Its taking space when button is click to show
if you noticed, the below components are "forced" to stretch. What I want is to make the date range calendars "float".
Any help?
By default, the DateRangePicker component isn't going to have absolute position, which is what you need if you'd like to position the modal without taking up DOM space.
A generic example would be something like this:
.datePicker {
position: absolute;
top: 50px;
left: 50vw;
}
<DateRangePicker
className="datePicker"
open={open}
toggle={toggle}
onChange={(range) => setDateRange(range)}
/>
Using this after modifying the top and left properties to your needs may work if you combine it with a few media queries to compensate for different screen sizes.
However, if you want a more dynamic approach, you can take a look at this answer.
NOTE: Keep in mind that if the parent container is position relative, the absolute positioning will be relative to that element.

How to set defaultValue of a radioGroup from a nested Array object in React state?

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

Resources