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.
Related
I'm trying to retrieve images from an API and display them in a three column grid system. When I retrieved images, they are displayed one beneath each other.
Kindly advised best way to achieve this
import React from "react";
import Display from "./Display";
class App extends React.Component {
constructor() {
super();
this.state = {
loading: true,
image: [],
};
}
async componentDidMount() {
const url = "http://jsonplaceholder.typicode.com/photos?_start=0&_limit=10";
const response = await fetch(url);
const data = await response.json();
this.setState({ image: data, loading: false });
console.log(data);
}
render() {
if (this.state.loading) {
return <div> loading ... </div>;
}
if (!this.state.image.length) {
return <div> didnt get an image</div>;
}
return (
<div>
{this.state.image.map((img) => (
<div>
<div>
{" "}
<Display
showImage={img.url}
showTitle={img.title}
showAlbum={img.albumId}
/>{" "}
</div>
<div key={img.id}>
<ul></ul>
</div>
</div>
))}
</div>
);
}
}
export default App;
function Display(props) {
const classes = useStyles();
return (
<div className={classes.root}>
<Grid container spacing={4}>
<Grid item xs={12} sm={6} md={4}>
<CardActionArea>
<CardMedia
component="img"
alt="Contemplative Reptile"
height="200"
width="200"
img
src={props.showImage}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{props.showTitle}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
Album ID: {props.showAlbum}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Grid>
</Grid>
</div>
);
}
export default Display;
In my App.js I'm able to using the map feature and get all the items from the array, I'm thinking I should modify my App.jS file to print out the results in the three columns but I'm not sure. Can I modify the Display file so that each card goes right beside each other? Do I need to use another array for the cards?
You can add display flex to the parent div in your render method as default flexDirection = 'row'
render() {
if (this.state.loading) {
return <div> loading ... </div>;
}
if (!this.state.image.length) {
return <div> didnt get an image</div>;
}
return (
<div style={{display:"flex"}}> //added display flex
{this.state.image.map((img) => (
<div>
<div>
{" "}
<Display
showImage={img.url}
showTitle={img.title}
showAlbum={img.albumId}
/>{" "}
</div>
<div key={img.id}>
<ul></ul>
</div>
</div>
))}
</div>
);
}
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 trying too add Unit Testing to a Project I've been working on, using Enzyme. I've been able to set up a basic test, however, when I try to run it, I get the following error "Method “text” is meant to be run on 1 node. 0 found instead."
I've been going through various online tutorials, as well as SO answers for similar questions, but I can't find anything that helps. I've also tried console.log-ing the wrapper, which returns:
<Connect(withRouter(WithStyles(SignIn))) >
Code Being Tested;
class SignIn extends Component {
//Removed unnecessary code for expediency
render() {
const { classes, loading } = this.props;
const {
values,
touched,
errors,
isValid,
} = this.state;
const hasError = field => !!(touched[field] && errors[field]);
const { store } = configureStore();
return (
<Provider store={store}>
<div className={classes.root}>
<Grid
className={classes.grid}
container
>
<Grid
className={classes.quoteWrapper}
item
lg={5}
>
<div className={classes.quote}>
<div className={classes.quoteInner}>
<Typography
className={classes.quoteText}
variant="h1"
>
We work with your business to provide a range of innovative
and bespoke software solutions.
</Typography>
</div>
</div>
</Grid>
<Grid
className={classes.content}
item
lg={7}
xs={12}
>
<div className={classes.content}>
<div className={classes.contentBody}>
<form className={classes.form}>
<p>
<Typography
className={classes.title}
variant="h2"
>
Sign in
</Typography>
</p>
<div className={classes.fields}>
<TextField
className={classes.textField}
error={hasError('username')}
helperText={
hasError('username') ? errors.username[0] : null
}
label="User name"
name="username"
onChange={event =>
this.handleFieldChange('username', event.target.value)
}
onKeyPress={this.handleKeyPress}
type="text"
value={values.username}
variant="outlined"
/>
<TextField
className={classes.textField}
error={hasError('password')}
helperText={
hasError('password') ? errors.password[0] : null
}
label="Password"
name="password"
onChange={event =>
this.handleFieldChange('password', event.target.value)
}
onKeyPress={this.handleKeyPress}
type="password"
value={values.password}
variant="outlined"
/>
</div>
{loading ? (
<CircularProgress className={classes.progress}/>
) : (
<Button
className={classes.signInButton}
color="primary"
disabled={!isValid}
onClick={this.handleSignIn}
size="large"
variant="contained"
>
Sign in now
</Button>
)}
<Typography
className={classes.resetPassword}
variant="body1"
>
Have you forgotten your password?{' '}
<Link
className={classes.resetPasswordURL}
to="/reset-password"
>
Reset it
</Link>
</Typography>
</form>
</div>
</div>
</Grid>
</Grid>
</div>
</Provider>
);
}
}
SignIn.propTypes = {
className: PropTypes.string,
classes: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
history: PropTypes.object.isRequired,
loading: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
loading: state.authReducer.loadingKeys.includes(dataConstants.loading.main),
user: state.authReducer.user
});
export default connect(mapStateToProps)(compose(
withRouter,
withStyles(styles)
)(SignIn));
And here is my test:
describe('SignIn component', () => {
it('starts with a count of 0', () => {
const wrapper = shallow(
<Provider store={store}>
<SignIn/>;
</Provider>
).dive();
let wrapperDebug = wrapper.debug();
console.log(wrapperDebug);
const text = wrapper.find('p').text();
expect(text).toEqual('Sign in');
});
});
The test should find the typography surrounded by p (I have left a few lines clear on either side in my code to highlight this area), and compare the text found within to the expected value. Thanks in advance for any help!
To solve this: Error: Uncaught [Error: Invariant failed: You should not use <withRouter(WithStyles(SignIn)) /> outside a ]. As said in an older post, you should use mount() in order to render all the components tree. Also, use from the react-router-dom to wrap your components. This replace the component for testing purposes.
Hope this helps: https://reactrouter.com/web/api/MemoryRouter
I'm trying to render a specific component based on stage scenario of the page. I'm using a varriable "transitComponent" to render one of three components - a circular progress (wait) or one of two buttons once the response is received.
Any suggestions?
render() {
const { classes } = this.props;
if (this.state.stage==1){ transitComponent = CircularProgress};
if (this.state.stage==2){ transitComponent = CancelButton };
if (this.state.stage==3){ transitComponent = OKButton };
return (
<div align="center">
<br />
<Button align="center" variant="contained" color="primary" onClick= {this.handleOpen}>Create Profile</Button>
<Modal aria-labelledby="simple-modal-title" aria-describedby="simple- modal-description" open={this.state.open} onClose={this.handleClose}>
<div style={getModalStyle()} className={classes.paper}>
<Typography variant="title" id="modal-title" align="center">
{this.state.message}
</Typography>
{transitComponent}
</div>
</Modal>
</div>
);
}
You are assigning CircularProgress, CancelButton or OKButton to a temporary variable transitComponent, depending on the current state.stage. That's OK, the only part you got wrong is how you render that component.
Since transitComponent is a component like any other, you don't render it with curly braces, but as a component, so <transitComponent /> would be the proper way.
One more thing: Since React naming convention requires you to name components capitalized, you should name it TransitComponent and render it as <TransitComponent />.
And don't forget to declare TransitComponent with a let statement!
Updated code example:
render() {
const { classes } = this.props;
let TransitComponent;
if (this.state.stage==1){ TransitComponent = CircularProgress};
if (this.state.stage==2){ TransitComponent = CancelButton };
if (this.state.stage==3){ TransitComponent = OKButton };
return (
<div align="center">
<br />
<Button align="center" variant="contained" color="primary" onClick={this.handleOpen}>Create Profile</Button>
<Modal aria-labelledby="simple-modal-title" aria-describedby="simple-modal-description" open={this.state.open} onClose={this.handleClose}>
<div style={getModalStyle()} className={classes.paper}>
<Typography variant="title" id="modal-title" align="center">
{this.state.message}
</Typography>
<TransitComponent />
</div>
</Modal>
</div>
);
}
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?