I have a problem when I try to access parent's props value in the child class.
I am trying to initialize the child props with the parent's props.
But the following code shows me empty string of value.
export default class Child extends React.Component {
constructor(props) {
super(props);
this.state = { value: props.value };
}
...
In other class I call this Child as follows:
const issue = this.state.issue;
...
<Child value={issue.number} />
...
I checked the number value before Child class is constructed (it shows me correct values), while it is construced, the value becomes empty..
For test, I used primitive number value, and it works.
Seems like some kind of link to issue.number is disconnected when Child is contructed.
Any clue?
This could easily happen. For example, issue.number becomes a number after AJAX re-rendering. In this case you have to do follow (in your parent component):
render() {
const issue = this.state.issue
if (issue.number) {
return <Child value={issue.number} />
}
}
Ah, I just figured it out.
It is related to Lifecycle.
The number value is not loaded when I render Child.
So, I need to figure it out how to update the number in Child with my intention.
Related
Actually, I am learning to React JS, But I have confusion that how can a parent's class method has access to his child's class state. I searched a lot on this topic but in object-oriented programming parent class hasn't access to his child's properties (state). But in react js how setState() has to access to his child's class properties (state). It can be a stupid question please tell me how it happens?
Don't worry, this is a great question.
Props are passed by reference!
Here are some great answers from a StackOverflow post that answer your question with more specificity.
In reactjs, are props pass by value or pass by reference?
I think some potential nuance I can offer is that React emphasizes composition over inheritance. You should think of it as your parent component being composed of the child components. While the child components are given access to the parent state, they aren't inheriting it.
Understanding the Parent Child structure through the Component API may help, should you want to clear any confusion. Since you have mentioned Class Components, let me illustrate an example with the same.
You may send props from a Parent to a child through defining a property within the JSX insertion -
<Child uniqueProp="Text sent to Child" />
Conversely, it is tricky to access Child data in a Parent component. You'll have to follow these steps -
Define a callback function in the Parent Component to get data from the Child Component.
Pass the callback function in the Parent Component as a prop to the Child Component.
The Child Component calls the Parent callback function using props.
Example -
Parent Component
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "Original Text"
}
}
handleCallback = (value) => {
this.setState({ text: value })
}
render() {
return (
<div>
<Child changeText={this.handleCallback} />
{this.state.text}
</div>
)
}
}
Child Component
class Child extends React.Component {
render() {
return (
<div>
<button onClick={() => this.props.changeText("Data from child")}>
Button
</button>
</div>
)
}
}
The reason it is set up that way is because props are passed by reference. Should you choose to change the parent's state traditionally state = {"something"} instead of setState({"something"}), the object will be modified in the child component as well but there will not be a re-render cycle. The user won't see UI changes until a manual refresh.
And when you use setState to change the parent's state (standard practice), the re-render cycle will be triggered as expected and the Child Component will reflect the UI changes.
Note: Be warned, the use of functional components is almost defacto now. Makes life easy with integrations of Hooks and data stores such as Redux in my opinion.
I'm currently using, in my application, a PureComponent parent and children components. I expected a PureComponent to only update if it's state or props change. To ensure this, I had a child component log something everytime its componentDidUpdate() method was called. The application looks something like this:
class Parent extends React.PureComponent{
constructor(props){
super(props);
this.state = {
item1: null,
item2: null,
list1: [],
list2: [],
}
render(){
let combinedList= [...this.state.list1, ...this.state.list2];
return(
<React.Fragment>
<Child myList={combinedList} />
<OtherChild item={this.state.item1} />
</React.Fragment>
);
The Child component looks something like this:
class Child extends React.PureComponent{
constructor(props){
super(props);
}
componentDidUpdate(){
console.log("Child updated");
}
render(){
return(
....
);
}
For example, when the state of item2 in Parent component changes, the Child component will log "Child updated". I'm a bit confused, since the child component doesn't receive item1 as a prop, and it's state isn't changed. Is this happening because the Parent component rerenders all of it's children? Or maybe because the shallow comparison Child does for its myList prop indicate that it has changed? Aside from writing my own shouldComponentUpdate() method, how can I prevent Child from rerendering everytime Parent rerenders?
Right now, in your render function for the Parent component, you're creating a new Array on every render with combinedList. PureComponent switches your shouldComponentUpdate() method to do a shallow compare to see if it should update. Since it's shallow, it's comparing references.
Since you're creating a new array on every render, it's going to be new every single time the Parent renders.
The solution here really depends on the full use-case that you have for combinedList. Looking at this example directly, I would recommend that you set combinedList in state. The other option would be to split your child component props up into a list1 and list2 prop instead of a myList prop.
Ok so this question is a bit tricky. I have been thinking about whether this is even correct concept wise, considering React is supposed to be a one-way flow of data, from parent to children, and not viceversa. But I would like to post the question anyway so I get different opinions and even possibly a way to get this to work.
In my app, I have a pretty large component that accepts forms as its children, and does some nifty React magic to pass its methods to the children so when the children elements are changed, they trigger the parent components methods that store the data in state and handles the form submissions. It works very nicely, however it is not so good at catching "defaultValues".
In a nutshell, I'm trying to trigger my parent method on the chilren's componentidMount() method, and it works, however, if there's more than one child trying to do this, the method gets called twice but it only uses the second child's dataset.
I have created a simplified version of my issue in the following code:
import React from 'react'
export class Parent extends React.Component {
constructor(props){
super(props)
this.state = {
data : {name:'james'}
}
this.updateData = this.updateData.bind(this)
}
updateData(key,data){
console.log('updating data')
this.setState({
data : {...this.state.data,[key]:data}
})
}
render(){
console.log(this.state)
return (
<div>
<Child1 updateData={this.updateData}/>
<Child2 updateData={this.updateData}/>
</div>
)
}
}
class Child1 extends React.Component {
componentDidMount(){
this.props.updateData('child1','myData')
}
render(){
return (
<div>
I am Child 1
</div>
)
}
}
class Child2 extends React.Component {
componentDidMount(){
this.props.updateData('child2','myData2')
}
render(){
return (
<div>
I am Child 2
</div>
)
}
}
This code will render 'updating data' twice on the console, but it will only update the state with the data sent in child2. Again, I can see how this may not be the best approach considering that im setting the state of a parent from its children, but it would be a good solution for setting default values on a parent component that gets reused a lot with different children.
Im all ears stack overflow
I think the problem is that setState does both updates at the same time (batches them) meaning the same initial state is used when merging both partial states. You need to use updater function as shown by kind user:
this.setState((prevState) => ({ data: { ...prevState.data, [key]: data } }));
In the example below, I'm seeing Child get unmounted and re-mounted on every ComponentA render. None of the other components in the tree are re-mounted.
class ComponentA extends Component {
renderChild() {
return <Child />;
}
render() {
return <ComponentB>{this.renderChild()}</ComponentB>;
}
}
class ComponentB extends Component {
render() {
return <ComponentC passthruChild={() => children} />;
}
}
class ComponentC extends Component {
render() {
const PassedThruChild = this.props.passthruChild;
return <div><PassedThruChild /></div>;
}
}
Why is this happening, and how can I make it not happen?
This is entirely speculative but I think it could be something along the lines of: when ComponentA renders, a new instance of Child gets returned by this.renderChild(). This instance is different from some cached instance which results in the cached child being replaced with the new instance. Cached one being unmounted and the new one being mounted.
In the other two cases the pass thru child could be a reference to the same object across multiple renders.
I think you should be able to check and see if they are the same object or a different object using the dev tools.
I can't post a reply to your comment since I don't have 50 points so I will answer here:
If you save were to cache the returned object from this.renderChild() so that you're not creating a new one every time the function is called you could probably make it work.
I need to remove a prop from a child.
I have a container element which uses a property on it's children to perform some enhancements on the children. That property should be removed from the child before rendering.
<AsyncContainer>
<Button onClick={this.asyncStuff} asyncHandler="onClick"/>
</AsyncContainer>
The asyncHandler property should be removed from the button before rendering.
AsyncContainer uses React.cloneElement(child, properties).
I've tried nulling the asyncHandler property, setting it to undefined and deleting the property from the child.props. It seems that it is impossible to get rid of this property again.
I just ran into this issue. You can just create a new element and use the old element's type and props you want to pass through. I'm not sure if this an anti-pattern or not, I just stumbled on it and it seems to be working well so far.
It should look something like this:
function AsyncContainer(props) {
const child = React.Children.only(props.children)
const { asyncHandler, ...childProps } = child.props
// do asyncHandler stuff
return React.createElement(child.type, childProps)
}
function AsyncContainer(props) {
const child = React.Children.only(props.children);
return React.cloneElement(
child,
{ asyncHandler: undefined }
);
}
How it works
You clone element using React.cloneElement because element is immutable and only way to change its props is to create clone.
Use second React.cloneElement argument to add new props and remove old props. Unneeded props should be assigned with undefined. You need to do this because by default cloned element is cloned with all its props.
As per the comments you cannot modify the props directly as they are immutable.
However, I think I have a simple solution to this problem. I have no idea what library that is or how it works, so this may or may not work. However, this is a general answer to how you would remove a prop before a component gets mounted.
That being said, I would try to create my own component which renders a <Button />:
class MyButtonComponent extends React.Component {
...
render() {
return <Button onClick={this.props.onClickHandler} />;
}
}
Then in the component you want to do your enhancements:
render() {
<AsyncContainer>
<MyButtonComponent onClickHandler={this.asyncStuff} asyncHandler="onClick"/>
</AsyncContainer>
}
This way you maintain your onClick eventlistener on the <Button /> component but you don't pass the illegal asyncHandler prop.
Edit:
Alternatively, you could also do:
class MyButtonComponent extends React.Component {
...
componentWillMount() {
let newProps = this.props;
delete newProps.asyncHandler;
this.setState({properties: newProps}):
}
render() {
return <Button {...this.state.properties} />;
}
}
This will apply all the props (with the spread operator) to <Button /> except for asyncHandler which we delete prior to the component being mounted by creating a copy of the props in state but with asyncHandler removed.
Also check this answer I gave to a similar question.