React get props in child component for inner function use - reactjs

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.

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 });
}
};

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

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

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;

How to refresh props with React/Redux when user enters a container

I have CompetitionSection which repeats all the competitions from database. When user clicks on one, it redirects him to a Competition Page, loads for a second and renders the page with all the details in it. So far, so good.
But when users goes back to the Competition Section and then click on the second competition, it instantly loads up the previous competition, 0 loading time.
From my point of view, what is failing is that the props of the component are not updating when I render the component (from the second time). Is not a router problem, which was my first instinct because I'm seeing the route.params changing acordingly, but the actions I dispatch to change the props are not dispatching. Here's a bit of code of said component.
class CompetitionPage extends React.Component {
componentWillMount() {
let id = getIdByName(this.props.params.shortname)
this.props.dispatch(getCompAction(id));
this.props.dispatch(getCompMatches(id));
this.props.dispatch(getCompParticipants(id));
this.props.dispatch(getCompBracket(id));
}
render() {
let { comp, compMatches, compBracket, compParticipants } = this.props
...
I tried every lifecycle method I know. component Will/Did Mount, component Will/Did update and I even set shouldUpdate to true and didn't do the trick. As I understand, the problem will be solved with a lifecycle method to dispatch the actions everytime an user enters Competition Page and not just for the first time. I'm running out of options here, so any help will be appreciated.
NOTE: I'm a newbie at React/Redux so I KNOW there are a couple of things there are anti-pattern/poorly done.
UPDATE: Added CompetitionsSection
class CompetitionsSection extends React.Component {
render() {
const {competitions} = this.props;
return (
...
{ Object.keys(competitions).map(function(comp, i) {
return (
<div key={i} className={competitions[comp].status ===
undefined? 'hide-it':'col-xs-12 col-md-6'}>
...
<Link to={"/competitions/"+competitions[comp].shortName}>
<RaisedButton label="Ver Torneo" primary={true} />
</Link>
...
It helps to better understand the lifecycle hooks. Mounting a component is when it is placed on the DOM. That can only happen once until it is removed from the DOM. An UPDATE occurs when new props are passed or setState is called. There are a few methods to troubleshoot when updates are not happening when you think they should:
Ensure that you are changing state in componentDidMount or componentDidUpdate. You cannot trigger an update in componentWillMount.
Make sure that the new props or state are completely new objects. If you are passing an object down in props and you are just mutating the object, it will not trigger an update. For instance, this would not trigger a update:
class CompetitionPage extends React.Component {
constructor(props) {
super(props)
this.state = {
competitions: [ compA, compB ]
}
}
triggerUpdate() {
this.setState({
competitions: competitions.push(compC)
})
}
componentDidMount() {
triggerUpdate()
}
render() {
return(
<div>
Hello
</div>
)
}
This is due to the fact that a new competition is being appended to the array in state. The correct way is to completly create a new state object and change what needs to be changed:
const newCompetitions = this.state.competitions.concat(compC)
this.setState(Object.assign({}, this.state, { competitions: newCompetitions }))
Use ComponentWillRecieveProps on an update to compare previous and current prop values. You can setState here if clean up needs to be done:
Read more about this method in the React documentation:
https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops

Trying to render a new instance in ReactJS

As an example (real tried code)
I have a component of which I want to initiate a NEW instance for rendering.
import React, { Component } from 'react';
export default class TinyObject extends Component {
constructor(props) {
super(props);
console.log("TinyObject constructor");
}
render() {
console.log("TinyObject render");
return (
<div>HEY THIS IS MY TINY OBJECT</div>
);
}
}
Then in main App constructor I do the following:
var myTinyObject = new TinyObject();
var myArray = [];
myArray.push(myTinyObject);
this.state = {testing: myArray};
Then a created a function to render this:
renderTest()
{
const {testing} = this.state;
const result = testing.map((test, i) => {
console.log(test);
return {test};
});
}
And I call this from the App render function like this:
render() {
const { gametables, tableActive } = this.state;
console.log("render");
return <div><div>{this.renderTest()}</div></div>;
}
It runs, no errors.
I see console log of the following:
console.log("TinyObject constructor");
console.log(test);
But I don't see console log of the TinyObject render nor do I see the render output.
Thanks to lustoykov answer I got a little further
JSX: var myTinyObject = <TinyObject />;
works!
but in the real app I add a little more and don't know how to do it here.
return <GameTable key={'gt'+index} data={table} settings={this.settingsData} sendTableNetworkMessage={this.sendTableNetworkMessage} />
this is the way I was rendering; and I needed more instances of GameTable
now the question is; how do I add the arguments like data & settings to myTinyObject.
thanks for helping so far.
You don't manually instantiate react component, use JSX or createElement. For instance
via JSX
var myTinyObject = <TinyObject prop1={prop1} prop2={prop2} />;
via React.createElement
var myTinyObject = React.createElement(TinyObject, { prop1, prop2 }, null);
I would definitely check out some tutorials and how React works at a basic level. You aren't really going to call your react components like you would normally do in javascript since the render function returns jsx.
Fundamentally, React is what is called a single page application. That means that your browser will load up a single html file with a div. Now that div will be where React performs its magic by using Javascript to change stuff around.
It is easiest for me to think of React as a tree. You create these components that you place on the DOM or in your HTML and React will add and remove them downwards. For instance, take a look at this picture of twitter.
So first the Feed component is going to be put on the DOM. Then the Feed component will render the Tweet components. So as you can see the rendering goes in one direction, downwards.
Now, as you can see your render methods are not returning javascript. It is returning something that looks like HTML but we call it JSX. That means we want to render it a little differently with our react classes.
If we have a child component:
class Child extends React.Component {
render() {
return <h1>Hello, I am inside the parent component</h1>;
}
}
We can call the render method like this:
class Parent extends React.Component {
render() {
<Child /> //This is how I use the Child class
}
}
Now the reason why react is so performant is that the child cannot be re-rendered unless we do 1 of two things:
It is a component with a state and we call a method setState()
We pass down new props to a child component from the parent component
You can read about it here
Now the only way to get React to call that render function again is by doing those two things.

Resources