React Adding two parents for a child component - reactjs

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.

Related

React JSX with component in variable does not sync props

The Scegli component is assigned to the App comp variable and then rendered in App render as a variable. However, the props assigned don't work: if I type in the input box, the value is frozen.
What am I doing wrong?
If I just move the <Scegli>...</Scegli> directly into the render (without assigning to a variable) it works as expected.
App.js
import React, { Component } from 'react';
import './App.css';
import Scegli from './components/Scegli';
class App extends Component {
constructor() {
super();
this.state = {
valore: 'Single'
}
this.comp = <Scegli value={this.state.valore} handleChange={this.setValoreHandler} />;
}
setValoreHandler = e => {
this.setState({
valore: e.target.value
});
}
render() {
return (
<div>
{this.comp}
</div>
);
}
}
export default App;
Scegli.js
import React, { Component } from 'react';
class Scegli extends Component {
render() {
return (
<div>
<input value={this.props.value} onChange={this.props.handleChange} />
Valore scelto: {this.props.value}
</div>
);
}
}
export default Scegli;
this.comp is declared once, at the component's mount and stays in that state. You are not updating/re-rendering it anywhere, that's why it remains unchanged.
You could either:
move the JSX component directly to render:
<div>
<Scegli value={this.state.valore} handleChange={this.setValoreHandler} />
</div>
or
update the class variable with every input change (not recommended though):
setValoreHandler = (e) => {
this.comp = <Scegli value={e.target.value} handleChange={this.setValoreHandler} />;
this.forceUpdate();
}
(I could be wrong) but when you define this.comp in the constructor() it is only loaded once with the default state. The constructor() is not called on re-render (similar to componentWillMount()). So that is why it is frozen as the updated state is never sent this.comp
Instead of this.comp in render do
return (
<div>
<Scegli value={this.state.valore} handleChange={this.setValoreHandler}/>
</div>
);

How to pass an Array between two sibilings in react

In my project I have App.js that is Parent component. And for Parent component there are two child components those are Childone component and Childtwo component. Now I am trying to pass data from Childone component to Childtwo component. Someone please tell me to achieve this
This is App.js
import React, { Component } from "react";
import Childone from "./Childone/Childone";
import Childtwo from "./Childtwo/Childtwo";
class App extends Component {
render() {
return (
<div className="App">
<Childone />
<Childtwo />
</div>
);
}
}
export default App;
This is Childone
import React, { Component } from "react";
class Childone extends Component {
render() {
const Employs = ["Mark", "Tom"];
return <div className="Childone" />;
}
}
export default Childone;
This is Childtwo
import React, { Component } from "react";
class Childtwo extends Component {
render() {
return <div className="Childtwo" />;
}
}
export default Childtwo;
If you feel I am not clear with my doubt, please put a comment.
There are two ways of doing it.
By passing data from Childone to Parent through callback and from Parent to Childtwo as prop. Thereby letting parent be the intermediatory.
By creating a context at the parent and let Childone be the producer and Childtwo be the consumer.
Context is used to avoid prop drilling. Also frequent update of context values isn't considered as a good practice.
Also, for your case we can go with approach ( 1 ).
You can enter data in Childone and see the data reflect in Childtwo in this sandbox.
Are you familiar with React's unidirectional data flow? I would try to create a mental model in your head of having the Parent be the container/controller that passes the required information into your children.
So in this case you would either want the data to originate from Parent or have some event handler associated with the data in Child1. If the latter, attach a callback function from Parent to Child1 that accepts the data as an argument to then be passed into Child2.
best solution is is to use redux. If you are not familiar with redux then you can follow below approach :
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
class App extends Component {
constructor() {
super();
this.state = {
number:[0]
};
}
render() {
return(
<div>
<div>Parent Componet</div>
<Child1 passToChild={this.passToChild.bind(this)}> </Child1>
<Child2 number={this.state.number}></Child2>
</div>
)
}
passToChild(number){
this.setState({
number : [...this.state.number,number]
});
}
}
class Child1 extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div>Child1</div>
<button onClick={() => this.sendToSibiling()}>
Send data to sibiling
</button>
</div>
);
}
sendToSibiling() {
this.props.passToChild(Math.random());
}
}
class Child2 extends Component {
constructor(props) {
super(props);
}
render(){
return(
<div>
<div>Child 2</div>
<span>data from sibiling : {...this.props.number}</span>
</div>
)
}
}

Passing an event method between siblings component in ReactJS

I am practicing in ReactJS and I have a trouble in passing a method between 2 sibling component. I have created React app which has 3 component: MainPage is the parent, FirstPage and SecondPage are two children. In FirstPage component there is a header with some text and SecondPage component has a button. My main goal is to pass the change-header method I defined in FirstPage, through MainPage component, to SecondPage component, so that when I click on the button that event method is fired.
I follow this tutorial https://medium.com/#ruthmpardee/passing-data-between-react-components-103ad82ebd17 to build my app. I also use react-router-dom in MainPage to display two page: one for FirstPage, another for SecondPage
Here is my FirstPage component:
import React from 'react'
class FirstPage extends React.Component{
constructor(props){
super(props)
this.state = {
msg: 'First page'
}
}
componentDidMount(){
this.props.callBack(this.changeText.bind(this))
}
render(){
return(
<div className = "first">
<h2>{this.state.msg}</h2>
</div>
)
}
changeText(){
{/* event method I defined to change the header text*/}
this.setState({msg: 'Text changed !!'})
this.props.history.push('/first')
}
}
export default FirstPage
and MainPage component:
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import React from 'react'
import FirstPage from '../component/FirstPage'
import SecondPage from '../component/SecondPage'
class MainPage extends React.Component{
constructor(props){
super(props);
this.state = {
func : null
}
}
myCallBack(callFunc){
this.setState({func: callFunc})
}
render(){
return(
<div className = "main">
<Router>
<Switch>
<Route path = "/first" render = {(props) => <FirstPage {...props} callBack = {this.myCallBack.bind(this)} />} />
<Route path = "/second" render = {(props) => <SecondPage {...props} myFunc = {this.state.func}/>} />
</Switch>
</Router>
</div>
)
}
}
export default MainPage
Follow the tutorial, I defined the property func inside MainPage state to store the event method from FirstPage. The myCallBack method is used to change the property of state. And I pass that method to the FirstPage by using callBack = {this.myCallBack.bind(this)}. So in the FirstPage, when the this.props.callBack(this.changeText.bind(this)) called, the event method will be stored into MainPage state
And finally my SecondPage commponent:
import React from 'react'
class SecondPage extends React.Component{
render(){
return(
<div className = "second">
<h2>Second page</h2>
<button onClick = {this.props.myFunc}> Click here to change</button>
</div>
)
}
}
export default SecondPage
App.js :
import React from 'react'
import MainPage from './component/MainPage'
function App() {
return (
<div className = "App">
<MainPage/>
</div>
);
}
export default App;
I simply pass the this.state.func that store my event to SecondPage through props. And I think this should be work: when I click the button, React will redirect to the 'FirstPage' and change the header field. But in fact when I clicked, nothing happen. Can anyone show me which part I did wrong ?
Hi Quang.
In order to do this, that is a possible approach you can follow:
For any shared data between two siblings of a parent component, we basically put that shared data in the closest parent, which in this case MainPage
Further:
The content you want show in FirstPage and change by SecondPage should exist in the state of the parent component MainPage
Pass the content to the FirstPage as a prop, such like content={this.state.content}
The function that changes the content changeText should be inside MainPage because it will be changing the specific state that is sent as a prop to the FirstPage
Function, when invoked by SecondPage should be changing the state of the MainPage, which is passed to the FirstPage as the content of the header.
Solution:
- FirstPage:
// Re-write as a functional component, because we won't be using lifecycle methods, thus, no need for it to be class component.
import React from 'react'
const FirstPage = (props) => (
<div className = "first">
<h2>{props.msg}</h2>
</div>
);
export default FirstPage
- SecondPage:
// Re-write as a functional component, because we won't be using lifecycle methods, thus, no need for it to be class component.
import React from 'react'
const SecondPage = (props) => (
<div className = "second">
<h2>Second page</h2>
<button onClick = {props.changeMsg}> Click here to change</button>
</div>
)
export default SecondPage
- MainPage:
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import React from "react";
import FirstPage from "../component/FirstPage";
import SecondPage from "../component/SecondPage";
class MainPage extends React.Component {
constructor(props) {
super(props);
this.state = {
msg: '', //added msg that is shown in FirstPage
};
}
// Added the function changeMsg to modify msg value in the state by the button onClick event from SecondPage.
changeMsg(){
{/* event method I defined to change the header text*/}
this.setState({ msg: 'Text changed !!' })
this.props.history.push('/first')
}
// Cleared some un-used functions.
// passed msg for FirstPage as a Prop
// passed changeMsg to SecondPage as a Prop
render() {
return (
<div className="main">
<Router>
<Switch>
<Route
path="/first"
render={props => (
<FirstPage {...props} msg={this.state.msg} />
)}
/>
<Route
path="/second"
render={props => (
<SecondPage {...props} changeMsg={this.changeMsg.bind(this)} />
)}
/>
</Switch>
</Router>
</div>
);
}
}
export default MainPage;

nested component can't get props through connect

I am using a connected component as a props passed to a parent component, but I can't get any props from connect() or even from the function definition inside the component.
I have tried to defined the props function in render() but still not work
import React from 'react';
import PropTypes from 'prop-types';
import {Col,Row} from 'antd'
import injectSheet from 'react-jss';
class SubComponent extends React.Component{
constructor(props){
super(props);
}
picClicked=()=>{
console.log("cause state!");
console.log(this.props.picClicked);// output undefined
this.props.picClicked;
}
render(){
const {picClicked,classes} = this.props;// classes is from react-jss
console.log(this.props);// only classes is shown from the console log
return(
<div>
<div onClick={this.picClicked} className={classes.headBlank}>
</div>
</div>
)
}
SubComponent.propTypes={
picClicked:PropTypes.func.isRequired
};
export default injectSheet(logoStyle)(SubComponent)
then the container looks like following:
const mdtp = (dispatch)=>{
return{
picClicked:()=>{console.log("picClicked!");}
};
};
export default connect(null,mdtp)(SubComponent);
The SubComponent is then used as a props in another parent component like this:
ReactDOM.render(
<Provider store={storeMainContents}>
<ParentComponent contentsShow={<SubComponent />} />
</Provider>
,document.getElementById("parentComponent")
);
The parent component looks like this:
render(){
const {contentsShow,classes}=this.props;
return(
<div>
<div>
{this.props.contentsShow}
</div>
</div>
);
}
What I expected is that the SubComponent can access the props like picClicked as a action that can be dispatched or at least get accessed. Now it is simply undefined.

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>

Resources