There is simple scenario I updated a value in parent which passed to child component and expected cWRP method firing but not. here code below;
Parent component:
class App extends Component {
changeProps(){//interpreter jumps here fine..
debugger
this.appState.index=15 //update props value
}
render() {
return (
<div className="App">
<EasyABC parentUpdateProps={this.changeProps} appState={this.props.appState} />
</div>
)
}
}
child component:
#observer
export default class EasyABC extends Component{
constructor(props){
super(props)
}
componentWillReceiveProps(nextProps){//why its not jump here after update props in parent?
debugger
}
playSound(){// when this method called, cWRP above should be invoked rigth?
this.props.parentUpdateProps()
}
render(){
return(
<div>
<a onClick={()=> this.playSound()}>Play Sound Again</a>
Edited: i am using mobx as state handler, but dont bother with it
You need to update the state of the component using setState and use the same for passing it to child component
class App extends Component {
constructor() {
this.state = {
index: 0,
};
this.changeProps = this.changeProps.bind(this);
}
changeProps(){
this.setState({
index: 15,
});
// this will update state (not props)
}
render() {
return (
<div className="App">
<EasyABC
parentUpdateProps={this.changeProps}
appState={...this.state}
/>
</div>
);
}
}
<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>
You are updating the state wrongly. You have to use setState e.g.
changeProps() {
this.setState({
index: 15
});
}
You have to dereference the observable value in the render function or it will not fire the will receive props because the component is not actually using it to render.
You could just do something like this:
render() {
if (this.props.appState.index) {
return <div>Play Sound Again</div>;
}
return <div>Play Sound</div>;
}
It really doesn't matter how you use it, but that you access it within the call stack of the render method.
Related
I am following this tutorial but it does not say how to pass to the function a parameter.
In they the child they have
<Button onClick={this.props.action} />
handler(id) {
this.setState({
messageShown: true,
id : id
});
}
what happens if I want to send a value along with it(say some id or something).
I tried to do
<Button onClick={() => this.props.action(1)} />
but then my "state" is undefined.
It's hard to say what's going wrong without seeing a full code example, but what you're trying to do is certainly possible. Here's a working example.
class Parent extends React.Component {
constructor(props) {
super(props)
// Bind the this context to the handler function
this.handler = this.handler.bind(this);
// Set some state
this.state = {
messageShown: false
};
}
// This method will be sent to the child component
handler(id) {
this.setState({
messageShown: true,
id: id
});
}
// Render the child component and set the action property with the handler as value
render() {
console.log(this.state);
return (
<div>
<Child action={this.handler} />
<div>{this.state.id}</div>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
{/* The button will execute the handler function set by the parent component */}
<button onClick={() => this.props.action(1)} > button </button>
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('main'));
<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="main"></div>
To achieve what you want, in your Child component you should call a function that calls passed function. In this case you’ll be able to pass any parameter you want.
Let’s code!
Your Parent component will be:
class Parent extends React.Component {
constructor(props) {
super(props)
// Bind the this context to the handler function
this.handler = this.handler.bind(this);
// Set some state
this.state = {
messageShown: false,
id: -1 // initialize new state property with a value
};
}
// This method will be sent to the child component
handler(id) {
this.setState({
messageShown: true,
id: id
});
}
// Render the child component and set the action property with the handler as value
render() {
return <Child action={this.handler} />
}
}
And your Child component will be
class Child extends React.Component {
render() {
return (
<div>
{/* The button will execute the handler function set by the parent component, passing any parameter */}
<Button onClick={() => this.props.action(1)} />
</div>
)
}
}
Hope this helps
Usually when this.state is undefined after invoking a callback function it is a binding issue. Double check that the handler function has this bound to it in the parent component's constructor.
this.handler = this.handler.bind(this);
More on binding: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
I have the following parent/child component. In Parent I have a report object that gets set to the parent state as this.state.updateReport. This then gets sent to Child via props. In Child, I store the report in this.state.childEditedReport and make changes to it.
class Parent extends React.Component {
constructor(props) {
super(props)
this.handler = this.handler.bind(this);
this.state = {
updateReport: undefined
};
}
updateParentState() {
this.setState({
updateReport: Report (report is coming from somewhere else)
});
}
render() {
return <Child report={this.props.updateReport} />
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = { childEditedReport: this.props.report }
}
//do some update on the report via `this.props.childEditedReport`
render() {
return (
<div>
Child
</div>
)
}
}
How can I send this changed report object back upto Parent as the updated version of the parent's this.state.updateReport?
First of all, you need to store the report props that came from parent component in child constructor states using componentWillReceiveProps then you need to use liftStateUp
I did an example with comments explanation beside each code will help to understand how does lift state works from Child to Parnet.
class App extends React.Component {
constructor(props) {
super(props)
//this.handler = this.handler.bind(this);
this.state = {
updateReport: 'Parent Data'
};
}
componentDidMount(){
setTimeout(()=>{
this.setState({updateReport: 'data receive from asyn call'})
},2000); // let consider the asyn call takes a 2 sec.
}
// updateParentState() {
// this.setState({
// updateReport: Report (report is coming from somewhere else)
// });
// }
update= (data) =>{
this.setState({ updateReport: data}) // setting data that Received from child in parent State
}
render() {
return (
<div className="App">
<h1>Parent Component </h1>
Let's consider that the asyn takes 2 sec to set the date in the state
updateReport: <b>{this.state.updateReport}</b>
<Child report={this.state.updateReport} liftStateUp={this.update} />
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = { childEditedReport: 'empty' }
}
componentWillReceiveProps(nextProps){
if(nextProps.report !== this.props.report){
this.setState({childEditedReport: nextProps.report}) // setting the data in state
}
}
//do some update on the report via `this.props.childEditedReport`
someChanges = () =>{
let update = this.state.childEditedReport; // updating new data
this.props.liftStateUp(update);
}
render() {
return (
<div>
<h1>Child Component</h1>
Check ChildEditedReport State: <b>{this.state.childEditedReport}</b><br />
<button onClick={()=>this.setState({ childEditedReport: 'New Data Received' })}>Make some Change</button> Step one<br />
<button onClick={this.someChanges}>Lift State To Parent</button> Setp two
</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>
You can't, shouldn't and won't. React works on a process of unidirectional flow of data - pass things down as props to children, but there's no way to pass them back up. Instead, write the function that makes alterations in your parent component, and pass this function as props to your child. Your child component can then call the function as you need to, and it will update the parent's state. Your parent will then re-render the child with the new data, meaning that making any changes in your child component will hopefully be unnecessary.
I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,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" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>
I have a component that receives images as props, performs some calculation on them, and as a result I need to update its class. But if I use setState after the calculation, I get the warning that I shouldn't update state yet... How should I restructure this?
class MyImageGallery extends React.Component {
//[Other React Code]
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
//NEED TO UPDATE STATE HERE?
}
//componentWillUpdate()? componentDidUpdate()? componentWillMount()? componentDidMount()? {
//I CAN ADD CLASS HERE USING REF, BUT THEN THE COMPONENT'S
// FIRST RENDERED WITHOUT THE CLASS AND IT'S ONLY ADDED LATER
}
render() {
return (
<div ref="galleryWrapper" className={GET FROM STATE????}
<ImageGallery
items={this.getImages()}
/>
</div>
);
} }
You should put your logic into componentWillReceiveProps (https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops) so as to do a prop transition before render occurs.
In the end what we did was run the logic in the constructor and then put the class into the initial state:
class MyImageGallery extends React.Component {
constructor(props) {
super(props);
this.getImages = this.getImages.bind(this);
this.images = this.getImages();
this.state = {smallImgsWidthClass: this.smallImgsWidthClass};
}
getImages() {
//Some calculation based on this.props.images, which is coming from the parent component
this.smallImgsWidthClass = '[calculation result]';
return this.props.images;
}
render() {
return (
<div className={this.state.smallImgsWidthClass }
<ImageGallery
items={this.images}
/>
</div>
);
}
}
I've got a parent component which feeds a onSomeAction prop to a child component:
export default class myComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="mycomponent">
<ChildComponent onSomeAction={this.doSomething} />
</div>
)
}
doSomething() {
console.log(this);
}
}
In the child component when something is clicked on I'm calling a method whiih in turns calls the onSomeAction prop:
export default class ChildComponent extends Component {
render() {
return (
<div className="">
<a onClick={() => this.doIt()}>doIt</a>
</div>
)
}
doIt() {
const { onSomeAction } = this.props;
onSomeAction();
}
}
The problem I'm seeing is back in the parent component the this context seems to have been lost - the console.log in the doSomething method returns undefined. Why is this? I need to be able to access
the parent component context.
You need set context in parent component, you can do it with .bind
<ChildComponent onSomeAction={ this.doSomething.bind(this) } />
^^^^^^^^^^^^
Example
There are two options for you on how you can get the element that has been clicked or the whole component scope this.
option one:
instead of logging this you should logg the event target like so:
doSomething(e) {
console.log(e.target);
}
option two:
you have to attach the this keyword to the doSomething method like so:
constructor(props) {
super(props);
this.doSomething = this.doSomething.bind(this);
}
That was you'll be able to access the keyword this in your do something method.
Option3:
If you want to refer with this to the child component then you have to bind(this) on the function call int he child component
Actually you can fix it 3 ways:
<ChildComponent onSomeAction={ this.doSomething.bind(this) } />
<ChildComponent onSomeAction={ () => this.doSomething() } />
<ChildComponent onSomeAction={this.doSomething} />
and add this to constructor: this.doSomething = this.doSomething.bind(this)