Watching state from child component React with Material UI - reactjs

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>

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.

React Adding two parents for a child component

State is the smart component which store all the states of child components
import React from 'react';
import TextArea from './Components/TextArea';
class State extends React.Component {
constructor(props) {
super(props);
this.state = {
name: ''
}
this.myChangeHandler = this.myChangeHandler.bind(this)
}
myChangeHandler = (event) => {
event.preventDefault();
this.setState({
[event.target.name]:event.target.value
});
}
render() {
return (
<div>
{/* Change code below this line */}
<TextArea name = {this.state.name}
myChangeHandler = {this.myChangeHandler}/>
{/* Change code above this line */}
</div>
);
}
};
export default State;
Now TextArea is the child component which share the input value to state.
import React from 'react';
import '../style.css';
class TextArea extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<input type="text" onChange= {this.props.myChangeHandler} name="name" value={this.props.name}></input>
<h1>Hello, my name is:{this.props.name} </h1>
</div>
);
}
};
export default TextArea;
There are several child components that send the data to state component.
so the app component is used to order the child component.
I need two parents for a child component. please see the image enter image description here
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom'
import './App.css';
import TextArea from './Components/TextArea'
function App() {
return (
<div className="App">
<BrowserRouter>
<Route path = "/new" component= {TextArea} />
</BrowserRouter>
</div>
);
}
export default App;
You can't have two "direct" parent for a single component but you can have multiple indirect parents.
Example:
App is the parent of State
State is the parent of TextArea
App is also a parent of TextArea but not a direct one.
This means that you can pass props (data and functions) from the top parent to all of his children.
If you need to pass a function from App to TextArea you need to pass it by State.
Here a tutorial on how to do it.
You can also use the Context and here's a tutorial on how to do it.

react-google-maps StandaloneSearchBox unmounting

I am trying to use the StandaloneSearchBox Component from https://www.npmjs.com/package/react-google-maps
After looking at the docs and some other answers I implemented the component like this:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withScriptjs } from "react-google-maps";
import StandaloneSearchBox from "react-google-maps/lib/components/places/StandaloneSearchBox";
import { Input } from "semantic-ui-react";
import API_KEY from "../config/googleAPIkey";
class AddressSearchbox extends Component {
constructor(props) {
super(props);
this.searchboxRef = null;
}
onSearchBoxMounted = ref => {
this.searchboxRef = ref;
};
onPlacesChanged = () => {
const places = this.searchboxRef.getPlaces();
this.props.onPlaceSelect(places[0]);
};
render() {
const Searchbox = withScriptjs(props => (
<StandaloneSearchBox
ref={props.onSearchBoxMounted}
onPlacesChanged={props.onPlacesChanged}
>
<Input
type="text"
placeholder="Type address or google place name"
icon="search"
/>
</StandaloneSearchBox>
));
return (
<Searchbox
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${API_KEY}&v=3.exp&libraries=geometry,drawing,places`}
loadingElement={<div style={{ height: `100%` }} />}
onPlacesChanged={this.onPlacesChanged}
onSearchBoxMounted={this.onSearchBoxMounted}
/>
);
}
}
AddressSearchbox.propTypes = {
onPlaceSelect: PropTypes.func.isRequired
};
export default AddressSearchbox;
I use the component in a signup form where all the other input fields update the state on input change causing re-rendering of the whole form.
When the AddressSearchbox component gets re-rendered it seems that it gets unmounted and then remounts causing flickering. The component itself works fine.
EDIT: When logging the ref parameter passed in onSearchBoxMounted() it prints null and then the SearchBox object after every re-render, so according to this the SearchBox component gets unmounted
I'm not sure if it's still actual, but to fix this you need to extract this part from the render function before your class definition:
const Searchbox = withScriptjs(props => (....))
So it will look like this:
imports ...
const Searchbox = withScriptjs(props => (....))
class AddressSearchbox extends Component {
...
render() {
return (
<Searchbox .../>
);
}
}
In practice, most React apps only call ReactDOM.render() once.
Source: https://reactjs.org/docs/rendering-elements.html
You see this flickering because ReactJS runs render() function each time when your state changes.

ReactComponent Button value won't render on my react app

I've got a simple React App going on. My index.js file looks, of course, like this:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
Going deeper, my App.js file declares an App extends Compoennt class, which contains my to-be-rendered elements and their functions:
import React, { Component } from "react";
import logo from "./SmartTransit_logo.png";
import MyButton from "./components/MyButton";
import "./App.css";
import { isWallet, helloWorld } from "./services/neo-service";
class App extends Component {
state = {
inputValue: ""
};
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Smart Transit Live Demo</h1>
</header>
<div style={{ width: 500, margin: "auto", marginTop: 10 }}>
<MyButton
buttonText="My Button"
onClick={ params => {helloWorld();}}
/>
</div>
</div>
);
}
}
export default App;
And the declaration of MyButton from /components/MyButton:
import React, { Component } from "react";
import PropTypes from "prop-types";
class MyButton extends Component {
render() {
return (
<button className="MyButton"
value = {this.props.buttonText}
>
{this.props.children}
</button>
);
}
}
MyButton.propTypes = {
buttonText: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
export default MyButton;
Finally, the declaration for helloWorld() is done like so (NOTE: neon-js is an npm package I'm using):
import { wallet } from "#cityofzion/neon-js";
export function isWallet(address) {
console.log(wallet.isAddress(address));
return wallet.isAddress(address);
}
export function helloWorld() {
console.log("Hello world");
return 1;
}
My problem is that the resulting Button doesn't get its value text rendered, and although it gets the CSS code for it just fine, it appears empty!
Not only that, but pressing it doesn't log a "Hello World" in the console, as it should, so it's even disconnected from its onClick function.
Any idea on what I'm doing wrong?
Buttons don't receive a "value" prop. The text inside of the button element is what gives it its text.
The button does appear to accept children to use as button text, but no children is actually being passed down to it. this.props.children is the content between JSX tags when the component is rendered.
React doesn't add the event handlers to elements automatically. You have to pass them along yourself in order for them to be properly triggered.
With that in mind, here's how you should render your button in App:
<MyButton onClick={() => helloWorld()}>
My Button
</MyButton>
And here's how MyButton's code should look:
class MyButton extends Component {
render() {
return (
<button className="MyButton" onClick={this.props.onClick}>
{this.props.children}
</button>
)
}
}
As you can see, the buttonText prop is no longer required; that's what the children prop is for.
You need to define super(props) in class constructor when you are going to use this.props
constructor(props) {
super(props);
}
Define this in MyButton component.
The problem is, you are not calling onClick method from mybutton component and button take it's value between it's opening and closing tag.
Use this code:
this.props.onClick()}> {this.props.buttonText}

react - material-ui appbar icon touch event doesn't fire

When I click on the element AppBar, icon on the left, _handleClick() method should execute.
I can't get console message.
I'm using material-ui framework and the attribute onLeftIconButtonTouchTap is provided for a callback function for when the left icon is selected via a touch tap.
import React, { Component } from 'react'
import { AppBar, IconButton } from 'material-ui'
import MoreVertIcon from 'material-ui/lib/svg-icons/navigation/more-vert';
let injectTapEventPlugin = require("react-tap-event-plugin");
//Needed for onTouchTap
//Can go away when react 1.0 release
//Check this repo:
//https://github.com/zilverline/react-tap-event-plugin
injectTapEventPlugin();
class Header extends Component {
constructor(props) {
super(props);
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
e.preventDefault();
// Show/Hide the LeftMenu
window.console.log("Click!");
}
render() {
return (
<AppBar title="Arasaaccc"
iconElementLeft={ <IconButton>
<MoreVertIcon/>
</IconButton> }
onLeftIconButtonTouchTap={ this._handleClick }
isInitiallyOpen={ true } />
)
}
}
export default Header
However it works with another component:
class Prueba extends Component {
constructor(props) {
super(props);
this._handleClick = this._handleClick.bind(this);
}
_handleClick(e) {
e.preventDefault();
window.console.log("Click!");
}
render (){
return (
<h1 onClick={this._handleClick}>Prueba Prueba Prueba</h1>
)
}
}
export default Prueba;
If you specify an icon for the AppBar component, onLeftIconButtonTouchTap event does not work.
Either you don't specify an icon:
<AppBar title="Arasaaccc"
onLeftIconButtonTouchTap={ this._handleClick }
isInitiallyOpen={ true } />
Or you apply the event on the IconButton component:
<AppBar title="Arasaaccc"
iconElementLeft={ <IconButton onTouchTap={ this._handleClick } >
<MoreVertIcon />
</IconButton> }
isInitiallyOpen={ true } />
Edit: Note that, according to this GitHub issue, the problem should be solved. You still can't have a a _handleClick on both of iconElementLeft and onLeftIconButtonTouchTap, either one or the other.
I can't see any problems with your code, so my guess is you'll need the React-Tap-Event-Plugin. The docs say this dependency is temporary and will go away once react v1.0 is released.

Resources