Props passed to child component are empty in constructor and componentDidMount() - reactjs

I am just testing out various ideas and I do have experience using React, however, I seem to be totally missing something here.
In the ParentComponent I set initial state of an array within the constructor.
In componentDidMount() I then call a function to get some mock data which is just an array of objects. The function can be seen within ParentComponent. I then set the state using the mock data.
In the render() function of the ParentComponent I pass the array of objects as a prop to the ChildComponent.
In the ChildComponent when I try accessing the props within the constructor or within componentDidMount() the array is empty.
If I access the props in the render() function of the ChildComponent they are visible.
In React Dev Tools the props are clearly visible within the ChildComponent.
Does anyone have any idea why this behaviour is happening?
I need to be able to do some one time calculations on the data when the component is first mounted. Does anyone have any suggestions on how I can do this?
In all other languages I am aware of, if you pass arguments to a constructor they are immediately available.
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
componentDidMount() {
const mockData = this.getMockData();
this.setState({
data: mockData
});
}
render() {
return (<ChildComponent data={ this.state.data }/>);
}
getMockData = () => {
return [
{
key: 1,
name: "Test1",
price: "£0.00"
},
{
key: 2,
name: "Test2",
price: "£0.00"
}
]
}
}
export default ParentComponent;
class ChildComponent extends Component {
constructor(props) {
super(props);
console.log(props.data);
}
render() {
return (return some stuff here);
}
}
export default ChildComponent;

As you are populating the data array in componentDidMount lifecycle method, data array is initially empty when it is passed as a prop to the ChildComponent. This is because componentDidMount is called after the initial render.
So, when ChildComponent is mounted as a result of initial render of ParentComponent, data prop passed to ChildComponent is an empty array. Once the ChildComponent has mounted, then ParentComponent is mounted. At this point, componentDidMount method in ParentComponent is called.
You see the empty array in the constructor because it is only called once during the initial render of the ChildComponent. render() method in ChildComponent sees the latest data array because when componentDidMount is called in the ParentComponent, it updates the state, ParentComponent re-renders, leading to the re-render of ChildComponent with updated data array as a prop BUT the constructor in the ChildComponent is not called again, only render() method executes when ChildComponent re-renders.
To see the updated data in the ChildComponent, you can use one of the following options:
Use the data prop in the render() method
Use componentDidUpdate() lifecycle method
componentDidUpdate(prevProps, prevState) {
console.log(this.props.data);
}
Logging data prop in the render() method will first log the empty data array because of the initial render and then it will log the updated data array due to re-render of the ChildComponent.
Logging data prop in the componentDidUpdate() method will only log the updated data array because it is not called during the initial render of the component.

You assign a data to this.state.data in componentDidMount which is called as React completed to updated the DOM , so the props actually passed to the child component but their values in the moment of childComponenet construction still null because the componentDidMount still not called, and therefore the "this.getMockData()" not called yet

Related

useState with arrays not rerendering

I am facing issue while using useState hook with array. I checked various resources on stackoverflow, but could not fix it.
my basic code snippet looks like :
const [users, setUsers] = useState([]);
function addNewContact(user) {
const newUsers = [...users,user];
console.log(newUsers);
setUsers(newUsers);
}
<CardContainer users={users}></CardContainer>
class CardContainer extends Component {
constructor(props) {
super(props);
console.log("this -> ");
console.log(this.props.users);
this.state = {
users: this.props.users
}
}
render() {
//console.log(this.state.users)
return (
<div class="row row-cols-1 row-cols-md-2 g-4">
{
this.state.users.map(user => {
return <Card id={user.phone} title={user.name} email={user.email} phone={user.phone}></Card>
})
}
</div>
)
}
}
export default CardContainer;
I am able to see updated array in the console, but the component using it is not rendering again. Can anyone please help me on this.
The issue is due to you're storing the prop in the state of the child component, which is assigned on component initialization and component initialization/constructor only run one, until its remounted. After that, whenever, the state changes in the parent component, the child component is not re-rendering, because it uses its own state for map.
This below code only runs once on the component initialization.
this.state = {
users: this.props.users
}
In the child component, you can directly use the props and the child component will always re-render on change in the parent component. Instead of this.state.users.map you can directly map the array from props like this this.props.users.map. This way,the component will re-render on state change in the parent compoenent.
As #Junaid said, constructor is only called once before component mounting. If you really need to set a separate state inside the child component, then you can use componentDidUpdate(prevProps) react life cycle method. Make sure to compare previous and current props in order to avoid infinite loop of re-rendering.
componentDidUpdate(prevProps) {
if (this.props.users !== prevProps.users) {
this.setState({ users: this.props.users });
}
};

React Native state variables not getting updated from props

I'm passing 3 props - numLikes, id and userLiked to my class and I want to set my state variables initially before any render occurs.
Ideally, the values of state variables should be equal to their props counterparts but this isn't the case.
This is my code:
export default class LikeButton2 extends Component {
constructor(props) {
super(props);
this.state = {
numLikes: props.numLikes,
id: props.id,
userLiked: props.userLiked,
isloading: true,
};
}
//....
}
I used React Native Debugger to check for the variable values and the variables "numLikes" and "userLiked" were not getting updated. Attached is the proof for the same:
I also tried using spread syntax.
This is my code:
export default class LikeButton2 extends Component {
constructor(props) {
super(props);
this.state = {
...props,
isLoading: true,
};
}
//....
}
Although this also resulted in undesired values for the state variables. Proof for this in RN Debugger:
How can I correctly update the values?
In this exemple, when the component is mounted to be displayed, the state is set to be equal to the props. The constructor is only called at that time.
After that, if the props change, the state won't be updated to match them. If you want your state to be updated when the props change, you need to use the method ComponentDidUpdate:ComponentDidUpdate(prevProps,prevState).
It will be called every time the props or the state change. You can then compare the current props (this.props) and the previous props (prevProps) to set your state accordingly.
If you just want to use your props as they are you can just use this.props, it will always reflect the value you give to the component.
If I have <Component number={myNumber}/> somewhere in the code, Component will rerender every time myNumber changes, and the value this.props.myNumber in Component will be accurate.
export default class ComponentTest extends Component {
constructor(props) {
super(props);
}
render() {
return <div>{this.props.number}</div>
}
}

Misunderstanding React. FC and class component interaction

I'am new to React and don't understand something. I have two components: parent functional and child class component. Suppose, from FC I need to pass very simple array from useState hook. And when in class component in constructor I try to log out props - the result is empty array. But in component render array items successfully. If I don't use - useState hook and pass existing array - console.log(props) results that array. Why it happens? I think it's associated with async operations, but what if need to define initial state in constructor based on props? Since props undefined , I can't do it. Сan you help me figure it out
const FunctionalComponent = () => {
const [state, setState] = useState([]);
useEffect(() => {
setState(['Jim', 'Bob', 'Tom']);
}, []);
return (
<div>
<ClassComponent names={state} />
</div>
);
}
// And class component
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
// Some state ...
};
console.log(props); --- // []
}
render() {
const { names } = this.props;
return <div>{names}</div>; -- // renders items
}
}
https://codesandbox.io/s/props-to-class-component-from-usestate-5z5k3?file=/src/index.js
You can see how the class component renders proper data but console.log output does not reffer to them. That's because creating and changing state is async operation. To see how props changed you must use useEffect for FC and componentDidUpdate for class components.
class Counter extends React.Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
console.log(this.props.names)
}
render() {
const { names } = this.props;
return <div>{names}</div>;
}
}
Or you can put your console.log right inside of the render section:
render() {
console.log(this.props.names)
const { names } = this.props;
return <div>{names}</div>;
}
If you want to console.log new state of class component, use callback function as the second parameter of setState like shown below:
setState({counter: this.counter + 1}, ()=>console.log(counter))
Otherwise, it will show you the previous state value.
Your initial state is [] as defined by useState. With this state the functional component is initially rendered and it also renders ClassComponent with [] as the initial props. That's what is passed to constructor.
After that the constructor is no longer called. Instead, the new props will be only assigned to the component.

ReactJS lifecycle setState in componentDidMount

I have 2 components for demonstration of my problem:
Parent:
import React from "react";
import ReactDOM from "react-dom";
import { Grid, Row } from "react-flexbox-grid";
import Hello from "./Hello";
class App extends React.Component {
state = {
name: "Michal"
};
componentDidMount = () => {
this.setState({ name: "Tina" });
};
componentDidUpdate(prevState) {
console.log("App componentDidUpdate", prevState, this.state);
}
handleUpdate = value => {
console.log("App handleUpdate");
this.setState({ name: value });
};
render() {
return (
<Grid>
<Row>
<Hello name={this.state.name} update={this.handleUpdate} />
</Row>
</Grid>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Child:
import * as React from "react";
class Hello extends React.PureComponent {
componentDidMount() {
// setTimeout(() => {
this.props.update("Matus");
// }, 0);
}
componentDidUpdate(prevProps) {
console.log("Hello componentDidUpdate", prevProps, this.props);
}
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
export default Hello;
In child component I want to set value in parent state via props function. But setState function is ignored, it works if props function is called from setTimeout.
Can you explain me why it work in setTimeout, why I should avoid this construction. And what is correct way to do it?
Hello component represent "Select", which in componentDidMount will fetch options and set default value.
Thank you.
Components initialise from the bottom up in React. So in your example Hello triggers componentDidMount, attempts to set the state in App via this.props.update, then App overrides it a split-second later when it calls its own componentDidMount. The name you set in the child component never reaches the state.
I'm not sure what the purpose of this is, hopefully only for leaning purposes as components shouldn't need to immediately set their own state when mounting. If you need to perform some logic before initialising the state in App you can use a constructor and do it there.
Regardless, the solution is remove the initial state setter in App.
It is not ignored and it does fire. You are just not observing it with your logs.
Check out:
https://codesandbox.io/s/kind-jackson-b2r2b?file=/src/App.js
In the console you will see the following execution order in the console window:
Hello componentDidMount props = Object {name: "Michal", update: function ()}
App handleUpdate value = Matus
App componentDidMount props = Object {}
Hello componentDidUpdate props = Object {name: "Tina", update: function ()}
App componentDidUpdate state = Object {}
Object {name: "Tina"}
Thus you will see the child componentDidMount fires and completes mount before the parent component completed and fires its componentDidMount, as components completes mounting from the child components up.
So you just never observe the state going to Matus because it triggers a new state change to Tina when it completes mounting.
You setState function from Hello component is ignored because of the React lifecycle. Basically App componentDidMount function overrides your state change from Hello component before it was rendered. That's why setTimeout helps, it moves your state change to the new rendering loop.
I don't know exact why you are trying to load data and pass it from the child component to parent but the good practice in React is to pass data from top to bottom. So the better solution would be to use Select component to just render the data from parent and react to user events.
<Select options={options} selected={option} handle={handleSelect} />
Reason:
React rendering is synchronous.
Rendering is a depth-first traversal
Now,
componentDidMount() {
this.props.update("Matus");
}
Is executed first, which sets the name Matus. Then the following executes -
componentDidMount = () => { this.setState({ name: "Tina" }); };
This sets the name Tina.
All of this happens on the first call-stack where the rendering happens. If we use setTimeout(), then
this.props.update("Matus");
will be moved to the second call-stack, which will be executed after the initial rendering and mounting has ended, thus setting the name Tina and triggering a re-render.
If you want to use class components, you need to use a constructor function to initialise state and pass the props from parent to child.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Michal"
};
}
// ... rest of parent component
import * as React from "react";
class Hello extends React.PureComponent {
constructor(props) {
super(props)
}
componentDidMount() {
// setTimeout(() => {
this.props.update("Matus");
// }, 0);
}
componentDidUpdate(prevProps) {
console.log("Hello componentDidUpdate", prevProps, this.props);
}
render() {
return <h1>Hello {this.props.name}!</h1>;
}
}
export default Hello;

React get props in child component for inner function use

i got a component A:
import React, { Component } from 'react'
import Gmap from '../global/gmap.component'
class RandomPlace extends Component {
render() {
return (
<Gmap address={this.state.random.location} />
which renders among other things, the Gmap component:
class Gmap extends Component {
componentDidMount () {
}
render() {
return (
<div className="gmap-component">
<p>{this.props.address}</p>
This <p>{this.props.address}</p> is well displayed and updated when i hit a "reload" button on component A. At this point, the React Chrome extension shows well the props' address content. And sees it being updated well on the "reload" action.
Problem is, i cant seem to be able to reach the props property address in the internal functions of my component Gmap like componentDidMount() or aCustomFunction().
I have tested this:
componentDidMount () {
console.log('gmap did mount')
this.setState({address: this.props.address})
let x = this.props.address
let y = this.state.address
console.log(x)
console.log(y)
With a constructor at the top of the class:
constructor (props) {
super(props)
this.state = {
address: 'xx'
}
But nothing shows up. I am new to React and sure i am missing something pretty basic but cant see to spot it.
Thanks in advance.
Are you asking how to call a custom function on your child component? If so,
<Gmap address={this.state.random.location} ref={(map) => { this.map = map; }} />
Then
this.map.aCustomFunction()
I'm not entirely sure, but i think it's going like:
In your component A, address={this.state.random.location} set from the state, as you see.
random object fill with call getRandomPlace() in componentDidMount().
So what's going on: at first you render Gmap component you have prop address there with undefined, because on component A didn't call componentDidMount() yet.
Then in component A trigger componentDidMount you get filled object "random" and yuor component Gmap recive normal prop addres, with no indefined. And rerender component with this new adderss prop, but your componentDidMount() in component Gmap has already invoked and doesn't trigger more...
If i'm right, you can set at component A
constructor(){
super();
this.state={
random: {location: "some test value"}
}
}
and you will see this "some test value" instead undefined in your console log.

Resources