I am trying to create dynamic input fields in my react application(using Material UI) on the click of a button. Whenever the add button is clicked, it creates a new input field inside a dialog box but I can't seem to enter any values in the text-field. Following is my app.js file -
import React from "react";
import "./styles.css";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Dialog from "#material-ui/core/Dialog";
import DialogActions from "#material-ui/core/DialogActions";
import DialogContent from "#material-ui/core/DialogContent";
import DialogContentText from "#material-ui/core/DialogContentText";
import DialogTitle from "#material-ui/core/DialogTitle";
import IconButton from "#material-ui/core/IconButton";
import DeleteIcon from "#material-ui/icons/Delete";
import { Box, Grid } from "#material-ui/core";
export default function App() {
const [open, setOpen] = React.useState(false);
const [values, setValues] = React.useState([]);
const [text, setText] = React.useState("");
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
setValues([]);
};
const handleChangeText = (e) => {
setText(e.target.value);
};
const addValue = () => {
setValues([...values, ""]);
};
const handleValueChange = (index, e) => {
values[index] = e.target.value;
console.log(values);
setValues(values);
};
const deleteValue = (jump) => {
setValues(values.filter((j) => j !== jump));
};
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Create
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">New Dialog</DialogTitle>
<DialogContent>
<DialogContentText>Sample Text.</DialogContentText>
<TextField
autoFocus
margin="dense"
value={text}
onChange={handleChangeText}
label="Text"
fullWidth
/>
{values.map((jump, index) => (
<Box key={"jump" + index}>
<Grid container spacing={1} alignItems="flex-end">
<Grid item xs={10}>
<TextField
autoFocus
margin="dense"
label="Value"
value={jump || ""}
onChange={(e) => handleValueChange(index, e)}
fullWidth
/>
</Grid>
<Grid item xs={2}>
<div
className="font-icon-wrapper"
onClick={() => deleteValue(jump)}
>
<IconButton aria-label="delete">
<DeleteIcon />
</IconButton>
</div>
</Grid>
</Grid>
</Box>
))}
</DialogContent>
<Button onClick={addValue} color="primary">
Add
</Button>
<DialogActions>
<Button onClick={handleClose} variant="contained" color="secondary">
Cancel
</Button>
<Button onClick={handleClose} variant="contained" color="primary">
Create
</Button>
</DialogActions>
</Dialog>
</div>
);
}
Here is the code sandbox link.
I have a simple text field inside the dialog box also that works perfectly. The problem is with the dynamic fields. I don't know what mistake I am making in this code. Someone please help me out. Thanks in advance
You a have problem in the function handleValueChanges, don't use mutation use immutable data.
the correct code:
const handleValueChange = (index, e) => {
const updatedValues = values.map((value, i) => {
if(i === index) {
return e.target.value
}else {
return value
}
})
setValues(updatedValues);
};
You can test from here: https://codesandbox.io/s/dynamic-textfield-forked-gck25?file=/src/App.js
Related
wanted to add a new element in a table with Materials UI,using a component that adds a new row.The component takes data from user and the idea is to send this data to the Table component.How can I transfer this data from DialogBoxAddPost to Table component and how to mix the data from the API and the new user added data.
Thank you!
Table component:
import * as React from 'react';
import { styled } from '#mui/material/styles';
import Table from '#mui/material/Table';
import TableBody from '#mui/material/TableBody';
import TableCell, { tableCellClasses } from '#mui/material/TableCell';
import TableContainer from '#mui/material/TableContainer';
import TableHead from '#mui/material/TableHead';
import TableRow from '#mui/material/TableRow';
import Paper from '#mui/material/Paper';
import { Button } from '#mui/material';
import './App.css';
import { useEffect, useState } from "react";
import DialogBoxEdit from './DialogBoxEdit';
export default function Tableall() {
const [posts, setPosts] = useState([])
const fetchData = () => {
fetch("https://jsonplaceholder.typicode.com/posts").then(response => {
return response.json()
})
.then((data) => {
setPosts(data.slice(0, 6));
});
};
useEffect(() => {
fetchData()
}, []);
const handleDelete = (postIndex) => {
setPosts((prevPosts) =>
prevPosts.filter((_, index) => index !== postIndex)
);
};
return (
<TableContainer sx={{
marginLeft:'auto',
width:'max-content',
marginRight:'auto',
}}component={Paper}>
<Table sx={{ minWidth: 700,tableLayout:'auto' }} aria-label="customized table">
<TableHead>
<TableRow>
<StyledTableCell sx={{width:250}}>Title</StyledTableCell>
<StyledTableCell align="center" sx={{width:500}}>Description</StyledTableCell>
<StyledTableCell align="center" sx={{width:100}}></StyledTableCell>
<StyledTableCell align="center"sx={{width:100}}></StyledTableCell>
</TableRow>
</TableHead>
<TableBody>
{posts.map((post,postIndex) => (
<StyledTableRow key={post.id}>
<StyledTableCell component="th" scope="row">{post.title}</StyledTableCell>
<StyledTableCell align="center">{post.body}</StyledTableCell>
<StyledTableCell align="center"><DialogBoxEdit dataParent1={post.title} dataParent2={post.body} /></StyledTableCell>
<StyledTableCell align="center"><Button variant="outlined" color="error" onClick={() => handleDelete(postIndex)}>Delete</Button></StyledTableCell>
</StyledTableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
Dialogbox to add a new elements in a table.
import * as React from 'react';
import Button from '#mui/material/Button';
import Dialog from '#mui/material/Dialog';
import DialogActions from '#mui/material/DialogActions';
import DialogContent from '#mui/material/DialogContent';
import DialogContentText from '#mui/material/DialogContentText';
import DialogTitle from '#mui/material/DialogTitle';
import useMediaQuery from '#mui/material/useMediaQuery';
import { useTheme } from '#mui/material/styles';
import Table from './TableAll';
import TextField from '#mui/material/TextField';
import Divider from '#mui/material/Divider';
import {useState} from 'react';
export default function ResponsiveDialog() {
const [open, setOpen] = React.useState(false);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const [message, setMessage] = useState('');
const [message2, setMessage2] = useState('');
const handleChange = event => {
setMessage(event.target.value);
};
const handleChange2 = event => {
setMessage2(event.target.value);
};
const handleClick = event => {
event.preventDefault();
console.log(message,message2);
setOpen(false);
};
return (
<div>
<Button variant="contained" sx={{right:477,top:-10,height:48}} color='warning' size='large' onClick={handleClickOpen}>
Add new +
</Button>
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
aria-labelledby="responsive-dialog-title"
fullWidth
maxWidth="sm"
>
<DialogTitle id="responsive-dialog-title">{"Add new post"}
</DialogTitle>
<DialogContent>
<Divider variant="middle" />
<DialogContentText>
Title
</DialogContentText>
<TextField id="outlined-basic" onChange={handleChange} value={message} name="message" label="Title" variant="outlined" style = {{width: 500}} />
<DialogContentText>
Description
</DialogContentText>
<TextField id="outlined-basic" onChange={handleChange2} value={message2} multiline rows={4} label="Description" variant="outlined" style = {{width: 500}} />
</DialogContent>
<DialogActions>
<Button autoFocus variant="outlined" color='error' onClick={handleClose}>
Exit
</Button>
<Button color='success' variant="outlined" onClick={handleClick} autoFocus>
Add
</Button>
</DialogActions>
</Dialog>
</div>
);
}
I am trying to say on submit to hide the dialog box and show my cat images, very new to react and i have gotten this far already and this is the last function i need to set in my app to add the other smaller details like validation etc....
I am having problems getting this change to happen,is there any refactoring i could do as well?
import React from 'react';
import ImageGrid from './ImageGrid';
import '../index.css'
// Material UI
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogTitle from '#material-ui/core/DialogTitle';
import Input from '#material-ui/core/Input'
import Dialog from '#material-ui/core/Dialog';
import Button from '#material-ui/core/Button';
import Grid from '#material-ui/core/Grid';
const DialogBox = () => {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
// function to hide the dialog box and show the ImageGrid
function showCats() {
// Get the modal
const startPage = document.getElementsByClassName('modal');
// get the image elements
const catImages = document.getElementsByTagName(ImageGrid);
// target the submit button
const button = document.getElementsByClassName('toggle');
if (Input === '') {
alert('Please enter a valid search')
button.current.disabled = true;
} else if (Input === String) {
startPage.style.display = 'none';
catImages.style.display = 'show';
} else {
button.style.display = 'disabled'
}
showCats();
}
const handleSubmit = () => {
console.log('i work')
showCats()
}
return (
<div className='modal'>
<Grid container justifyContent='center'>
{/* Button To Open Dialog Box */}
<Button
style={{border: '1px solid #ebc340', color: '#ebc340'}}
variant="outlined"
color="secondary"
onClick={handleClickOpen}>
Welcome to my Case Study, Click to begin
</Button>
</Grid>
{/* Dialog Box Content */}
<Dialog
className='dialog-btn'
open={open}
onClose={handleClose}>
<DialogTitle>
To begin the application, please insert your URL below:
</DialogTitle>
<DialogContent>
<Input
placeholder="Enter Your Link Here"
// inputProps={ariaLabel}
fullWidth/>
</DialogContent>
{/* Dialog Box Actions */}
<DialogActions>
<Button
onClick={handleClose}
color="secondary">
Cancel
</Button>
<Button
className='toggle'
onClick={handleSubmit}
color='primary'
autoFocus
type='submit'>
Submit
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default DialogBox
set open to false inside your handleSubmit function:
import React from 'react';
import ImageGrid from './ImageGrid';
import '../index.css'
// Material UI
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogTitle from '#material-ui/core/DialogTitle';
import Input from '#material-ui/core/Input'
import Dialog from '#material-ui/core/Dialog';
import Button from '#material-ui/core/Button';
import Grid from '#material-ui/core/Grid';
const DialogBox = () => {
const [open, setOpen] = React.useState(false);
const [url, setUrl] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleSubmit = () => {
setOpen(false)
}
return (
<div className='modal'>
<Grid container justifyContent='center'>
{/* Button To Open Dialog Box */}
<Button
style={{border: '1px solid #ebc340', color: '#ebc340'}}
variant="outlined"
color="secondary"
onClick={handleClickOpen}>
Welcome to my Case Study, Click to begin
</Button>
</Grid>
{/* Dialog Box Content */}
<Dialog
className='dialog-btn'
open={open}
onClose={handleClose}>
<DialogTitle>
To begin the application, please insert your URL below:
</DialogTitle>
<DialogContent>
<Input
placeholder="Enter Your Link Here"
fullWidth
onChange={(e)=> setUrl(e.target.value)}
/>
</DialogContent>
{/* Dialog Box Actions */}
<DialogActions>
<Button
onClick={handleClose}
color="secondary">
Cancel
</Button>
<Button
className='toggle'
onClick={handleSubmit}
color='primary'
autoFocus
type='submit'>
Submit
</Button>
</DialogActions>
</Dialog>
{/* Show the image */}
{!open && url.trim()
? <img src={url} alt="cat"/>
: <p>Open the dialog and insert your url to see the image<p/>
}
</div>
);
}
export default DialogBox
i'm using the datetime picker from: https://material-ui.com/components/pickers/ I want the default value to come from props.note.NoteDate but I can't seem to set or even see the default value as Note date has the annoying format string I cannot remove. Any help is appreciated.
here is my react control:
import React from 'react';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
export default function EditNoteDialog(props) {
const [open, setOpen] = React.useState(true);
const [notetext, setNoteText] = React.useState();
const [notedate, setNoteDate] = React.useState();
React.useEffect (() => {
//notedate is a string like 2021-09-01T01:34:00.000Z
setNoteDate(props.note.NoteDate);
setNoteText(props.note.NoteText);
},[props.note.NoteDate, props.note.NoteText])
let handleNoteTextChange = (e) => {
setNoteText(e.currentTarget.value);
};
let handleNoteDateChange = (e) => {
console.log(e.currentTarget.value);
setNoteDate(e.currentTarget.value);
};
const handleClose = () => {
let editednote = {
NoteID: props.note.NoteID,
NoteText: notetext,
NoteType: props.note.NoteType,
NoteDate: notedate
};
//console.log(editednote.NoteDate)
setOpen(false);
props.SaveEditedNote(editednote);
};
const handleCancel = () => {
setOpen(false);
props.handleCancel(0);
};
return (
<div>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Edit Note</DialogTitle>
<DialogContent>
<DialogContentText>
Edit Note
</DialogContentText>
{/* <TextField
autoFocus
margin="dense"
id="noteid"
label="NoteID"
defaultValue = {noteid}
fullWidth
/> */}
<TextField
autoFocus
margin="dense"
id="notetext"
label="NoteText"
defaultValue = {notetext}
fullWidth
onChange = {handleNoteTextChange}
/>
<TextField
autoFocus
type = 'datetime-local'
margin="dense"
id="notedate"
defaultValue = "2021/8/15T03:12:05"
//value = {notedate}
label="Note Date"
fullWidth
onChange = {handleNoteDateChange}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleCancel} color="primary">
Cancel
</Button>
<Button onClick={handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
per briosheje's comment datetime-local's default value doesn't seem to allow for milliseconds. Fortunately I didn't either so I just chopped the milliseconds off.
defaultValue = {props.note.NoteDate.split(".")[0]}
I am trying to trigger the Redirect React Dom
that is my button component in the handleMenuItemClick() function. But nothing happens.
I have tried a bunch of stuff but but still no success.
How can I make the both work together? My best try was to make a function that return the Redirect component as I saw in one post around, but still no success.
My Code:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { Grid, Button, ButtonGroup, ArrowDropDownIcon, ClickAwayListener, Grow, Paper, Popper, MenuItem, MenuList, Link } from '#material-ui/core/Grid';
const SplitButton = (props) => {
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const myGroups = props.myGroups
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
return <Redirect to={`/groups/${index}`} />
};
const handleToggle = () => {
setOpen((prevOpen) => !prevOpen);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) {
return;
}
setOpen(false);
};
return (
<>
<ButtonGroup variant="contained" color="primary" ref={anchorRef} aria-label="split button">
<Button onClick={null}>My Groups</Button>
<Button
color="primary"
size="small"
aria-controls={open ? 'split-button-menu' : undefined}
aria-expanded={open ? 'true' : undefined}
aria-label="select merge strategy"
aria-haspopup="menu"
onClick={handleToggle}
>
<ArrowDropDownIcon />
</Button>
</ButtonGroup>
<Popper open={open} anchorEl={anchorRef.current} role={undefined} transition disablePortal>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: placement === 'bottom' ? 'center top' : 'center bottom',
}}
>
<Paper>
<ClickAwayListener onClickAway={handleClose}>
<MenuList id="split-button-menu">
{ myGroups.map((group) => (
<MenuItem
key={group.id}
onClick={(event) => handleMenuItemClick(event, group.id)}
>
{group.title}
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</>
);
}
export default SplitButton
You can redirect user via 2 methods: useHistory or <Redirect />
useHistory hook
If you want to redirect the user directly on click, you can treat the code imperatively and tell React what to do:
const history = useHistory();
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
history.push(`/groups/${index}`)
};
More info https://reactrouter.com/web/api/Hooks/usehistory
Redirect component
Or if you feel more comfortable using React's default declarative model, you can say what's changed and allow your code to react to this change:
const [redirectUrl, setRedirectUrl] = useState('')
const handleMenuItemClick = (event, index) => {
setSelectedIndex(index);
setOpen(false);
setRedirectUrl(`/groups/${index}`)
};
if (redirectUrl) {
return <Redirect to={redirectUrl} />
}
return (
<>
<ButtonGroup variant="contained" color="primary" ref={anchorRef} aria-label="split button">
<Button onClick={null}>My Groups</Button>
<Button
...
More info https://reactrouter.com/web/api/Redirect
I want the error message isn't shown until a user clicks on TextField. Here is my code:
import React, { useState, useEffect } from 'react';
import { TextField, Grid, Button } from '#material-ui/core';
const ReplyToComment = () => {
const [replyContent, setReplyContent] = useState();
const [errorMessage, setErrorMessage] = useState([]);
useEffect(() => {
if(replyContent) {
if(replyContent.length > 7){
setErrorMessage([]);
} else {
setErrorMessage(["Your answer is too short"])
}
} else {
setErrorMessage(["answer field can not empty"])
}
}, [replyContent]);
const handleChange = (event) => {
setReplyContent(event.target.value)
};
const handleSubmit = async () => {
console.log("************")
};
return (
<Grid container spacing={4} alignItems="center" justify="center" >
<Grid item xs={12}>
<TextField
value={replyContent}
name="reply"
fullWidth
required
error={Boolean(errorMessage.length)}
multiline
label="answer"
helperText={errorMessage[0]}
onChange={handleChange}
/>
</Grid>
<Button onClick={handleSubmit} variant="contained" color="primary" disabled={Boolean(errorMessage.length)}>add</Button>
</Grid>
);
}
export default ReplyToComment;
This way, the error message is shown before a user any action, but I want the error message isn't shown until a user clicks on TextField.
Try it on Codesandbox
Easiest solution would be to use a flag which holds the information about the clicked (touched) state for the TextField.
Example:
const ReplyToComment = () => {
const [replyContent, setReplyContent] = useState();
const [errorMessage, setErrorMessage] = useState([]);
const [touched, setTouched] = useState(false);
const handleTouch = () => {
setTouched(true);
};
// ...
return (
<Grid container spacing={4} alignItems="center" justify="center" >
<Grid item xs={12}>
<TextField
value={replyContent}
name="reply"
fullWidth
required
onFocus={handleTouch}
error={touched && Boolean(errorMessage.length)}
multiline
label="answer"
helperText={touched && errorMessage[0]}
onChange={handleChange}
/>
</Grid>
<Button onClick={handleSubmit} variant="contained" color="primary" disabled={Boolean(errorMessage.length)}>add</Button>
</Grid>
);
}
I have set the value on onFocus but it is totally up to you when you would want the TextField to be classified as touched (onBlur, onClick)