Toogle button not updating state properly - reactjs

I have a button that I am using to toggle my sidebar in react application. The toggle button works fine for first two toggle states than it repeats the state twice for third time.
This is how I am toggling state from child component to parent:
import React, { Component } from 'react'
export default class Header extends Component {
constructor(props) {
super(props)
this.state = {
toggle: false
}
}
toggleSidebar = () => {
this.setState({
toggle : !this.state.toggle
});
console.log(this.state.toggle)
this.props.getToggleState(this.state.toggle);
}
render() {
return (
<div>
<button style={{width: '60px'}} onClick={this.toggleSidebar}>Toogle</button>
</div>
)
}
}
export default class App extends Component{
constructor(props) {
super(props)
this.state = {
toggleVal:''
}
}
getData = (val) => {
this.setState({
toggleVal: val
})
}
render(){
let toggleConst = '';
if(this.state.toggleVal){
toggleConst = (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection:'row'}}>
<div style={{flexDirection:'column'}}>
<Header getToggleState={this.getData}/>
<Routes/>
<Footer/>
</div>
</div>
</Router>
)
}
else{
toggleConst = (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection:'row'}}>
<SideNav toggleVal={this.state.toggleVal}/>
<div style={{flexDirection:'column'}}>
<Header getToggleState={this.getData}/>
<Routes/>
<Footer/>
</div>
</div>
</Router>
)
}
return (
toggleConst
);
}
}
Toggling the button hides/open the sidebar perfectly but it stuck on state when gets 'false' as twice.
This is how state console goes:
I am not able to find the problem here. Any help appreciated.

App.js
import React, {Component} from 'react';
import { BrowserRouter as Router} from "react-router-dom";
import Header from './Header';
import Sidebar from './Sidebar'
export default class App extends Component{
constructor(props) {
super(props)
this.state = {
toggleVal: false
}
}
getData = (val) => {
this.setState({
toggleVal: val
});
}
render(){
console.log("called.....123...",this.state.toggleVal)
if(this.state.toggleVal){
return (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection:'row'}}>
<Sidebar toggleVal={this.state.toggleVal}/>
<div style={{flexDirection:'column'}}>
<Header getToggleState={this.getData} />
</div>
</div>
</Router>
)
}
else{
return (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection:'row'}}>
<Sidebar toggleVal={this.state.toggleVal}/>
<div style={{flexDirection:'column'}}>
<Header getToggleState={this.getData}/>
</div>
</div>
</Router>
)
}
}
}
Header.js
import React, { Component } from 'react'
export default class Header extends Component {
constructor(props) {
super(props)
this.state = {
toggle: false
}
}
toggleSidebar = () => {
this.setState({
toggle: !this.state.toggle
},()=>{
// console.log(this.state.toggle)
this.props.getToggleState(this.state.toggle);
});
}
render() {
return (
<div>
<button onClick={()=>this.toggleSidebar(this.state.toggle)}>Toogle</button>
</div>
)
}
}
Sidebar.js
import React, { Component } from 'react'
import { NavLink } from "react-router-dom";
export default class Sidebar extends Component {
render() {
return (
<>
{
this.props.toggleVal &&
<div className="sidebar_container">
<nav className="nav_container">
<ul>
<li>
<NavLink to="/" activeClassName="active" exact={true}>Dashboard</NavLink>
</li>
<li>
<NavLink to="/user" activeClassName="active">User PRofile</NavLink>
</li>
<li>
<NavLink to="/register" activeClassName="active">Register</NavLink>
</li>
</ul>
</nav>
</div>
}
</>
)
}
}
https://repl.it/repls/IncredibleLinedCgi
This Will Work for You

Change this part of the code:
this.setState({
toggle : !this.state.toggle
});
To this:
this.setState(prev => {
return { toggle : !prev.toggle }
});

You should call getToggleState inside your setState callback in order to use proper state as argument
this.setState(prevState => {
this.props.getToggleState(!prevState.toggle);
return { toggle: !prevState.toggle };
});
Despite this solution, it's better if you don't keep duplicate state in child component <Header /> as conditional render is Parent duty.

This could be much simpler in my opinion.
Define the state on the parent component App ìsToggled
Call from the child component Header via callback this.props.onToggle()
Use conditional rendering on parent component {this.state.isToggled && <Sidebar/>}
import React, {Component} from 'react';
import {BrowserRouter as Router} from "react-router-dom";
import Header from './Header';
import Sidebar from './Sidebar'
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isToggled: false
}
};
onToggle = () => {
this.setState({
isToggled: !this.state.isToggled
});
console.log(this.state.isToggled);
};
render() {
return (
<Router>
<div style={{display: 'flex', backgroundColor: '#ccc', height: '100%', flexDirection: 'row'}}>
<div style={{flexDirection: 'column'}}>
<Header onToggle={this.onToggle}/>
</div>
{this.state.isToggled && <Sidebar/>}
</div>
</Router>
)
}
}
import React, {Component} from 'react'
export default class Header extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<button onClick={() => {
this.props.onToggle()
}}>Toggle
</button>
</div>
)
}
}
import React, {Component} from 'react'
import {NavLink} from "react-router-dom";
export default class Sidebar extends Component {
render() {
return (
<div className="sidebar_container">
<nav className="nav_container">
<ul>
<li>
<NavLink to="/" activeClassName="active" exact={true}>Dashboard</NavLink>
</li>
<li>
<NavLink to="/user" activeClassName="active">User PRofile</NavLink>
</li>
<li>
<NavLink to="/register" activeClassName="active">Register</NavLink>
</li>
</ul>
</nav>
</div>
)
}
}

Related

Getting null when invoking sibling component function

I am implementing a scrolling functionality on the same page when the Contact Us button is clicked. The Contact Us is contained in a child component (MyNavbar); when clicked, it will scroll to a fragment contained in another child component (MyContactForm), which is sibling of MyNavbar.
Here's the parent component:
// App.js
import React, { Component } from 'react';
import MyNavbar from './components/MyNavbar';
import MyContactForm from './components/MyContactForm';
export default class App extends Component {
constructor(props) {
super(props);
...
}
scrollToContactForm = () => {
this.refs.contactForm.scrollTo();
}
render() {
return (
<main>
<MyNavbar onClickToContactUs={ () => this.scrollToContactForm() } />
<MyContactForm ref="contactForm" />
</main>
);
}
}
And here are the two child components, MyNavbar
// MyNavbar.js
import React, { useState } from 'react';
import { Navbar, Nav, NavItem, NavLink } from 'reactstrap';
const MyNavbar = (props) => {
return (
<Navbar>
<Nav>
...
<NavItem>
<NavLink href="/products/"> Products </NavLink>
</NavItem>
<NavItem>
<NavLink href="/services/"> Services </NavLink>
</NavItem>
<NavItem>
<NavLink onClick={ () => props.onClickToContactUs() } href="#"> Contact Us </NavLink>
</NavItem>
</Nav>
</Navbar>
);
}
export default MyNavbar;
and MyContactForm:
// MyContactForm.js
import React, { Component } from 'react';
import { Form, ... } from 'reactstrap';
export default class MyContactForm extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
...
inquiry: ''
};
this.setEmail = this.setEmail.bind(this);
...
this.setInquiry = this.setInquiry.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.myRef = React.createRef();
}
setEmail(event) {
this.setState( { email: event.target.email } );
}
...
setInquiry(event) {
this.setState( { question: event.target.inquiry } );
}
handleSubmit(event) {
alert("Thank you for contacting us. We will respond to you shortly");
event.preventDefault();
}
scrollTo = () => window.scrollTo(0, this.myRef.current.offsetTop);
render() {
return (
<React.Fragment ref={this.myRef} >
<Form onSubmit={this.handleSubmit}>
...
</Form>
</React.Fragment>
);
}
}
The app runs, however when I click Contact Us, I get a message saying
this.myRef.current is null
How can I get this to work?
Here's what worked for me:
I replaced <React.Fragment> with a <div>. Putting the ref in <Form> doesn't work either, as it should be on a DOM node (HTML element, not React component). So MyContactForm.js becomes:
render() {
return (
<div ref={this.myRef} >
<Form onSubmit={this.handleSubmit}>
...
</Form>
</div>
);
}

Parent child component event handler in loop

I have created one child component and one parent component the child component gets called in from loop and event handlers also get called from prop but when i click on navlink from child component every click event is called
if first navlink is called then only handler for first navlink should be called.
import { NavLink } from "react-router-dom";
import React, { Component } from "react";
import axios from "axios";
// import SearchComponent from "../Common/SearchComponent";
// import Navbar from "../Common/Navbar";
import { CompanyId } from "../../locales/global.json";
import CategoryComponent from "./CategoryComponent";
class CategoryPageComponent extends Component {
state = {
Categories: []
};
componentDidMount() {
if (this.state.Categories.length === 0) this.bindCategoryData();
}
onCategorySearch = e => {
if (this.state.Categories.length === 0 && e === "")
this.bindCategoryData(0);
};
bindCategoryData = () => {
var $this = this;
axios
.post(
"http://fstrumplifyml.azurewebsites.net/api/ApiCategory/GetCategories",
{
companyid: CompanyId,
languageid: 1
}
)
.then(function(response) {
$this.setState({
Categories: response.data.Data
});
});
};
UpdateSubCategories = (categories, categoryId) => {
console.log(categories);
};
renderCategoryComponent = category => {
return (
<CategoryComponent
Category={category}
OnUpdateSubCategories={this.UpdateSubCategories.bind(this)}
/>
);
};
render() {
return (
<React.Fragment>
{/* <Navbar /> */}
<section className="space--sm">
<div className="container">
{/* <SearchComponent onSearch={this.onCategorySearch} /> */}
<div className="row">
{this.state.Categories.map(this.renderCategoryComponent)}
</div>
</div>
</section>
</React.Fragment>
);
}
}
export default CategoryPageComponent;
import React, { Component } from "react";
import { NavLink } from "react-router-dom";
class CategoryComponent extends Component {
render() {
return (
<div className="masonry__item col-md-4 filter-computing">
<div className="product">
<NavLink
to={this.props.OnUpdateSubCategories(
this.props.Category.SubCategories,
this.props.Category.CategoryId
)}
>
<img
alt={this.props.Category.CategoryName}
className="ProductImage"
src={this.props.Category.ImageUrl}
/>
</NavLink>
<NavLink
className="block"
to={this.props.OnUpdateSubCategories(
this.props.Category.SubCategories,
this.props.Category.CategoryId
)}
>
<div>
<h5>{this.props.Category.CategoryName}</h5>
</div>
</NavLink>
</div>
</div>
);
}
}
export default CategoryComponent;
<NavLink
className="block"
to={this.props.OnUpdateSubCategories(
this.props.Category.SubCategories,
this.props.Category.CategoryId
)}
>
when you put a function call inside props, it will be called everytime when render() runs
you can use arrow functions in props
to={
()=>{this.props.OnUpdateSubCategories(
this.props.Category.SubCategories,
this.props.Category.CategoryId
)}
}
but a function instance will be created in every render
so it is better to create a function inside CategoryComponent

access state of react component from other component

I have the following spinner
import React, { Component } from 'react'
import './Spinner.scss'
export default class Spinner extends Component {
constructor(props) {
super(props);
this.state = {showLoading: true};
}
render () {
return (
<div className="spinner">
<div className="double-bounce1"></div>
<div className="double-bounce2"></div>
</div>
)
}
}
and from other component I would like to show or hide this spinner here is the code of the component:
import React, { Component } from 'react'
import RTable from '../../../components/RTable/RTable'
import Spinner from '../../../components/Spinner/Spinner'
import CsvDownload from '../containers/CsvDownloadContainer'
export default class Table extends Component {
_renderBreadcrumb () {
const { breadcrumb, handleBreadcrumbClick } = this.props
return (
<ol className="breadcrumb">
{(breadcrumb || []).map(el => {
return (
<li key={el.datasetKey}>
<a onClick={() => { handleBreadcrumbClick(el.granularity, el.datasetKey, el.datasetKeyHuman) }}>
{el.datasetKeyHuman}
</a>
</li>
)
})}
</ol>
)
}
render () {
const { datasetRows, columns, metadata, showLoading } = this.props
return (
<div className="row">
<div className="col-sm-12">
{this._renderBreadcrumb()}
<RTable rows={datasetRows} columns={columns} metadata={metadata} />
{ this.props.showLoading ? <Spinner /> : null }
<CsvDownload />
</div>
</div>
)
}
}
as you can see I trying to show or hide the spinner using:
{ this.props.showLoading ? <Spinner /> : null }
but I'm always getting undefinde. Some help please.
You have to move this
constructor(props) {
super(props);
this.state = {showLoading: true};
}
to your <Table /> component, otherwise you access showLoading from <Table />'s props, but it is not passed from anywhere.
Then change also
{ this.props.showLoading ? <Spinner /> : null }
to
{ this.state.showLoading ? <Spinner /> : null }
To show / hide <Spinner /> just call this.setState({ showLoading: Boolean }) in your <Table /> component.

React implementing react-sticky

I am trying to implement the following: https://www.npmjs.com/package/react-sticky
in my code as follow:
import React from 'react';
import Video from './../video.jsx';
import Overview from './overview.jsx';
import Photography from './photography.jsx';
import Details from './details.jsx';
import Cast from './cast.jsx';
import porgectsCollection from './../../data/projectInfo.js';
import { StickyContainer, Sticky } from 'react-sticky';
class Nav extends React.Component {
constructor(props) {
super(props);
this.state = {
mobileMenu: false
};
}
showMobileMenu () {
this.setState({ mobileMenu: !this.state.mobileMenu });
}
render () {
let links = this.props.project.links.map(function(el, i){
return <li key={i}>{el}</li>;
});
const open = this.state.mobileMenu ? ' open' : '';
return (
<StickyContainer>
<span onClick={this.showMobileMenu.bind(this)} className="mobile-trigger">X</span>
<Sticky topOffset={100} stickyClassName="sticky-nav">
<nav className={"secondary-nav" + open}>
<ul>
{links}
</ul>
</nav>
</Sticky>
</StickyContainer>
);
}
}
class SingleProject extends React.Component {
getProjectDataFromUrl() {
return porgectsCollection.filter(el => el.title === this.props.params.id);
}
render () {
let data = this.getProjectDataFromUrl(),
project = data[0];
console.log(project);
return (
<section className="project-page">
<Video project={project} />
<Nav project={project} />
<Overview project={project} />
<Photography project={project} />
<Details project={project} />
<Cast project={project} />
</section>
);
}
}
export default SingleProject;
I would hope that when "Sticky" reached 100px from the top it would get a custom class "sticky-nav" applied to it. However the nav keeps on scrolling without getting stuck at all. I can see the divs applied around my markup with the extra padding but no more then that.
URL project: https://github.com/WebTerminator/aldemar,
file in question is singleProject.jsx
import React from 'react';
import Video from './../video.jsx';
import Overview from './overview.jsx';
import Photography from './photography.jsx';
import Details from './details.jsx';
import Cast from './cast.jsx';
import porgectsCollection from './../../data/projectInfo.js';
import { StickyContainer, Sticky } from 'react-sticky';
class Nav extends React.Component {
constructor(props) {
super(props);
this.state = {
mobileMenu: false
};
}
showMobileMenu () {
this.setState({ mobileMenu: !this.state.mobileMenu });
}
render () {
let links = this.props.project.links.map(function(el, i){
return <li key={i}>{el}</li>;
});
const open = this.state.mobileMenu ? ' open' : '';
return (
<Sticky stickyClassName="sticky-nav" topOffset={-100}>
<span onClick={this.showMobileMenu.bind(this)} className="mobile-trigger">X</span>
<nav className={"secondary-nav" + open}>
<ul>
{links}
</ul>
</nav>
</Sticky>
);
}
}
class SingleProject extends React.Component {
getProjectDataFromUrl() {
return porgectsCollection.filter(el => el.title === this.props.params.id);
}
render () {
let data = this.getProjectDataFromUrl(),
project = data[0];
return (
<section className="project-page">
<StickyContainer>
<Video project={project} />
<Nav project={project} />
<Overview project={project} />
<Photography project={project} />
<Details project={project} />
<Cast project={project} />
</StickyContainer>
</section>
);
}
}
export default SingleProject;

React - how to pass state to another component

I'm trying to figure out how to notify another component about a state change. Let's say I have 3 components - App.jsx,Header.jsx,and SidebarPush.jsx and all I'm simply trying to do is toggle a class with an onClick.
So the Header.jsx file will have 2 buttons when clicked will toggle the states to true or false. The other 2 components App.jsx and Header.jsx will need to know about these state changes so they can toggle a class
whenever those states change.
App.jsx
import React from 'react';
import Header from 'Header';
import classNames from "classnames";
import SidebarPush from 'SidebarPush';
import PageWrapper from 'PageWrapper';
var MainWrapper = React.createClass({
render: function() {
return (
<div className={classNames({ 'wrapper': false, 'SidebarPush-collapsed': !this.state.sidbarPushCollapsed })}>
<Header/>
<SidebarPush/>
<PageWrapper>
{this.props.children}
</PageWrapper>
</div>
);
}
});
module.exports = MainWrapper;
Header.jsx
import React from 'react';
import ReactDom from 'react-dom';
class Header extends React.Component {
constructor() {
super();
this.state = {
sidbarPushCollapsed: false,
profileCollapsed: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
sidbarPushCollapsed: !this.state.sidbarPushCollapsed,
profileCollapsed: !this.state.profileCollapsed
});
}
render() {
return (
<header id="header">
<ul>
<li>
<button type="button" id="sidbarPush" onClick={this.handleClick} profile={this.state.profileCollapsed}>
<i className="fa fa-bars"></i>
</button>
</li>
<li>
<button type="button" id="profile" onClick={this.handleClick}>
<i className="icon-user"></i>
</button>
</li>
</ul>
<ul>
<li>
<button id="sidbarOverlay" onClick={this.handleClick}>
<i className="fa fa-indent"></i>
</button>
</li>
</ul>
</header>
);
}
};
module.exports = Header;
SidebarPush.jsx
import React from 'react';
import ReactDom from 'react-dom';
import classNames from "classnames";
class SidebarPush extends React.Component {
render() {
return (
<aside className="sidebarPush">
<div className={classNames({ 'sidebar-profile': true, 'hidden': !this.state.pagesCollapsed })}>
....
</div>
<nav className="sidebarNav">
....
</nav>
</aside>
);
}
}
export default SidebarPush;
Move all of your state and your handleClick function from Header to your MainWrapper component.
Then pass values as props to all components that need to share this functionality.
class MainWrapper extends React.Component {
constructor() {
super();
this.state = {
sidbarPushCollapsed: false,
profileCollapsed: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
sidbarPushCollapsed: !this.state.sidbarPushCollapsed,
profileCollapsed: !this.state.profileCollapsed
});
}
render() {
return (
//...
<Header
handleClick={this.handleClick}
sidbarPushCollapsed={this.state.sidbarPushCollapsed}
profileCollapsed={this.state.profileCollapsed} />
);
Then in your Header's render() method, you'd use this.props:
<button type="button" id="sidbarPush" onClick={this.props.handleClick} profile={this.props.profileCollapsed}>

Resources