React Child and Parent Components state change on click - reactjs

I am trying to make a click on a button of a child React component change the Boolean state of the child and of its parent.
The issue here is that it has to change states of both components.
Here is a link for the code I am trying to get working:
https://stackblitz.com/edit/child-to-parent-state-pass-bkmvwc?file=Child.js
The requirement is to click the hamburger button and it changes the state of the child component (the actual hamburger button) and its parent component.
Thank you!

I would not recommend doing what you are doing.
But, knowing nothing of your background I will only answer your question.
In parent.js you are missing the bind to this.
Use this line instead and check if this works

Why don't you manage the state from the parent component like:
Parent.js:
import React from 'react'
import { Link } from 'react-router-dom'
import { initializeIcons } from '#uifabric/icons';
import Hamburger from './Child'
initializeIcons();
export default class NavBar extends React.Component {
constructor(props) {
super(props);
this.state = {
opened: false
};
}
handleCounter = () => {
this.setState({ opened: !this.state.opened });
}
render() {
return (
<Hamburger
opened={this.state.opened}
handleCounter={this.handleCounter}
/>
);
}
}
Child.js
import React from 'react'
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { initializeIcons } from '#uifabric/icons';
initializeIcons();
export default class Hamburger extends React.Component {
constructor(props) {
super(props);
}
updateParent() {
this.props.handleCounter(this.state);
}
render() {
return (
<IconButton
checked={this.props.opened}
iconProps={{ iconName: (this.props.opened ? 'Cancel' : 'GlobalNavButton'), style: { fontSize: 35 } }}
className="hamburger mobile-only"
title="Open Global Navigation"
ariaLabel="Open Global Navigation"
styles={{
root: {
padding: '0',
border: 'none',
background: 'transparent !important'
}
}}
onClick={this.props.handleCounter}
/>
);
}
}
PS: I removed the comments for readability

You would only change the state of the parent. The child would just read the props that are passed to it.
Parent component
constructor(props){
super(props)
this.state = {
hamburgerOpen: false
}
}
handleHamburgerToggle = () => {
let { hamburgerOpen } = this.state;
this.setState({
hamburgerOpen: !hamburgerOpen
})
}
render() {
let { hamburgerOpen } = this.state;
return (
<Child
hamburgerOpen={hamburgerOpen}
handleHamburgerToggle={this.handleHamburgerToggle}
/>
)
}
Child will have access to the props passed to it. You can make the Hamburger a functional component as well since it isn't concerned about the current state, only the parent is.
hamburgerOpen and toggleHamburgerOpen
Child Component
const { handleHamburgerToggle } = this.props;
return {
<div>
<div
onClick={() => handleHamburgerToggle()}
>
Click me to toggle hamburger
</div>
</div>
}

Related

How to call functional child component method from class parent component

I have a class based parent component like below
import React from "react";
import ReactDOM from "react-dom";
import FunChild from "./FunChild";
class App extends React.Component {
constructor(props) {
super(props);
this.childRef = React.createRef();
this.parentmethodFun = this.parentmethodFun.bind(this);
}
parentmethodFun() {
this.childRef.current.childmethod();
}
render() {
return (
<div>
<FunChild />
<button type="button" onClick={this.parentmethodFun}>
function
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
the funChild.js file
import React from "react";
function FunChild(props) {
childmethod() {
console.log("child method is called");
}
return (<div>This is child ...!</div>);
}
export default FunChild;
if that child was a class component I can very easily use ref={this.childRef} to access child method.
But, it's a functional component and It was giving lot of problems. Can anyone please help me on this.
reference project link https://codesandbox.io/s/react-playground-forked-74xzn?file=/index.js
You should avoid this kind of relation because it is not the way how react works. In React you should pass everything from up to bottom. But if you reale want to achieve something like this you can use reference forwarding and imperative handler hook. E.g:
import { Component, forwardRef, createRef, useImperativeHandle } from "react";
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
childMethod() {
console.log("child method is called");
}
}));
return <div>This is child ...!</div>;
});
class App extends Component {
constructor(props) {
super(props);
this.childRef = createRef();
this.parentmethodFun = this.parentmethodFun.bind(this);
}
parentmethodFun() {
this.childRef.childMethod();
}
render() {
return (
<div>
<Child ref={(ref) => (this.childRef = ref)} />
<button type="button" onClick={this.parentmethodFun}>
function
</button>
</div>
);
}
}
Personally, i think you should rethink your application structure as it is very likely that there is a better solution than this trick.

Display element based on event fired and props passed in

I am trying, to manipulate another element, by, passing props directly to it, and then have it display itself. If I pass true/false.
Live running code:
https://codesandbox.io/s/keen-dan-rt0kj
I don't know if it's possible to have a system of objects, and based on an event, tell a parent to display a child.
App.js
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
state = {
display: false
};
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}
./components/Content.js:
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
display: props.display
};
}
render() {
const { display } = this.state;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Goal:
I want to based on a state, and based on fired event, display an element that already in store of root.
EDIT: I am aware that, this exists and can be used: import PropTypes from 'prop-types', however, I am not sure this is good practice, since it requires some parent or some other component to implement the props.
JUST Tried:
App:
<Content display={this.state.display} content={"Hello World"} />
Content:
<h3>{this.state.content}</h3>
It seems the passed in text, stored in Content state = {content: props.content} does get displayed, wheres, the boolean value does not work directly. Is there something wrong with sending in a bool ?
try this in your Content Component
export default class Content extends React.Component {
constructor(props) {
super();
this.state = {
};
}
render() {
return (
<>
{this.props.display?(
<div
id="mydiv"
className="mydiv"
>
<h3>A simple div</h3>
</div>
):null}
</>
);
}
}
The reason this may not be working is because you are initiating the state in a way that does not connect the display props after the component is initialized. This means that after the Content component is "constructed", the state of the Content and it's parent are not linked. This is because the constructor() function is only run once to initialize the state.
The best option you have is to not use the internal state of the Content component. Rather than initializing state with the display prop, just use the display prop in your render function.
Trying something like this might work
import React from "react";
export default class Content extends React.Component {
constructor(props) {
super(props);
}
render() {
const { display } = this.props;
return (
<div
id="mydiv"
className="mydiv"
style={{ display: display ? "block" : "none" }}
>
<h3>A simple div</h3>
</div>
);
}
}
Also I would reccommend using state in the root:
import React from "react";
import "./styles.css";
import Content from "./components/Content";
export default class App extends React.Component {
constructor(props) {
super();
state = {
display: false
};
}
render() {
return (
<div className="App">
<button onClick={() => this.setState({ display: !this.state.display })}>
Display div
</button>
<Content display={this.state.display} />
</div>
);
}
}

In React JS, how do I tell a parent component that something has happened in the child?

I have a React JS app with a simple hierarchy: ContainingBox wraps two InfoBox components. in this example, I simply want to tell the ContainingBox component 1) that something has been clicked, and 2) which InfoBox (by label name) has been clicked?
Here is some basic code that works in my browser to get this question up & running. All it does it console.log when you click onto one of the InfoBox elements on the page.
Essentially, what I am trying to achieve is that I want the ContainingBox to change state (specifically, border color as rendered) when one of the child InfoBox elements is clicked.
I'm not sure what the right direction here is.
I built this app with React 16.10.2, but I would be happy to read answers pointing me towards the latest 'React way' of thinking.
import React from 'react';
import styled from 'styled-components'
import './App.css';
const StyledInfoBox = styled.div`
width: 100px;
border: solid 1px green;
padding: 10px;
cursor: pointer;
`
class InfoBox extends React.Component {
constructor({blurb}) {
super()
this.state = {
label: (blurb ? blurb.label : ""),
}
this.selectBox = this.selectBox.bind(this);
}
selectBox(e) {
e.preventDefault()
console.log("selectBox")
// how do I tell the ContainingBox component 1) that something has been clicked,
// and 2) which InfoBox (by label name) has been clicked?
}
render() {
const {label} = this.state
return (
<StyledInfoBox onClick={this.selectBox} >
{label}
</StyledInfoBox>
)
}
}
class ContainingBox extends React.Component {
render() {
return (
<div>
<InfoBox key={1} blurb={{label: "Aenean malesuada lorem"}} />
<InfoBox key={2} blurb={{label: "Lorem Ipsum dor ameet"}} />
</div>
)
}
}
function App() {
return (
<div className="App">
<ContainingBox />
</div>
)
}
export default App;
You pass a callback from the parent component to child component via the props.
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
changeNameTo = (newName) => this.setState({name: newName})
render() {
return (
<div>
<h1>{this.state.name}</h1>
<p>
<Child callbackExample={this.changeNameTo} />
</p>
</div>
);
}
}
Then you have your Child component.
class Child extends Component {
render() {
return(
<div>
<button onClick={() => this.props.callbackExample("Doggos")}>
Click me
</button>
</div>)
}
}
When you click the button, the callback is invoked setting the state of the parent, which is then reflected when the parent re-renders.

Watching state from child component React with Material UI

New to React. Just using create-react-app and Material UI, nothing else.
Coming from an Angular background.
I cannot communicate from a sibling component to open the sidebar.
I'm separating each part into their own files.
I can get the open button in the Header to talk to the parent App, but cannot get the parent App to communicate with the child LeftSidebar.
Header Component
import React, {Component} from 'react';
import AppBar from 'material-ui/AppBar';
import IconButton from 'material-ui/IconButton';
import NavigationMenu from 'material-ui/svg-icons/navigation/menu';
class Header extends Component {
openLeftBar = () => {
// calls parent method
this.props.onOpenLeftBar();
}
render() {
return (
<AppBar iconElementLeft={
<IconButton onClick={this.openLeftBar}>
<NavigationMenu />
</IconButton>
}
/>
);
}
}
export default Header;
App Component -- receives event from Header, but unsure how to pass dynamic 'watcher' down to LeftSidebar Component
import React, { Component } from 'react';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import RaisedButton from 'material-ui/RaisedButton';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
// components
import Header from './Header/Header';
import Body from './Body/Body';
import Footer from './Footer/Footer';
import LeftSidebar from './LeftSidebar/LeftSidebar';
class App extends Component {
constructor() {
super() // gives component context of this instead of parent this
this.state = {
leftBarOpen : false
}
}
notifyOpen = () => {
console.log('opened') // works
this.setState({leftBarOpen: true});
/*** need to pass down to child component and $watch somehow... ***/
}
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<Header onOpenLeftBar={this.notifyOpen} />
<Body />
<LeftSidebar listenForOpen={this.state.leftBarOpen} />
<Footer />
</div>
</MuiThemeProvider>
);
}
}
export default App;
LeftSidebar Component - cannot get it to listen to parent App component - Angular would use $scope.$watch or $onChanges
// LeftSidebar
import React, { Component } from 'react';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
import IconButton from 'material-ui/IconButton';
import NavigationClose from 'material-ui/svg-icons/navigation/close';
class LeftNavBar extends Component {
/** unsure if necessary here **/
constructor(props, state) {
super(props, state)
this.state = {
leftBarOpen : this.props.leftBarOpen
}
}
/** closing functionality works **/
close = () => {
this.setState({leftBarOpen: false});
}
render() {
return (
<Drawer open={this.state.leftBarOpen}>
<IconButton onClick={this.close}>
<NavigationClose />
</IconButton>
<MenuItem>Menu Item</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
</Drawer>
);
}
}
export default LeftSidebar;
Free your mind of concepts like "watchers". In React there is only state and props. When a component's state changes via this.setState(..) it will update all of its children in render.
Your code is suffering from a typical anti-pattern of duplicating state. If both the header and the sibling components want to access or update the same piece of state, then they belong in a common ancestor (App, in your case) and no where else.
(some stuff removed / renamed for brevity)
class App extends Component {
// don't need `constructor` can just apply initial state here
state = { leftBarOpen: false }
// probably want 'toggle', but for demo purposes, have two methods
open = () => {
this.setState({ leftBarOpen: true })
}
close = () => {
this.setState({ leftBarOpen: false })
}
render() {
return (
<div className="App">
<Header onOpenLeftBar={this.open} />
<LeftSidebar
closeLeftBar={this.close}
leftBarOpen={this.state.leftBarOpen}
/>
</div>
)
}
}
Now Header and LeftSidebar do not need to be classes at all, and simply react to props, and call prop functions.
const LeftSideBar = props => (
<Drawer open={props.leftBarOpen}>
<IconButton onClick={props.closeLeftBar}>
<NavigationClose />
</IconButton>
</Drawer>
)
Now anytime the state in App changes, no matter who initiated the change, your LeftSideBar will react appropriately since it only knows the most recent props
Once you set the leftBarOpen prop as internal state of LeftNavBar you can't modify it externally anymore as you only read the prop in the constructor which only run once when the component initialize it self.
You can use the componentWillReceiveProps life cycle method and update the state respectively when a new prop is received.
That being said, i don't think a Drawer should be responsible for being closed or opened, but should be responsible on how it looks or what it does when its closed or opened.
A drawer can't close or open it self, same as a light-Ball can't turn it self on or off but a switch / button can and should.
Here is a small example to illustrate my point:
const LightBall = ({ on }) => {
return (
<div>{`The light is ${on ? 'On' : 'Off'}`}</div>
);
}
const MySwitch = ({ onClick, on }) => {
return (
<button onClick={onClick}>{`Turn the light ${!on ? 'On' : 'Off'}`}</button>
)
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
lightOn: false
};
}
toggleLight = () => this.setState({ lightOn: !this.state.lightOn });
render() {
const { lightOn } = this.state;
return (
<div>
<MySwitch onClick={this.toggleLight} on={lightOn} />
<LightBall on={lightOn} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>

How do I communicate with my stateless component in React?

I wanted to create a very simple app containing a button. When I click on it it should change it's name and should change it's state to: isDisabled:true. I accomplished this by writing button inline, giving it an OnClick and so on, but I wanted to try this with stateless component with the same functionality, however I'm totally stuck.
import React, { Component } from 'react';
import './App.css';
class App extends Component{
constructor(){
super()
this.state = {name:'Hey buddy click me',
isDisabled:false,
}
}
render() {
return (
<div>
<MyButton handleClick={this.handleClick.bind(this)}></MyButton>
</div>
)
}
handleClick = () => {
this.setState ({
name:'Dont click!',
isDisabled:true,
});
}
}
const MyButton = (name, isDisabled) => <button onClick={this.handleClick.bind(this)}>{name}</button>
export default App;
You need to pass name, isDisabled too with handleClick as props if you want to access then in MyButton component.
Though name can also be passed as children. But as i feel you are learning start passing it as props.Then in future you can use children.
import React, { Component } from 'react';
import './App.css';
class App extends Component{
constructor(){
super()
this.state = {name:'Hey buddy click me',
isDisabled:false,
}
}
render() {
return (
<div>
<MyButton
name={this.state.name}
isDisabled={this.state.isDisabled}
handleClick={this.handleClick.bind(this)}>
</MyButton>
</div>
)
}
handleClick = () => {
this.setState ({
name:'Dont click!',
isDisabled:true,
});
}
}
//here we need to receive props from parent components, if we need to use here
const MyButton = ({name, isDisabled, handleClick}) => <button onClick={handleClick}>{name}</button>
export default App;

Resources