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}>
Related
i have 2 components & i want to pass data by input from parent component to the child component, i got stuck to pass the data to the child component and display or render the child component
is there any technique to pass the props to the child components & render it?
and when i see the console on developer tools there is nothing wrong/happen except the console.log code
import React, {Component} from 'react';
import ReactDOM from'react-dom';
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
//props.keyword
const keyword = 'avenger';
const API = `https://api.themoviedb.org/3/search/movie?api_key=341c549444f65b6a022eea5fc24f5b77&language=en-US&query=${keyword}&page=1&include_adult=false`;
const DEFAULT_QUERY = 'redux';
class MovieSearch extends Component{
constructor(props){
super(props);
this.state={
movies:[]
}
}
componentDidMount(){
fetch(API + DEFAULT_QUERY)
.then(response=>response.json())
.then(data=>{
this.setState({movies:data.results})
})
}
render(){
const {movies} = this.state;
return(
<div className="row container">
{movies.map(movie =>
<div className="col-md-4">
<div className="card" style={{width: '15rem'}} key={movie.id}>
<img src="" className="card-img-top" alt="..."/>
<div className="card-body">
<h5 className="card-title">{movie.title}</h5>
<p>{movie.id}</p>
<Link to="/movie/detail">Detail</Link>
</div>
</div>
</div>
)}
</div>
)
}
}
export default MovieSearch;
import React, { Component } from"react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
//component
import Jumbotron from "./jumbotron";
import MovieList from "./movieList";
import MovieSearch from"./MovieSearch";
import Movie from"./Movie";
//HOC
const idMovie = (WrappedComponent)=>{
class IdMovie extends Component{
constructor(props){
super(props)
this.state={}
}
}
}
//route start here
class Main extends Component{
constructor(props){
super(props)
this.state={
keyword: ''
}
this.handleInput = this.handleInput.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleInput(event){
this.setState({keyword:event.target.value})
}
handleSubmit(event){
console.log(this.state.keyword)
event.preventDefault()
}
render(){
return (
<Router>
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
<Link className="navbar-brand" to="/">Carifilm</Link>
<button className="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="navbar-nav mr-auto">
<li className="nav-item active">
<Link className="nav-link" to="/">Home <span className="sr-only">(current)</span></Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/movies">Movies</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/search-movie">Cari film</Link>
</li>
</ul>
<form className="form-inline my-2 my-lg-0" onSubmit={this.handleSubmit}>
<input className="form-control mr-sm-2"
type="search" placeholder="Search"
aria-label="Search"
value={this.state.keyword}
onChange={this.handleInput}/>
<button className="btn btn-outline-success my-2 my-sm-0" type="submit" value="submit">Search</button>
</form>
</div>
</nav>
<Switch>
<Route exact path="/">
<Home/>
</Route>
<Route path="/movies">
<Movies/>
</Route>
<Route path="/movie/detail">
<MovieDetail/>
</Route>
<Route path="/search-movie">
<CariFilm/>
</Route>
</Switch>
</Router>
)}
}
export default Main;
function Home(){
return(
<Jumbotron/>
)
}
function Movies(){
return(
<MovieList/>
)
}
function MovieDetail(){
return(
<Movie/>
)
}
function CariFilm(props){
return(
<MovieSearch/>
)
}
Please check this example. Here I passed items into my Child Component and displayed items in child component.
Parent
import React, {Component, useEffect, useState} from 'react';
import {PChild} from "./PChild";
export class Parent extends Component {
constructor(props) {
super(props);
this.state = {items: []};
}
componentDidMount() {
let json = [];
json.push({track: { id:1, name: 'Black Sabbath, from the album Black Sabbath (1970)'}});
json.push({track: { id:2, name: 'Blackfield, from the album Blackfield (2004)'}});
json.push({track: { id:3, name: 'Bo Diddley, from the album Bo Diddley (1958)'}});
json.push({track: { id:4, name: 'Damn Yankees, from the album Damn Yankees (1990)'}});
this.setState({items: json});
}
render() {
return (
<div>
<PChild items={this.state.items} name="Khabir"/>
</div>
);
}
}
Child
import React, {useEffect, useState} from 'react';
// Parent to Child communication
export class PChild extends React.Component {
componentDidUpdate() {
console.log(this.props.items);
console.log(this.props.name);
}
render() {
return (
<div>
{this.props.items.map((item, i) => {
return <li key={item.track.id}>
{(`Item ${i+1} - ${item.track.name}`)}
</li>
})}
</div>
);
}
}
The main form page where the login and Sign up form will live give undefined. How can I tried to render forms to this MainScreen.js file when the user click on in the navbar.
MainScreen.js (Where the forms are live)
import React from 'react';
import RegisterBox from '../Forms/Register'
import LoginBox from '../Forms/Login'
// This is the page for form to live on
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoginOpen: true,
isRegisterOpen: false
};
}
render() {
return (
<div>
<div className="root-container">
{this.state.isLoginOpen && <LoginBox/>}
{this.state.isRegisterOpen && <RegisterBox/>}
</div>
</div>
)
}
}
export default App;
Navigation.js (Where the button is clicked but are not being defined anywhere so cannot find the page to take me to the forms.)
import React from 'react';
import { BrowserRouter as Router, Link } from 'react-router-dom';
import Dropdown from "../components//pages/dropdowns/dropdowns";
import hamburger from "../images/menu.svg"
class Navigation extends React.Component {
constructor(props) {
super(props);
this.state = {
isExpanded: false
};
}
handleToggle(e) {
e.preventDefault();
this.setState(prevState => ({
isExpanded: !prevState.isExpanded, // negate the previous expanded state
}));
}
render() {
const { isExpanded } = this.state;
return (
<Router>
<div className="NavbarContainer">
<div className="mobilecontainer LeftNav">
<h2 className="BrandName LeftNav mobileboxmenu inline FarRight">Kommonplaces</h2>
<div className="hamburger inlinev" >
<img
onClick={e => this.handleToggle(e)}
alt="menubtn"
src={hamburger}
/>
</div>
</div>
<ul className={`NavBar collapsed ${isExpanded ? "is-expanded" : ""}`}>
<Dropdown/>
<li className="RightNav"><Link to="/">Host Your Space</Link></li>
<li className="RightNav"><Link to="/">About Us</Link></li>
<li className="RightNav"><Link to="/">Contact Us</Link></li>
<div className="btnflexright">
<button
className={"controller " + (this.state.isLoginOpen
? "selected-controller"
: "")}
onClick={this
.props
.showLoginBox}>
Login
</button>
<button
className={"controller " + (this.state.isRegisterOpen
? "selected-controller"
: "")}
onClick={this
.props
.showRegisterBox}>
Sign up
</button>
</div>
</ul>
</div>
</Router>
);
}
}
export default Navigation;
You need to do a couple things:
add some couple methods to your App component to set the state.
import and render the Navigation component in your App component (so you can pass your methods in as functions. you could use the Context API but it's more complicated...)
pass those methods as functions to your Navigation component (and pass the state in as props if you need them too)
call the methods through the props of your Navigation component
In your App component:
import React from 'react';
import RegisterBox from '../Forms/Register'
import LoginBox from '../Forms/Login'
import Navigation from './Navigation'
// This is the page for form to live on
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoginOpen: true,
isRegisterOpen: false
};
}
/*
* 1. add these two methods here to modify the state of `App`
*/
showLoginBox() {
this.setState({ isLoginOpen: true });
}
showRegisterBox() {
this.setState({ isRegisterOpen: true });
}
render() {
return (
<div>
<div className="root-container">
{
/* 2. render the `Navigation` component in `App` */
/* 3. pass your methods in as function props to `Navigation` */
}
<Navigation
isLoginOpen={this.state.isLoginOpen}
isRegisterOpen={this.state.isRegisterOpen}
showLoginBox={this.showLoginBox.bind(this)}
showRegisterBox={this.showRegisterBox.bind(this)}
/>
{this.state.isLoginOpen && <LoginBox/>}
{this.state.isRegisterOpen && <RegisterBox/>}
</div>
</div>
)
}
}
export default App;
Now you can call those methods from your Navigation component...
{ /* 4. call the prop functions in `Navigation` component */ }
<button
className={"controller " + (this.props.isLoginOpen ? "selected-controller" : "")}
onClick={this.props.showLoginBox}>
Login
</button>
<button
className={"controller " + (this.props.isRegisterOpen ? "selected-controller" : "")}
onClick={this.props.showRegisterBox}>
Sign up
</button>
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>
)
}
}
While trying to toggle between two child components, I need to have the trigger button in the child component, and pass the click function through the child component to the in order to toggle the other child component. I'm not sure how to push the props from the child to the parent in order to trigger the toggle.
Parent component
import React from 'react'
import CancelOffer from '../CancelPages/CancelOffer'
import CancelWarning from '../CancelPages/CancelWarning'
class Cancel extends React.Component {
constructor() {
super()
this.state = {
isHidden: true
}
}
toggleOffer() {
this.setState({
isHidden: !this.state.isHidden
})
}
render() {
return (
<div className = 'cancel'
style = {{backgroundImage: `url(${this.props.backgroundImage})`}} >
<div className = 'container' >
{!this.state.isHidden &&
<CancelOffer { ...this.props}/>
}
{this.state.isHidden &&
<CancelWarning { ...this.props}/>
}
{this.state.isHidden &&
<button onClick = {this.toggleOffer.bind(this)} > Click < /button>
}
</div>
</div>
)
}
}
export default Cancel
Child component
import React from 'react'
import SvgIcon from '../SvgIcon/SvgIcon'
import './CancelWarning.scss'
function CancelOffer (props) {
const content = props.config.contentStrings
return (
<div className='cancel-warning'>
<h2 className='heading md'>heading</h2>
<p className='subpara'>subheading</p>
<div className='losses'>
<ul>
<li>text</li>
<li>text</li>
<li>text</li>
</ul>
</div>
<div className='footer-links'>
<a href='/member' className='btn btn--primary btn--lg'>continue</a>
<a href='/cancel' className='cancel-link'>Cancel</a>
//NEED TO HAVE BUTTON HERE AND PASS PROPS TO PARENT TO TOGGLE VIEW
{this.state.isHidden &&
<button onClick = {this.toggleOffer.bind(this)}>Click</button>
}
</div>
</div>
)
}
export default CancelOffer
You can just pass it like regular param. Also, you can use arrow functions instead of binding.
Parent component
import React from 'react'
import CancelOffer from '../CancelPages/CancelOffer'
import CancelWarning from '../CancelPages/CancelWarning'
class Cancel extends React.Component {
constructor() {
super()
this.state = {
isHidden: true
}
this.toggleOffer = this.toggleOffer.bind(this);
}
toggleOffer() {
this.setState({
isHidden: !this.state.isHidden
})
}
render() {
const { isHidden } = this.state
return (
<div className = 'cancel'
style = {{backgroundImage: `url(${this.props.backgroundImage})`}} >
<div className = 'container' >
{!isHidden &&
<CancelOffer toggleOffer={this.toggleOffer} isHidden={isHidden}/>
}
{isHidden &&
<CancelWarning toggleOffer={this.toggleOffer} isHidden={isHidden}/>
}
{isHidden &&
<button onClick = {this.toggleOffer}> Click </button>
}
</div>
</div>
)
}
}
export default Cancel
Child component
import React from 'react'
import SvgIcon from '../SvgIcon/SvgIcon'
import './CancelWarning.scss'
function CancelOffer (props) {
const content = props.config.contentStrings
return (
<div className='cancel-warning'>
<h2 className='heading md'>heading</h2>
<p className='subpara'>subheading</p>
<div className='losses'>
<ul>
<li>text</li>
<li>text</li>
<li>text</li>
</ul>
</div>
<div className='footer-links'>
<a href='/member' className='btn btn--primary btn--lg'>continue</a>
<a href='/cancel' className='cancel-link'>Cancel</a>
{props.isHidden &&
<button onClick = {props.toggleOffer}>Click</button>
}
</div>
</div>
)
}
export default CancelOffer
I am trying to make a todoList by ReactJS. I want to delete an item by its id, but when I console.log(id), it returns undefined. Here is my code
App.js:
import React, { Component } from 'react';
import './App.css';
import Header from './Components/header';
import InputTodo from './Components/todoInput';
class App extends Component {
constructor(props){
super(props);
this.state={
todos:[
{id:0,text:'Make dinner'},
{id:1,text:'Fold the laundary'},
{id:2,text:'Do homework'}
]
}
}
addHere=(text)=>{
this.setState({
todos:this.state.todos.concat([text])
})
}
removeHere=(id)=>{
console.log(id);
// let arr=this.state.todos;
// let index=arr.findIndex((x)=>x.id===id);
// console.log(index);
}
render() {
return (
<div className='todo-wrapper'>
<Header/>
<InputTodo todoText='Type Here...' addTodo={this.addHere}/>
<div>
{this.state.todos.map((value,key)=>{
return (
<div className='row myList' key={key}>
<p className='col-xs-10'> {value.text}-{value.id} </p>
<button className='btn btn-danger pull-right col-xs-2' onClick={this.removeHere(value.id)}>Delete</button>
</div>
)})}
</div>
</div>
);
}
}
export default App;
The following is InputTodo.js:
import React, {Component} from 'react';
import '../App.css';
export default class InputTodo extends Component{
constructor(props){
super(props);
this.state={
todoInput:{
id:2,
text:''
}
}
}
handleSubmit=(e)=>{
if(this.refs.title.value===''){
alert('You must input something');
}
else{
this.state.todoInput={
id:this.state.todoInput.id+1,
text:this.refs.title.value
};
this.setState(this.state);
this.props.addTodo(this.state.todoInput);
this.refs.title.value='';
}
e.preventDefault();
}
render(){
return(
<form className='input-group' onSubmit={this.handleSubmit}>
<input type='text' ref="title" className='form-control'placeholder={this.props.todoText}/>
<span className='input-group-btn'>
<input type='submit' value='Submit' className='btn btn-primary' />
</span>
</form>
);
}
}
While FuzzyTree's answer will work, a cleaner approach would be extracting the todo item's JSX into its own component. This would have the added benefit of not creating a new function for the button's onClick prop every time App's render function gets called.
The component might look like this:
// TodoItem
class TodoItem extends Component {
handleRemove = () => this.props.onRemove(this.props.id)
render() {
return (
<div className='row myList'>
<p className='col-xs-10'> {this.props.text}-{this.props.id} </p>
<button className='btn btn-danger pull-right col-xs-2' onClick={this.handleRemove}> Delete </button>
</div>
)
}
}
// App render
render() {
return (
<div className='todo-wrapper'>
<Header/>
<InputTodo todoText='Type Here...' addTodo={this.addHere}/>
<div>
{this.state.todos.map(({ id, text }, key) =>
<TodoItem key={key} id={id} text={text} onRemove={this.removeHere} />
)}
</div>
</div>
);
}