I have a parent stateful component and i pass the state of show dialog to a stateless header component.
When a icon clicked on the header component it opens a stateless dialog component.
In the stateless dialog component i want to be able to enter data into a text-field.
Do i have to completely change my code to make the stateless dialog to a stateful component?
Below is my code. If anyone can recommend the best way of doing this. Thanks.
class Layout extends Component {
state = {
show:false
}
toggleSidenav = (action) =>{
this.setState({
showDialog:action
})
}
render(){
return(
<div>
<Header
showNav={this.state.showDialog}
onHideNav={() => this.toggleSidenav(false)}
onOpenNav={() => this.toggleSidenav(true)}
/>
</div>
)
}
}
export default Layout;
Header component
const Header = (props) => {
console.log(props.onOpenNav)
const navBars = () => (
<div>
<AppBar position="static">
<Toolbar>
<IconButton color="inherit" aria-label="createfolder">
<SvgIcon>
<path d={createfolder}
onClick={props.onOpenNav}
name="firstName" />
</SvgIcon>
</IconButton>
</Toolbar>
</AppBar>
</div>
)
return (
<div>
<SideNav {...props} />
<div>
{navBars()}
</div>
</div>
)
}
Dialog Component
const DialogBox = (props) => {
return (
<div>
<Dialog
open={props.showNav}
aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Add Folder</DialogTitle>
<DialogContent>
<TextField
margin="normal"
/>
</DialogContent>
<DialogActions>
<Button onClick={props.onHideNav} color="primary">
Cancel
</Button>
<Button onClick={props.onHideNav} color="primary"
onChange={this.handleFieldChange}
value={this.value}
>
Create
</Button>
</DialogActions>
</Dialog>
</div>
)
}
Since component Header is readily stateful. You could initialize its state to
state = {
show:false,
formData: {} //later, you may save user input from the child component here
}
and in Header component, you may add a function:
handleInputEntered = (event) => {
const _data = { ...this.state.formData };
_data[event.target.name] = event.target.value;
this.setState({
formData: _data
});
}
and make sure to pass this new function as a prop to like this:
<Header
handleInputEntered = {this.handleInputEntered}
/>
and set onChange to be this new function where you have input field:
<TextField
onChange={this.props.handleInputEntered}
/>
It seems you're using MaterialUI, so just look up how you may supply the onChange property to TextField component.
Is this clear?
Related
Good day. I'm having trouble figuring out how to pass input data from Child component to parent.
P.S
If there is a more appropriate way in managng the components data, i'm free to opened suggestions.
The Child
class CustomEmailInput extends React.Component {
constructor(props){
super(props);
}
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<CssTextField
className={classes.margin}
required
fullWidth
autoComplete="email"
onChange={this.props.setMail}
InputProps={{
className: classes.input
}}
InputLabelProps={{
className: classes.labelInput
}}
label="Email"/>
</div>
);
}
}
export default withStyles(useStyles)(CustomEmailInput);
The Parent
class SignIn extends React.Component {
constructor(props){
super(props);
this.state = {
email: '',
password: '',
};
this.handleData = this.handleData.bind(this);
this.setEmail = this.setEmail.bind(this);
}
handleData = props => {
const { email, password } = this.state;
console.log(email, password)
};
setEmail = (data) => {
this.setState({
email: data
});
console.log(data)
};
render() {
const { classes } = this.props;
return (
<div className={classes.bg}>
<Container component="main" maxWidth="xs" pt={5}>
<CssBaseline/>
<div className={classes.paper}>
<Typography
component="h1"
variant="h5"
style={{color: 'white'}}
>
Sign in
</Typography>
<form className={classes.form}>
<MyEmailField
setMail={this.setEmail}
/>
<MyPasswordField
// setPassword={this.setPassword}
label="Password"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="default"
className={classes.submit}
onClick={this.handleData}
>
Sign In
</Button>
<Grid container>
<Grid item xs>
<Link
to="/reset"
variant="body1"
className={classes.btn_forgot}
style={{textDecoration: 'none'}}
>
Forgot password?
</Link>
</Grid>
<Grid item>
<Link
to="/register"
variant="body2"
className={classes .btn_register}
style={{textDecoration: 'none'}}
>
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
</Container>
</div>
);
}
}
export default withStyles(useStyles)(SignIn);
I'm using material-UI, would it be better to change the child component to functional component?
The practice, which I can see you are using in this code, is setting the state of the parent by passing to the children a function that triggers setState (You're doing it with setEmail) and then call that function in child component to update parent's state.
(Another ways includes using mobx or redux)
first off all you should try the Redux pattern for your application, this way communication between components is not more up to the component itself. You will have an architecture dealing with this.
Dealing with your problem, a solution is to create a function in your parent that receives the input as parameter and pass it by props to the child, then you just have to call it inside the child at some event (onChange like you are doing).
I'm not a React expert yet thus I have a question for you - how to invoke my popup window from:
import {options, columns,convertToArray} from './consts'
const index = () => {
const {data, loading, error, performFetch} = fetchHook({path: "/xxx/yyy", fetchOnMount: true})
return (
<div className={classes.Container}>
<h1>List of products</h1>
<Divider className={classes.Divider} />
<ProductTable data={convertToArray(data)} options={options} columns={columns}/>
</div>
)
}
export default index;
consts.js
export const actions = (productPropertyId, showModal) => {
const productDetails = (productPropertyId) => {
}
const removeProduct = (productPropertyId, showModal) => {
actions(productPropertyId, showModal);
return (
<div className={classes.actionsContainer}>
<Button
onClick={() => productDetails(productPropertyId)}
> {"More"}
</Button>
<Button
const removeProduct = (productPropertyId, showModal) => {
actions(productPropertyId, showModal);
>{"Remove"}
</Button>
</div>
)
};
export const convertToArray = (productList) => {
let products = []
if (productList != null) {
productList.map(product => {
column1, column2, column3, actions(product.id)]
products.push(prod)
})
}
return products;
};
My popup is --> <FormDialog/> based on react Materials.
Is it possible to invoke popup in this place?
I have a react material table with some columns. The last column contains 2 buttons, one of them is "Remove" (row). Here I want to invoke my popup. Maybe I should rebuild my structure?
UPDATE
Below is my popup - I wonder how to run this popup from the place above:
const formDialog = (popupOpen) => {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
{/*<Button variant="outlined" color="primary" onClick={handleClickOpen}>*/}
{/* Open alert dialog*/}
{/*</Button>*/}
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Subscribe</DialogTitle>
<DialogContent>
<DialogContentText>
To subscribe to this website, please enter your email address here. We will send updates
occasionally.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
label="Email Address"
type="email"
fullWidth
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleClose} color="primary">
Subscribe
</Button>
</DialogActions>
</Dialog>
</div>
);
}
export default formDialog;
UPDATE 2
I updated my code taking into cosideration the tips from your response, see above. Can I add a parameter showModal in my export const actions = (productPropertyId, showModal) and then invoke this component with different showModal value? UNfortunately my popup doesn't appear when I click on Remove button :(
You can invoke it conditionally and controle it using some state variable. Like this:
const [removeModal, removeToggle] = useState(false);
return (<>
<div className={classes.actionsContainer}>
<Button
onClick={() => productDetails(productPropertyId)}
> {"More"}
</Button>
<Button
onClick={() => removeToggle(true)}
>{"Remove"}
</Button>
</div>
{removeModal ? <YourComponent /> : ''}
</>
)
I'm using a react fragment there <></> just to position the modal div outside the main div, but you can also invoke it inside your main div as well (I usually do this and set the position: fixed so the modal/popup coud appear in top of everything).
When I use standard button with onClick props event it works perfectly fine
<Button onClick={handleClose}>
Agree
</Button>
But I have also created my new component called ButtonSave and onClick is not working
<ButtonSave text="Agree" onClick={handleClose} />
My code inside ButtonSave component
export default function IconLabelButtons(props) {
const classes = useStyles();
return (
<div>
<Button
variant="contained"
color="primary"
size={props.size}
className={classes.button}
startIcon={<SaveIcon />}
>
{props.text}
</Button>
</div>
);
}
How am I supposed to use my handleClose right there?
You need to update your ButtonSave component and pass the onClick prop to it like:
export default function IconLabelButtons(props) {
const classes = useStyles();
return (
<div>
<Button
variant="contained"
color="primary"
size={props.size}
className={classes.button}
startIcon={<SaveIcon />}
onClick={props.onClick}
>
{props.text}
</Button>
</div>
);
}
Hope it works for you.
You need to use onClick which provided in props:
// <ButtonSave text="Agree" onClick={handleClose} />
const ButtonSave = ({ onClick, text }) => (
<Button onClick={onClick}>{text}</Button>
)
Based on the code you provided, it may be because you didn't pass it from ButtonSave to its child of Button. So:
export default function(props) {
return (
<Button onClick={props.handleClose} />
);
}
I am using functional components in my application, initially it had a component with a lot of code and I divided it into 3 to be able to better organize the code and have reusable components, the first one contains a <MaterialTable>, the second one <dialog> and the third one <form>.
In the component where the table is located import the <dialog>, within the <dialog> component import the <form>, that way the form is inside the dialog box and the dialog box I want to send it to call from the actions of the board.
The problem is that when adding the component in actions I get the following error
Expected an assignment or function call and instead saw an expression
How can I open the component from the actions in the table?
Table
export default function User(){
const[user, setUser]= useState({Users:[]});
useEffect(()=>{
const getUser=async()=>{
const response =await axios.get('/api/users');
setUser(response.data);
console.log(response.data)
}
getUser();
},[]);
return(
<div>
<MaterialTable
title="Users"
columns={[
{ title: 'Code', field: 'code' , type: 'numeric'},
{ title: 'Name', field: 'name' },
{ title: 'Lastname', field: 'lastname' },
{ title: 'Age', field: 'age', type: 'numeric'},
]}
data={user.Users}
actions={[
{
icon: 'event',
tooltip: 'Agregar cita',
onClick:(event, rowData)=>{
//event.preventDefault();
<Dialogs/>
}
}
]}
/>
</div>
);
}
Dialog
function Dialogs(){
const [open, setOpen] = useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return(
<div>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Subscription></DialogTitle>
<DialogContent>
<DialogContentText>
Subscription
</DialogContentText>
<AddSuscription/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
</DialogActions>
</Dialog>
</div>
)
}
export default Dialogs;
Form
export default function AddSuscription(props){
const initialState={code:0, email:'', alias:''}
const[subscription, setSubscription]=useState(initialState);
const handleChange=(event)=>{
setSubscription({...subscription,[event.target.name]:event.target.value})
}
const handleSubmit=(event)=>{
event.preventDefault();
if(!subscription.code || !subscription.email || !subscription.alias)
return
const postSubscription=async()=>{
try {
axios.post('/api/Subscription/add',subscription);
props.history.push('/Subscription');
}
catch (error) {
console.log('error', error);
}
}
postSubscription();
}
return(
<div>
<form onSubmit={handleSubmit} >
<TextField
id="filled-name"
name="code"
label="Code"
value={subscription.code}
onChange={handleChange}
margin="normal"
/>
<TextField
id="filled-name"
label="Email"
value={subscription.email}
name="email"
onChange={handleChange}
margin="normal"
/>
<TextField
id="filled-multiline-static"
label="Alias"
value={subscription.alias}
name="alias"
onChange={handleChange}
margin="normal"
/>
<Button
variant="contained"
color="primary"
type="submit">
Add
</Button>
</form>
<div>
);
}
onClick:(event, rowData)=>{
<Dialogs/>
}
You cannot render from an event handler like this. Where exactly would you expect that component to appear? React just doesn't work that way.
Instead, keep some state, change that state (which automatically re-renders your component), then conditionally render what you want based on that state.
Something like this:
Add state to the main component that needs to render the dialog:
export default function User() {
const [showDialog, setShowDialog] = useState(false)
//...
Change your event handler to change that state
onClick:(event, rowData)=>{
setShowDialog(true)
}
And in your main render, conditionally render the <Dialogs> component.
return(
<div>
{showDialog && <Dialogs/>}
<MaterialTable
{/* ... */}
/>
So I have a child component that I want to render multiple instances of in a parent container component. Passing in different props to each so they display differently.
What is happening is that they are both being rendered with the last instance of the props in the script being read into both instances. Thus the both components below end up with placeHolder==='Describe yourself'
Is there a work around for this so that they will each be injected with their props in turn exclusively?
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
buttonName='Update'
modalOpen={this.modalOpen}
placeHolder="New picture url"
change={this.handlePicture}
label='URL'
/>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
buttonName='Update'
modalOpen={this.modalOpen}
placeHolder='Describe yourself'
label='Bio'
change={this.handleBio}
/>
ButtonMode
class ButtonMode extends Component {
constructor(props){
super(props)
this.state = {
input:''
}
this.handleInput = this.handleInput.bind(this);
this.handle = this.handle.bind(this);
}
handleInput(val){
this.setState({input:val})
};
handle() {
this.props.change(this.state.input);
};
render(){
const { classes } = this.props;
return (
<div>
<Button
className={classes.button}
onClick={this.props.modalOpen}
>Update
</Button>
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={this.props.open}
onClose={this.props.handleClose}
>
<div className={classes.paper}>
<TextField
id="filled-textarea"
label={this.props.label}
placeholder={this.props.placeHolder}
multiline
className={classes.textField}
onChange={(e)=>{this.handleInput(e.target.value)}}
rows= '4'
/>
<Button
onClick={this.handle}
className={classes.button}
color="secondary">Submit</Button>
</div>
</Modal>
</div>
)
}
}
Then I used it like that
class UserCard extends Component {
constructor(props){
super(props);
this.state = {
tempPro:'',
open: false,
profilePicture:''
}
this.modalOpen = this.modalOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handlePicture = this.handlePicture.bind(this);
}
// componentDidMount(){
// const {userId, profilePic} = this.props;
// this.setState({profilePicture:profilePic});
// // axios.get(`/api/profile/${userId}`).then(res=>{
// // let {profilePic} = res.data[0];
// // this.setState({profilePic})
// // })
// }
handlePicture(val){
this.props.changePic(val);
this.setState({open:false});
};
handleBio(val){
this.setState({open:false});
};
handleClose(){
this.setState({open: false});
};
modalOpen(){
this.setState({open:true});
};
render() {
const { classes } = this.props;
const {stories} = this.props;
let storyShow = stories.map((story,id) => {
return(
<div value={story.story_id}>
<h3>{story.title}</h3>
<ul className={classes.background}>
<li>{story.description}</li>
<li>{story.is_complete}</li>
</ul>
</div>
)
});
return (
<div className={classes.rootD}>
<Grid container>
<Grid className={classes.itemFix} >
<Card className={classes.card}>
<CardMedia
className={classes.media}
image={this.props.proPic}
title={this.props.userName}
/>
<div>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
modalOpen={this.modalOpen}
placeHolder="New picture url"
change={this.handlePicture}
label='URL'
/>
</div>
<CardHeader
className={classes.titles}
title={this.props.userName}
subheader="Somewhere"
/>
<CardHeader className={classes.titles} title='Bio' />
<CardContent className={classes.background}>
<Typography className={classes.bio} paragraph>
{this.props.bio}
</Typography>
</CardContent>
<div>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
modalOpen={this.modalOpen}
placeHolder='Describe you how you want'
label='Bio'
change={this.handleBio}
/>
</div>
</Card>
</Grid>
<Grid className={classes.itemFixT}>
<Card className={classes.card}>
<CardContent>
<CardHeader
className={classes.titles}
title='Works'/>
<Typography paragraph>
<ul>
{storyShow}
</ul>
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</div>
);
}
}
UserCard.propTypes = {
classes: PropTypes.object.isRequired,
};
function mapStateToProps(state){
const {userId, profilePic} = state;
return {
userId,
profilePic
}
}
export default connect(mapStateToProps,{})(withStyles(styles)(UserCard));
I had a similar issue where I was trying to pass different functions to the children components. I had a UploadFile component that contained an <input/> and a <Button/> from material-ui, and I wanted to reuse this component multiple times throughout a page, as the user has multiple files to upload, and in order to save the files, I needed callback functions in the main page.
What I had to do, was give each child component <UploadFile/> in my case, and <ButtonMode/> in your case, a unique id passed in as a prop, since otherwise, the top level page cannot tell each reference to the child component apart from any others.
The code of the child component:
function UploadFile({ value, handleFile }) {
const classes = useStyles();
return (
<>
<input
accept=".tsv,.fa,.fasta"
className={classes.input}
id={value}
type="file"
style={{ display: 'none' }}
onChange={e => handleFile(e.target.files[0])}
/>
<label htmlFor={value}>
<Button
variant="contained"
color='default'
component="span"
startIcon={<CloudUploadIcon />}
className={classes.button}>
Upload
</Button>
</label>
</>
);
}
The usage of this component in the parent (handleFile is the function I am passing in and is defined above in the parent component):
<UploadFile value='prosite' handleFile={handlePrositeFile} />
<UploadFile value='pfam' handleFile={handlePfamFile} />
I spent an embarrassingly long time on a similar issue. I tried all sorts of JS debugging and even re-read the entire concept of closure :)
This is was my culprit: <TextField id="filled-textarea" ... />
i.e. the id is static. If we have multiple instances of the same id on one page, we have a problem.
Make id dynamic, e.g. <TextField id={this.props.label} ... />
I was using the same state for both modals and in each instance of handleOpen() it was only ever opening the last instance of modal in the script.