I'm having trouble rerendering component when parent state passed to child component changes. MapNav component contains buttons which triggers methods changing current displayed popup. My problem is to re-render UserPopup button when someone clicks zaloguj or wyloguj, it should work but it does not and i cant figure out what is wrong also i don't want to trigger animation when isLoggedIn state changes and one more thing logIn method changes state properly. isLoggedIn state is just temporary later i'll change that to use store instead. I'm trying to write as much functional component but in some cases i cant avoid using state and i bet that there are issues in my code so i'll appreciate all improvement tips.
const MapNav = (props) => {
return <div className={'mapNavContainer'}>
<ul className={'navList'}>
<li><i className={"glyphicon glyphicon-fullscreen"} /></li>
<li><i className={"glyphicon glyphicon-chevron-left"} /></li>
<li><i className={"glyphicon glyphicon-chevron-right"} /></li>
<li><i className={"glyphicon glyphicon-map-marker"} /></li>
<li><a href="#" onClick={props.onUserIconClick}><i className={"glyphicon glyphicon-user"} /></a></li>
<li><a href="#" onClick={props.onInfoClick}><i className={"glyphicon glyphicon-info-sign"} /></a></li>
</ul>
</div>
}
const UserPopup = (props) => {
return <div>
{(props.isLoggedIn) ? (
<div className={'navPopups col-sm-3 panel panel-default'}>
<div className={"panel-heading"}>Zalogowany</div>
<div className={"panel-body"}>
<button type='submit' onClick={props.logIn}>wyloguj</button>
</div>
</div>
) : (
<div className={'navPopups col-sm-3 panel panel-default'}>
<div className={"panel-heading"}>Niezalogowany</div>
<div className={"panel-body"}>
<button type='submit' onClick={props.logIn}>Zaloguj</button>
</div>
</div>
)
}
</div>;
};
function Welcome(props) {
return <div className={'navPopups col-sm-3 panel panel-default'}>
<div className={"panel-heading"}>Informacje o Autorze.</div>
<div className={"panel-body"}>
Basic panel example
</div>
</div>;
}
class MapHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
currentPopup: null,
isLoggedIn: false,
username: 'gregorowicz.k#gmail.com',
};
this.onInfoClick = this.onInfoClick.bind(this);
this.onUserIconClick = this.onUserIconClick.bind(this);
this.logIn = this.logIn.bind(this);
}
onInfoClick = (e) => {
e.preventDefault();
this.setPopup(<Welcome />);
}
onUserIconClick = (e) => {
e.preventDefault();
const loginPopup = <UserPopup isLoggedIn={this.state.isLoggedIn} username={this.state.username} logIn={this.logIn} />;
this.setPopup(loginPopup);
}
setPopup = (obj) => {
this.state.currentPopup === null ? this.setState({ currentPopup: obj }) : this.setState({ currentPopup: null });
}
logIn = (e) => {
this.setState({ isLoggedIn: !this.state.isLoggedIn });
}
render() {
return (
<nav className={'header'}>
<div className={'container-fluid'}>
<div className={"navbar-header"}>
<button type="button" className={"navbar-toggle collapsed"} data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span className={"sr-only"}>Toggle navigation</span>
<span className={"icon-bar"} />
<span className={"icon-bar"} />
<span className={"icon-bar"} />
</button>
</div>
<div id={"navbar"} className={'navContainer'}>
{MapNav({onInfoClick:this.onInfoClick, onUserIconClick:this.onUserIconClick})}
{this.state.currentPopup ? this.state.currentPopup : null}
</div>
</div>
</nav>
)
}
}
// Render it
ReactDOM.render(
<MapHeader />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css" />
I've solved it. There is currentPopup state in my code which stored current popup displayed, to see changes i had to unmount and mount component again after isLogggedIn state changed, so i've changed the way to display current popup. Instead storing whole component in state, i store just simple string values which provide information about current component. Anyway thank you for tips, i'll surely apply them in my code.
const MapNav = (props) => {
return <div className={'mapNavContainer'}>
<ul className={'navList'}>
<li><i className={"glyphicon glyphicon-fullscreen"} /></li>
<li><i className={"glyphicon glyphicon-chevron-left"} /></li>
<li><i className={"glyphicon glyphicon-chevron-right"} /></li>
<li><i className={"glyphicon glyphicon-map-marker"} /></li>
<li><a href="#" onClick={props.onUserIconClick}><i className={"glyphicon glyphicon-user"} /></a></li>
<li><a href="#" onClick={props.onInfoClick}><i className={"glyphicon glyphicon-info-sign"} /></a></li>
</ul>
</div>
}
const UserPopup = (props) => {
return <div>
{(props.isLoggedIn) ? (
<div className={'navPopups col-sm-3 panel panel-default'}>
<div className={"panel-heading"}>Zalogowany</div>
<div className={"panel-body"}>
<button type='submit' onClick={props.logIn}>wyloguj</button>
</div>
</div>
) : (
<div className={'navPopups col-sm-3 panel panel-default'}>
<div className={"panel-heading"}>Niezalogowany</div>
<div className={"panel-body"}>
<button type='submit' onClick={props.logIn}>Zaloguj</button>
</div>
</div>
)
}
</div>;
};
function Welcome(props) {
return <div className={'navPopups col-sm-3 panel panel-default'}>
<div className={"panel-heading"}>Informacje o Autorze.</div>
<div className={"panel-body"}>
Basic panel example
</div>
</div>;
}
class MapHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
currentPopup: null,
isLoggedIn: null,
};
this.onInfoClick = this.onInfoClick.bind(this);
this.onUserIconClick = this.onUserIconClick.bind(this);
this.logIn = this.logIn.bind(this);
}
onInfoClick = (e) => {
e.preventDefault();
this.setState({ currentPopup: this.state.currentPopup === 'info' ? null : 'info' });
}
onUserIconClick = (e) => {
e.preventDefault();
this.setState({ currentPopup: this.state.currentPopup === 'user_login' ? null : 'user_login' });
}
setPopup = (obj) => {
this.setState({ currentPopup: this.state.currentPopup === 'user_login' ? null : 'user_login' });
}
logIn = (e) => {
this.setState({ isLoggedIn: !this.state.isLoggedIn });
}
render() {
return (
<nav className={'header'}>
<div className={'container-fluid'}>
<div className={"navbar-header"}>
<button type="button" className={"navbar-toggle collapsed"} data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span className={"sr-only"}>Toggle navigation</span>
<span className={"icon-bar"} />
<span className={"icon-bar"} />
<span className={"icon-bar"} />
</button>
</div>
<div id={"navbar"} className={'navContainer'}>
{MapNav({onInfoClick:this.onInfoClick, onUserIconClick:this.onUserIconClick})}
{this.state.currentPopup === 'info' ? <Welcome /> : null}
{this.state.currentPopup === 'user_login' ? (
<UserPopup isLoggedIn={this.state.isLoggedIn} logIn={this.logIn} />
) : null}
</div>
</div>
</nav>
)
}
}
// Render it
ReactDOM.render(
<MapHeader />,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css" />
Related
I'm using react and have a list with images, when hovered them it shows a text and when clicked it opens a modal, but each "li" has different content that goes in the modal. The problems is that every item of the list open only one modal, the last one, how can I do to open the correct modal?
Modal code
constructor(props) {
super(props);
this.state = {
visible : false
}
}
openModal() {
this.setState({
visible : true,
});
}
closeModal() {
this.setState({
visible : false,
});
}
list with the modal
<li className="bor">
<img src={bor}/>
<span className="first">teste</span>
<span className="second">veja o case completo</span>
<input type="button" onClick={() => this.openModal()} />
<section>
<Modal className="modal" visible={this.state.visible} width="90%" height="90%" effect="fadeInUp" onClickAway={() => this.closeModal()}>
<div className="close">
<a href="javascript:void(0);" onClick={() => this.closeModal()}>X</a>
</div>
<div>
<h1>teste1</h1>
</div>
</Modal>
</section>
</li>
<li className="baz">
<img src={baz}/>
<span className="first">teste2</span>
<span className="second">veja o case completo</span>
<input type="button" onClick={() => this.openModal()} />
<section>
<Modal className="modal" visible={this.state.visible} width="90%" height="90%" effect="fadeInUp" onClickAway={() => this.closeModal()}>
<div className="close">
<a href="javascript:void(0);" onClick={() => this.closeModal()}>X</a>
</div>
<div>
<h1>teste2</h1>
</div>
</Modal>
</section>
</li>
As #FatemehQasemkhani said it is best practice to use single modal & pass corresponding data like below. I approaced same way & it is working fine. I am taking some dummy data inside items. Whenever user is clicking on any list(li) item I am storing that currrent clicked item inside activeItemData and passing that value in modal.
import React, { Component } from "react";
import Modal from "react-awesome-modal";
class ProList extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
activeItemData: "",
items: [
{
desc: "this is item 1 "
},
{
desc: "this is item 2"
}
]
};
}
closeModal() {
this.setState({
visible: false
});
}
handleCurrentItem = item => {
// you can set two properties in setState or you can set visible property in callback also...
this.setState(
{
activeItemData: item.desc,
visible:true
},
// () => this.setState({ visible: true })
)
};
render() {
return (
<div>
<ul>
{ this.state.items.map(item => (
<li key={item.desc} onClick={() => this.handleCurrentItem(item)}>
{item.desc}
</li>
))}
</ul>
<Modal
className="modal"
visible={this.state.visible}
width="90%"
height="90%"
effect="fadeInUp"
onClickAway={() => this.closeModal()}
>
<div className="close">
<a href="javascript:void(0);" onClick={() => this.closeModal()}>
X
</a>
</div>
<div>
<h1>{this.state.activeItemData}</h1>
</div>
</Modal>
</div>
)
}
}
Below is an example of me editing a "course" object in the database. Works perfectly fine on desktop but not on my iPhone. I'm unable to check on Android or other devices.
Not sure what more detail I could add but I need to since StackOverflow won't let me post until I use this filler. Please ignore this entire paragraph....
imports...
class EditCoursePage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
name: this.props.name,
submitted: false,
_creator: {},
};
this.updateName = this.updateName.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.handleLogout = this.handleLogout.bind(this);
this.handleBack = this.handleBack.bind(this);
}
handleBack(event) {
window.location.href = `/courses/${this.props.match.params.cuid}`;
}
updateName(event) {
var input = event.target.value;
this.setState({
name: input,
});
}
componentWillMount() {
this.props.dispatch(actions.getCourse(this.props.match.params.cuid));
}
componentDidMount() {
this.props.dispatch(actions.getCourse(this.props.match.params.cuid));
}
onSubmit(event) {
const name = this.state.name;
const instructor = cookies.get('instructor')._id;
console.log(cookies.get('instructor')._id);
this.setState({
submitted: true,
});
this.props.dispatch(actions.editCourse(name,this.props.match.params.cuid));
console.log(this.props.name);
console.log(name, cookies.get('instructor')._id);
}
render() {
if (this.state.submitted) {
window.location.href = `https://young-mountain-65748.herokuapp.com/courses/${this.props.match.params.cuid}`;
}
return (
<div className="edit-course-form">
<div className="menu">
<Menu>
<a
id="dashboard-return"
className="menu-item"
href={`/courses/${this.props.match.params.cuid}`}
>
Back to Your Course
</a>
<a id="dashboard-logout" className="menu-item" href="/login">
Logout
</a>
</Menu>
</div>
<div className="mobile-header">
<div className="mobile-name">{this.props.name}</div>
</div>
<div className="edit-course-nav-options">
<div className="course-app-name">School Management App</div>
<ul>
<li>
<Link to="/login" onClick={this.handleLogout}>Log out </Link>
</li>
<li>
<Link to={`/courses/${this.props.match.params.cuid}`}>
Back to Your Course
</Link>
</li>
</ul>
</div>
<div className="container">
<div className="edit-course-name"><h2>{this.props.name}</h2></div>
<div className="submitForm">
<div className="field-line">
<label htmlFor="coursename">New Course Name:</label>
<input
id="coursename"
name="coursename"
value={this.state.name}
onChange={this.updateName}
/>
</div>
</div>
</div>
<div className="edit-course-buttons">
<button className="edit-course" onClick={this.onSubmit}>
Edit Course
</button>
<button className="edit-course-back" onClick={this.handleBack}>
Back
</button>
</div>
</div>
);
}
}
const mapStateToProps = (state, props) => {
return {
name: state.course.course.name,
_creator: state.course._creator,
};
};
export default connect(mapStateToProps)(EditCoursePage);
I am trying to change state of the component and render it to page.
var navbarValue = [{"Category":"Home","Status":1},{"Category":"About","Status":0},{"Category":"Contact","Status":0}];
class NavbarList extends React.Component {
constructor() {
super();
this.onClick = this.handleClick.bind(this);
}
handleClick(event) {
const {id} = event.target;
console.log(id);
if(!navbarValue[id].Status){
{navbarValue.map((obj, index) =>{
if(navbarValue[index].Status){
navbarValue[index].Status = 0;
return <li key={index}><a id={index} onClick={this.onClick}>{obj.Category} </a></li>
}
})}
navbarValue[id].Status = 1;
return <li className="active" key={id}><a id={id} onClick={this.onClick}>{navbarValue[id].Category}</a></li>
}
}
render() {
return (
<div className="navbar-wrapper">
<div className="container">
<nav className="navbar navbar-fixed-static-top navbar-inverse">
<div className="container">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<a className="navbar-brand" href="#">BrandName</a>
</div>
<div id="navbar" className="collapse navbar-collapse">
<ul className="nav navbar-nav">
{navbarValue.map((obj, index) => {
if (obj.Status)
return <li className="active" key={index}><a id={index} onClick={this.onClick}>{obj.Category}</a></li>
return <li key={index}><a id={index} onClick={this.onClick}>{obj.Category} </a></li>
})}
</ul>
</div>
</div>
</nav>
</div>
</div>
)};
};
ReactDOM.render(
<NavbarList />,
document.getElementById('root')
);
<script src="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I have an if statement in handleClick(event) which validates if function has to be run and rerender elements.
First it checks if button which is already active is not being clicked. Next if statment is in loop to find previous active element, change objects property to 0 and render to not active.
At the end render new active element to active and set objects property to 1.
Put navbarValue in a state object and update the state onClick. You don't need if/else statements here. Because your components will be rerendered on state update.
Like this: https://stackoverflow.com/a/45134876/4953199
This question already has answers here:
Accessing `this` in react when assigning method to an event
(3 answers)
Unable to access React instance (this) inside event handler [duplicate]
(19 answers)
How to access the correct `this` inside a callback
(13 answers)
Closed 5 years ago.
The error happens on the onClick method of <span className="nav-toggle">. The error is: cannot read property setState of null.I think it's an issue with the scoping of this or because setState is an async method. That's why I've tried using the recommended method in the docs (using setState with a function instead of passing in a new state object).
export default class NavigationBar extends Component {
constructor(props) {
super(props);
this.state = {
isMobile: this.props.isMobile
};
}
render() {
console.log('Rendering component');
return (
<header className="nav has-shadow">
<div className="nav-left">
<Link className="title" to="/">Food Diary</Link>
</div>
<span className={ (this.state.isMobile) ? 'nav-toggle is-active' : 'nav-toggle'} onClick={this.toggleMobileNav}>
<span></span>
<span></span>
<span></span>
</span>
<div className={ (this.state.isMobile) ? 'nav-right nav-menu is-active' : 'nav-right nav-menu'}>
<Link className={(this.props.currentActivePage === 'Home')? "nav-item is-active" : "nav-item"} to="/">
{(this.props.loggedIn) ? 'Home' : 'Login | Register'}
</Link>
<Link className={(this.props.currentActivePage === 'Dashboard')? "nav-item is-active" : "nav-item"} to="/dashboard">
Dashboard
</Link>
<Link className={(this.props.currentActivePage === 'Account')? "nav-item is-active" : "nav-item"} to="/account">
My Account
</Link>
<span className="nav-item">
<a className="button is-dark" href="https://github.com/">
<span className="icon">
<i className="fa fa-github"></i>
</span>
<span>Github Repository</span>
</a>
</span>
</div>
</header>
);
}
toggleMobileNav() {
console.log('Got inside toggleMobileNav');
this.setState((prevState, props) => {
return {
isMobile: !prevState.isMobile
};
});
}
};
You have to bind this to handlers:
export default class NavigationBar extends Component {
constructor(props) {
super(props);
this.state = {
isMobile: this.props.isMobile
};
this.toggleMobileNav = this.toggleMobileNav.bind(this); // BIND TO THIS
}
render() {
console.log('Rendering component');
return (
<header className="nav has-shadow">
<div className="nav-left">
<Link className="title" to="/">Food Diary</Link>
</div>
<span className={ (this.state.isMobile) ? 'nav-toggle is-active' : 'nav-toggle'} onClick={this.toggleMobileNav}>
<span></span>
<span></span>
<span></span>
</span>
<div className={ (this.state.isMobile) ? 'nav-right nav-menu is-active' : 'nav-right nav-menu'}>
<Link className={(this.props.currentActivePage === 'Home')? "nav-item is-active" : "nav-item"} to="/">
{(this.props.loggedIn) ? 'Home' : 'Login | Register'}
</Link>
<Link className={(this.props.currentActivePage === 'Dashboard')? "nav-item is-active" : "nav-item"} to="/dashboard">
Dashboard
</Link>
<Link className={(this.props.currentActivePage === 'Account')? "nav-item is-active" : "nav-item"} to="/account">
My Account
</Link>
<span className="nav-item">
<a className="button is-dark" href="https://github.com/">
<span className="icon">
<i className="fa fa-github"></i>
</span>
<span>Github Repository</span>
</a>
</span>
</div>
</header>
);
}
toggleMobileNav() {
console.log('Got inside toggleMobileNav');
this.setState((prevState, props) => {
return {
isMobile: !prevState.isMobile
};
});
}
};
More information here
I've a list of photos being displayed as a React Component.
The individual list items are initially displayed with a + sign. The behavior I'm trying to acheive is on clicking a particular list item, the sign changes to -, and once a different list item is clicked, the first one reverts to + and the current one goes to -.
This is my render code,
render() {
let classes = "glyphicon add-icon " + (this.state.glyphClass ? "glyphicon-plus" : "glyphicon-minus");
return (
<div className="row">
<ul className="list-inline">
{this.props.images.map(function (image) {
return (<li key={image.id}>
<a href="#" onClick={this.getMediaId} data-id={image.id} data-class={image.src} data-owner={image.owner}>
<div className="img-wrapper">
<div className="img" style={{backgroundImage: `url(${image.src})`}}></div>
<div className="img-selector">
<span className={classes} id="plus-icon" aria-hidden="true"></span>
</div>
</div>
</a>
</li>);
}, this)}
</ul>
</div>
);
}
This is the constructor,
constructor(props){
super(props);
this.getMediaId = this.getMediaId.bind(this);
this.state = { glyphClass : true };
}
And this is the method that does the toggle,
getMediaId(event){
event.preventDefault();
this.setState({
glyphClass: !this.state.glyphClass
});
console.log(this.state.glyphClass);
....
}
The behavior that I'm getting now is that onClick on any list item all the list items are toggling to - and then on a subsequent click all are toggling to +. I'd really appreciate some help in fixing this.
You can have a selectedItem in the state instead.
constructor(props){
super(props);
this.getMediaId = this.getMediaId.bind(this);
this.state = { selectedItem : null };
}
Then in the get media set the id of selectedItem when clicked.
getMediaId(id){
this.setState({
selectedItem: id
});
}
Then you can check the id when rendering the list.
if the selectedItem has the same id of the list, render the - else render +.
render() {
return (
<div className="row">
<ul className="list-inline">
{this.props.images.map(function (image) {
const classes = this.state.selectedItem === image.id ? 'glyphicon add-icon glyphicon-minus' : 'glyphicon add-icon glyphicon-plus';
return (<li key={image.id}>
<a href="#" onClick={(event) => {event.preventDefault(); this.getMediaId(image.id); }} data-id={image.id} data-class={image.src} data-owner={image.owner}>
<div className="img-wrapper">
<div className="img" style={{backgroundImage: `url(${image.src})`}}></div>
<div className="img-selector">
<span className={classes} id="plus-icon" aria-hidden="true"></span>
</div>
</div>
</a>
</li>);
}, this)}
</ul>
</div>
);
}