Let's say that my parent component got two child component :
Parent
| Child1
| Child2
I'am getting an input from Child2 and I'am passing it to the Parent component (until now, I know how to do). But then I need to pass that input to Child1 to update it's state.
How can I do that?
Hope you can get the main idea - create a function in the Parent component that will change the value passed to the Child1. ReactJS: Why is passing the component initial state a prop an anti-pattern?
class Parent extends Component {
constructor(props){
super(props);
this.state = {
value: ""
}
}
changeValue(value){
this.setState({value});
}
render(){
return (
<div>
<Child1 value={this.state.value}/>
<Child2 changeValue={changeValue}/>
</div>
)
}
}
class Child2 extends Component {
constructor(props) {
super(props);
this.state={
input: ""
}
}
handleChange(e){
var {value} = e.target;
this.setState({
input: value
},() => this.props.changeValue(value));
}
render(){
return (
<div>
<input value={this.state.input} onChange={this.handleChange}/>
</div>
)
}
}
class Child1 extends Component {
constructor(props) {
super(props);
this.state={value:''}
}
componentWillReceiveProps(nextProps) {
this.setState({value: nextProps.value})
}
render(){
return (
<div>
{this.props.value}
</div>
)
}
}
You can have a function in your child component that updates the state based on the value sent from the parent component. And you can access a function of the child component form the parent component using refs
Example
Parent:
class Parent extends React.Component {
funcUpdateChild1 = () => {
this.child1.updateState('value here');
}
render() {
return (
<Child1 ref={(ip) => {this.child1 = ip}} />
<Child2 ref={(ip) => {this.child2 = ip}} />
)
}
}
Child1
class Child1 extends React.Component {
updateState = (value) => {
//use the value to set state here
}
render() {
return (
//child1 contents here
)
}
}
**Component parent **
import React from 'react';
import MM from './modall';
class App extends React.Component {
constructor() {
super();
this.state = {
naslov:'',
telo:''
};
this.setStateHandler = this.setStateHandler.bind(this);
this.postaviStanje = this.postaviStanje.bind(this);
this.Stanje = this.Stanje.bind(this);
}
setStateHandler() {
this.setState({ naslov: "Naslov Prvi u Modalu", telo:"Novo Prvo telo modala"});
};
postaviStanje(){
this.setState({naslov: " Novi drugi u Modalu", telo:"Novo drugo telo modala"});
};
Stanje(){
this.setState({naslov: " Novi treci u Modalu", telo:"Novo trece telo modala"});
};
render() {
return (
<div>
<button onClick = {this.setStateHandler} data-toggle="modal" data-target="#modal">SET STATE</button>
<button onClick = {this.postaviStanje} data-toggle="modal" data-target="#modal">SET STATE2</button>
<button onClick = {this.Stanje} data-toggle="modal" data-target="#modal">SET STATE3</button>
<MM telo={this.state.telo} naslov={this.state.naslov} />)
</div>
);
}
}
export default App;
Compnent child
/**
* Created by trika on 31-Jan-18.
*/
import React,{Component} from 'react';
class Modal extends Component{
constructor(props) {
super(props);
this.state = {
naslov:this.props.naslov,
telo: this.props.telo
};
}
render(){
return(
<div className="modal" id="modal" role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h1 className="modal-title"><strong>{this.props.naslov}</strong></h1>
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>Modal body text goes here.</p>
<h2><strong>{this.props.telo}</strong></h2>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-primary">Save changes</button>
<button type="button" className="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
);
}
}
export default Modal;
Modern Solution with Hook:
1. Parent Component:
const Parent = ({}) => {
const [child2Data, setChild2Data] = useState(null);
return(
<view>
<Child1 child2Data={child2Data} />
<Child2 setChild2Data={setChild2Data}/>
</view>
)
}
1. Child2:
const Child2 = ({ setChild2Data }) => {
const [data, setData] = useState(null);
const _setData = (_data) => {
setData(_data)
setChild2Data(_data)
}
return(
<view onClick={() => _setData("Any Data")}>
</view>
)
}
1. Child1:
const Child1 = ({ child2Data }) => {
const [data, setData] = useState(null);
useEffect(() => {
setData(child2Data)
}, [child2Data])
}
Related
I am trying to implement forward Ref in my Demo Project but I am facing one issue. The value of current coming from forward ref is null, but once I re-render my NavBar component (by sending a prop) I get the value of current.
I basically need to scroll down to my Section present in Home Component from NavBar Component.
It can be done by directly by giving a href attribute and passing the id. But I wanted to learn how forward ref works and hence this approach.
Can someone please help me with this?
Here is my Code.
import './App.css';
import NavBar from './components/NavBar/NavBar';
import Home from './components/Home/Home';
class App extends Component {
constructor(props) {
super(props);
this.homeRefService = React.createRef();
this.homeRefContact = React.createRef();
}
render() {
return (
<div className="App">
<NavBar name={this.state.name} homeRef={{homeRefService: this.homeRefService , homeRefContact: this.homeRefContact}}/>
<Home ref={{homeRefService: this.homeRefService, homeRefContact: this.homeRefContact }}/>
</div>
);
}
}
export default App;
**Home Component**
import React from 'react';
const home = React.forwardRef((props , ref) => {
const { homeRefService , homeRefContact } = ref;
console.log(ref);
return (
<div>
<section ref={homeRefService} id="Services">
Our Services
</section>
<section ref={homeRefContact} id="Contact">
Contact Us
</section>
</div>
)
})
export default home
**NavBar Component**
import React, { Component } from 'react'
export class NavBar extends Component {
render() {
let homeRefs = this.props.homeRef;
let homeRefServiceId;
let homeRefContactId;
if(homeRefs.homeRefService.current) {
homeRefServiceId = homeRefs.homeRefService.current.id;
}
if(homeRefs.homeRefContact.current ) {
homeRefContactId = homeRefs.homeRefContact.current.id;
}
return (
<div>
<a href={'#' + homeRefServiceId}> Our Services</a>
<a href={'#' + homeRefContactId }>Contact Us</a>
</div>
)
}
}
export default NavBar
The ref is only accessible when the component got mounted to the DOM. So you might want to access the DOM element in componentDidMount.I suggest you to lift the state up to the parent component.
Demo
// App
class App extends React.Component {
constructor(props) {
super(props);
this.homeRefService = React.createRef();
this.homeRefContact = React.createRef();
this.state = { homeServiceId: "", homeContactId: "" };
}
componentDidMount() {
this.setState({
homeServiceId: this.homeRefService.current.id,
homeContactId: this.homeRefContact.current.id
});
}
render() {
return (
<div className="App">
<NavBar
homeServiceId={this.state.homeServiceId}
homeContactId={this.state.homeContactId}
/>
<Home
ref={{
homeRefService: this.homeRefService,
homeRefContact: this.homeRefContact
}}
/>
</div>
);
}
}
// NavBar
export class NavBar extends Component {
render() {
return (
<div>
<a href={"#" + this.props.homeServiceId}> Our Services</a>
<a href={"#" + this.props.homeContactId}>Contact Us</a>
</div>
);
}
}
export default NavBar;
All your code just be oke. You can access ref after all rendered.
Example demo how do it work:
export class NavBar extends Component {
render() {
let homeRefs = this.props.homeRef;
console.log('from Nav Bar');
console.log(this.props.homeRef.homeRefService);
console.log('----');
let homeRefServiceId;
let homeRefContactId;
if(homeRefs.homeRefService.current) {
homeRefServiceId = homeRefs.homeRefService.current.id;
}
if(homeRefs.homeRefContact.current ) {
homeRefContactId = homeRefs.homeRefContact.current.id;
}
return (
<div>
<a href={'#' + homeRefServiceId}> Our Services</a>
<a href={'#' + homeRefContactId }>Contact Us</a>
</div>
)
}
}
const Home = React.forwardRef((props , ref) => {
const { homeRefService , homeRefContact } = ref;
useEffect(() => {
console.log('from home');
console.log(homeRefService);
console.log('----');
props.showUpdate();
})
return (
<div>
<section ref={homeRefService} id="Services">
Our Services
</section>
<section ref={homeRefContact} id="Contact">
Contact Us
</section>
</div>
)
})
class App extends Component {
state = {
name: 'init',
}
constructor(props) {
super(props);
this.homeRefService = React.createRef();
this.homeRefContact = React.createRef();
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('from app');
console.log(this.homeRefService);
console.log('----');
}
render() {
return (
<div className="App">
<div>{this.state.name}</div>
<NavBar name={this.state.name} homeRef={{homeRefService: this.homeRefService , homeRefContact: this.homeRefContact}}/>
<Home showUpdate={() => this.state.name === 'init' && setTimeout(() => this.setState({name: 'UpdatedRef'}), 2000)} ref={{homeRefService: this.homeRefService, homeRefContact: this.homeRefContact }}/>
</div>
);
}
}
I'm using refs for calculating block height in child component, it works fine inside, so after each removeHandler() function "doCalculating" is called
But if I tried to call this into parent component, doCalculating() always return the initial value. Like just after componentDidMount()
Seems like doCalculating() into parent component refers to this.refs.tagList.clientHeight just once and not recalc even after child component update
React version 14.7 is used here, so I cannot use hooks
class ChildComponent extends Component {
componentDidMount() {
this.doCalculating()
}
doCalculating = () => {
const defaultHeight = 50
const newHeight = this.refs.tagList.clientHeight
if (newHeight > defaultHeight ) {
// do logic
}
}
render() {
return (
<ul
ref={"tagList"}
>
{array.map((item, index) => (
<li key={index}>
<button>
{item}
<span onClick={
(e) => {
this.removeHandler()
this.doCalculating()
}
} ></span>
</button>
</li>
)
)}
</ul>
)
}
}
class ParentComponent extends Component {
actionFunc = () => {
// some logic
// call recalculate function, that always return initial value
this.responsesTags.doCalculating()
}
render() {
return (
<div>
<ChildComponent
ref={instance => { this.responsesTags = instance }}
/>
<button onClick={() => this.actionFunc()} />
</div>
)
}
}
What is missing to recalculate a function when called in the parent component?
In my opinion your code works correctly, I've fiddle with your example (a little different), maybe it will be useful to you: https://jsfiddle.net/tu7vxfym/ . If I calculate height of the ul from child and parent component it will calculate correctly.
class ChildComponent extends React.Component {
constructor(){
super();
this.doCalculating = this.doCalculating.bind(this);
this.addDiv = this.addDiv.bind(this);
this.state = {
list: [],
height:undefined
}
}
componentDidMount() {
this.doCalculating()
}
doCalculating (){
const defaultHeight = 50
const newHeight = this.refs.tagList.clientHeight;
this.setState(state=>{
return state.height = this.refs.tagList.clientHeight
})
console.log(newHeight)
}
addDiv(){
this.setState(function(state){
return state.list.push(this.refs.tagList.clientHeight)
})
}
render() {
return (
<div>
<ul ref={"tagList"}>
{this.state.list.map((e,i)=>{
return (<li key={i}>{e}</li>)
})}
</ul>
<h1>Calculated height: {this.state.height}</h1>
<button onClick={this.addDiv}>Add list</button>
<button onClick={this.doCalculating}>Child button</button>
</div>
)
}
}
class ParentComponent extends React.Component {
constructor(){
super();
this.actionFunc = this.actionFunc.bind(this)
}
actionFunc(){
this.responsesTags.doCalculating()
}
render() {
return (
<div>
<ChildComponent ref={instance => { this.responsesTags = instance }}/>
<button onClick={this.actionFunc}>Parent button</button>
</div>
)
}
}
I have a simple component who show element onClick:
class MyComponent extends React.Component {
state = {
isVisible : false
}
render() {
const { isVisble } = this.state
return(
<div>
{isVisble ?
<div onClick={() => this.setState({isVisble: false})}>Hide</div> :
<div onClick={() => this.setState({isVisble: true})}>Show</div>}
</div>
)
}
}
I use this component three times in other component :
class MySuperComponent extends React.Component {
render() {
return(
<div>
<MyComponent />
<MyComponent />
<MyComponent />
</div>
)}
}
I need to pass isVisible at false for all other component if one of have isVisible to true
How to do that ?
Thanks
You should have your component controlled, so move isVisble to props and and then assign it from MySuperComponent.
Also pass MyComponent a callback so it can inform the parent if it wants to change the state.
You'd want some data structure to store that states.
https://codepen.io/mazhuravlev/pen/qxRGzE
class MySuperComponent extends React.Component {
constructor(props) {
super(props);
this.state = {children: [true, true, true]};
this.toggle = this.toggle.bind(this);
}
render() {
return (
<div>
{this.state.children.map((v, i) => <MyComponent visible={v} toggle={() => this.toggle(i)}/>)}
</div>
)
}
toggle(index) {
this.setState({children: this.state.children.map((v, i) => i !== index)});
}
}
class MyComponent extends React.Component {
render() {
const text = this.props.visible ? 'visible' : 'hidden';
return (<div onClick={this.props.toggle}>{text}</div>);
}
}
React.render(<MySuperComponent/>, document.getElementById('app'));
You can check your code here, is this what you want.
example
I want to post something(render on the screen) when I press a button.
Pressing the button doesn't give any errors but also doesn't give the result I expect from it. I'm new to react.
import React, { Component } from 'react';
import './style.css';
export default class Main extends Component {
newBlog = () => {
return (
<div>
<Post name = "James" about = "about java" post = "javs is great for complex software apps"/>
</div>
);
}
render() {
return(
<div>
<button onClick = {this.newBlog}>Post-Blog</button>
</div>
);
}
}
class Post extends Component {
render() {
const { name, about, post} = this.props;
return(
<div className = "main">
<h2>{name}</h2>
<h2>{about}</h2>
<hr/><br/>
<p>{post}</p>
</div>
);
}
}
As answer to your comment.
if i understand your point
import uuid from 'uuid/v4'; // get by npm install --save uuid || generate an unique id at each call
class Main extends React.Component {
constructor() {
super();
this.state = {
myBlog: [],
}
}
createNewBlog = () => {
var myPost = { name: 'John', about:'Javascript', post:"i'm john, i'll take about javascript"}
var currentMyBlog = this.state.myBlog;
currentMyBlog.push(myPost);
this.setState({myBlog: currentMyBlog});
};
render() {
return(
<div className=''>
{this.state.myBlog.map(post => (
<div key={uuid()}>
<Post name={post.name} about={post.about} post={post.post}/>
</div>
))}
<button onClick={ () => this.createNewBlog()}> Post blog</button>
</div>
)
}
};
The Returned JSX on button click needs to be rendered somewhere. You are better off setting a visible state on button click as onClick handler on Button doesn't do anything with the returned value in your case. Check the below snippet
class Main extends React.Component {
state = {visible: false}
newBlog = () => {
return (
<div>
<Post name = "James" about = "about java" post = "javs is great for complex software apps"/>
</div>
);
}
toggleVisible = () => {
this.setState(prev => ({visible: !prev.visible}))
}
render() {
return(
<div>
{this.state.visible? this.newBlog(): null}
<button onClick = {this.toggleVisible}>Post-Blog</button>
</div>
);
}
}
class Post extends React.Component {
render() {
const { name, about, post} = this.props;
return(
<div className = "main">
<h2>{name}</h2>
<h2>{about}</h2>
<hr/><br/>
<p>{post}</p>
</div>
);
}
}
ReactDOM.render(<Main/>, document.getElementById('app'));
<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="app"/>
you should use an arrow function with onClick event.
try that in render of Main component
render() {
return(
<div>
{this.state.visible? this.newBlog(): null}
<button onClick ={() => this.toggleVisible}>Post-Blog</button>
</div>
)
};
I have this simple code below. When I press the Toggle Button the component Child should hide/show, but it's not.
Do I have to re-render something?
I don't want to switch in/out a CSS class, just toggle via a button click
import React, {Component} from 'react';
let active = true
const handleClick = () => {
active = !active
}
class Parent extends React.Component {
render() {
return (
<div>
<OtherComponent />
{active && <Child />}
<button type="button" onClick={handleClick}>
Toggle
</button>
</div>
)
}
}
class Child extends React.Component {
render() {
return (
<div>
I am the child
</div>
)
}
}
class OtherComponent extends React.Component {
render() {
return (
<div>
I am the OtherComponent
</div>
)
}
}
You need to get or set it via state:
class Parent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
active: true,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
active: !this.state.active
});
}
render() {
return (
<div>
<OtherComponent />
{this.state.active && <Child />}
<button type="button" onClick={this.handleClick}>
Toggle
</button>
</div>
)
}
}
Note that with this approach you will re:render the entire parent component (as well as it's children).
Consider using another approach, when you are passing a prop to the child component and it will render itself with content based on this prop (it can render an empty div or something).
There are number of libraries that make this job easy for you, like react-collapse with animations and stuff.
You should only use state and props to manage your app state.
So instead try:
class Parent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
active: true
};
this.handleClick = this.handleClick.bind(this);
}
const handleClick = () => {
this.setState({active = !this.state.active});
}
render() {
return (
<div>
<OtherComponent />
{this.state.active && <Child />}
<button type="button" onClick={handleClick}>
Toggle
</button>
</div>
);
}
}
Alernatively, you could use forceUpdate() to force a re-render, but this is strongly discouraged:
const handleClick = () => {
active = !active;
this.forceUpdate();
}