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;
Related
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 });
}
};
I have a parent React component (MainComponent) that renders a child component (theKeyComponent) and passes a constant as a prop (myID). The parent component also tracks the state 'activeLink'.
import theKeyComponent from "../components/theKeyComponent.jsx";
export default class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeLink: '#section1'
}
}
render(){
const myId = this.state.activeLink === '#section1' ? '0001' : '0002';
return(){
<theKeyComponent myID={myID} />
<otherComponent otherPropsHere={otherProps} />
}
}
}
Here's a bit from inside the KeyComponent
export default class HelpScoutBeacon extends React.Component {
static propTypes = {
myID: PropTypes.string.isRequired
}
componentDidMount() {
myAPImethod('init', this.props.myID);
}
render(){return(){}}
}
I want to change the value of the constant myID depending on the value of 'activeLink'. This is not a problem when both components are mounted for the first time. However, when the value of 'activeLink' changes 'myID' doesn't change since the child component is already mounted.
I'm struggling to see what would be the 'React way' of doing this. Should 'myID' be set as another state and the function that sets the state for activeLink should include another one to set the state of myID? Or is this overcomplicating things and there's an easier way to re-render only that particular child component so that it considers the new value of myID.
I'm new to React so I was hoping I could get some clarification form SO community.
This is not a problem when both components are mounted for the first
time. However, when the value of 'activeLink' changes 'myID' doesn't
change since the child component is already mounted.
The issue is with regards to how you handle the trigger of the API call.
componentDidMount will only trigger when the component was initially mounted. This will not be triggered if a state is updated. You are going to want to use componentDidUpdate React Lifecycle as well in augmentation to componentDidMount. componentDidUpdate will trigger when your activeLink state changes because you pass it as props to theKeyComponent
componentDidUpdate() {
myAPImethod('init', this.props.myID);
}
Reference: https://reactjs.org/docs/react-component.html#componentdidupdate
import theKeyComponent from "../components/theKeyComponent.jsx";
export default class MainComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
activeLink: '#section1'
}
}
static getDerivedStateFromProps(props, state) {
if (props.activeLink !== state.activeLink ) {
// updating the state here if there are any changes in props.
return {
activeLink: props.activeLink,
};
}
// Return null if the props hasn't changed
return null;
}
use getDerivedStateFromProps hook to find out changes of props. you need to update the changes of props in state.
render(){
// now, here your state is updated.
const myId = this.state.activeLink === '#section1' ? '0001' : '0002';
return (
<theKeyComponent myID={myID} />
<otherComponent otherPropsHere={otherProps} />
)
}
Any suggestions are welcome.
I think this question has been answer several time but I can't find my specific case.
https://codesandbox.io/s/jjy9l3003
So basically I have an App component that trigger an action that change a state call "isSmall" to true if the screen is resized and less than 500px (and false if it is higher)
class App extends React.Component {
...
resizeHandeler(e) {
const { window, dispatch } = this.props;
if (window.innerWidth < 500 && !this.state.isSmall) {
dispatch(isSmallAction(true));
this.setState({ isSmall: true });
} else if (window.innerWidth >= 500 && this.state.isSmall) {
dispatch(isSmallAction(false));
console.log(isSmallAction(false));
this.setState({ isSmall: false })
}
};
componentDidMount() {
const { window } = this.props;
window.addEventListener('resize', this.resizeHandeler.bind(this));
}
...
I have an other component called HeaderContainer who is a child of App and connected to the Store and the state "isSmall", I want this component to rerender when the "isSmall" change state... but it is not
class Header extends React.Component {
constructor(props) {
super(props);
this.isSmall = props.isSmall;
this.isHome = props.isHome;
}
...
render() {
return (
<div>
{
this.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
...
even if I can see through the console that redux is actually updating the store the Header component is not re-rendering.
Can someone point out what I am missing ?
Am I misunderstanding the "connect()" redux-react function ?
Looking at your code on the link you posted your component is connected to the redux store via connect
const mapStateToProps = (state, ownProps) => {
return {
isHome: ownProps.isHome,
isSmall: state.get('isSmall')
}
}
export const HeaderContainer = connect(mapStateToProps)(Header);
That means that the props you are accessing in your mapStateToProps function (isHome and isSmall) are taken from the redux store and passed as props into your components.
To have React re-render your component you have to use 'this.props' inside the render function (as render is called every time a prop change):
render() {
return (
<div>
{
this.props.isSmall
?
(<div>Is small</div>)
:
(<div>is BIG</div>)
}
</div>
);
}
You are doing it well in the constructor but the constructor is only called once before the component is mounted. You should have a look at react lifecycle methods: https://reactjs.org/docs/react-component.html#constructor
You could remove entirely the constructor in your Header.js file.
You should also avoid using public class properties (e.g. this.isSmall = props.isSmall; ) in react when possible and make use of the React local state when your component needs it: https://reactjs.org/docs/state-and-lifecycle.html#adding-local-state-to-a-class
A component is only mounted once and then only being updated by getting passed new props. You constructor is therefore only being called once before mount. That means that the instance properties you set there will never change during the lifetime of your mounted component. You have to directly Access this.props in your render() function to make updating work. You can remove the constructor as he doesn't do anything useful in this case.
Here I'm trying to get value from DefaultOpts.jsx and update the values to setState in Filters.jsx. But I'm getting error as below :
setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
Filters.jsx
import React from 'react';
import DefaultOpts from 'DefaultOpts.jsx';
export default class Filters extends React.Component {
constructor(props) {
super(props);
this.state = {
vOptions : []
}
this.handleOptions = this.handleOptions.bind(this)
}
handleOptions(params) {
console.log(params)
this.setState({
vOptions : params
});
}
componentDidMount() {
}
componentDidUpdate() {
}
render() {
return (
<div>
<DefaultOpts handleOptions={this.handleOptions.bind(this)} />
</div>
)
}
}
DefaultOpts.jsx
import React from 'react';
class DefaultOpts extends React.Component{
constructor(props) {
super(props);
}
componentDidMount() {
}
componentDidUpdate() {
}
render() {
var optArray = "";
$.ajax({
type: "get",
url: "url-path",
success: function(data) {
optArray = data;
}
});
return (
<div>
{this.props.handleOptions(optArray)}
</div>
)
}
}
export default DefaultOpts;
I got some answers in stackoverflow but I'm not able to get what's issue in my code. Please suggest me here what's wrong in my code..
You can't call this.props.handleOptions inside the render because it will trigger setState of the parent component - and you are still inside the rendering process. That's why it complains.
Try to execute this function inside the componentDidMount (together with your ajax call)
There are several problems with your code:
1) First and main one that results in the mentioned error is the fact that by calling handleOptions in render you are calling setState that in turn starts react life cycle. This is a really bad practice and always should/can be avoided.
2) You have one more async call to $.ajax in render that does not directly result in updating state but still considered a bad practice.
To conclude - your render function must not result in any app logic being performed, its task is to render results that have already been prepared. Do all heavy/async work in componentDidMount/componentDidUpdate and you will be fine.
render will execute before didMount... so you are setting the state before it is mounted
anyway move the $.ajax call to didMount, you shouldn't be doing logic things in render()
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.