React componentDidMount confusion - javascript

I'm confused by some behavior and wondering if someone can help. I have a React component which fetches movie data depending on the filter passed in through props. Using console.log I can see that my componentDidMount() is only being called once, yet each time the component is re-rendered due to receiving different props a state variable that is only set in componentDidMount() changes. My code is quite long so I don't want to post it all but I can if needed. The snippet that is causing my confusion is below:
class MoviesList extends React.Component {
constructor(props) {
super(props);
this.state = {
filterList : [], filter: {}
};
}
async componentDidMount() {
console.log("shouldOnlyCallOnce");
if( this.props.filter.on) {
if (this.props.filter.type == "title"){
try {
const url = "mycustomapi.com/api/"+this.props.filter.title;
const response = await fetch(url);
const jsonData = await response.json();
jsonData.sort((a, b) => a.title.localeCompare(b.title));
console.log("??");
this.setState({filterList: jsonData, filter: this.props.filter});
}
catch (error) {
console.error(error);
}
}
Despite the state.filter only being set there in the whole component, it changes each time my component is reloaded. Does anyone know how this could happen without console logging multiple times?

As I see it, the only explanation is that you are modifying the contents of the filter outside this component.
Consider this:
let filterA = { title: 'foo' }
let filterB = filterA
console.log(filterB.title) // Shows 'foo'
filterA.title = 'bar'
console.log(filterB.title) // Shows 'bar'
Even though we never directly modified filterB here, the value changed. This is because filterA and filterB are referencing the same object, so of course they both change.
If you in your case pass the filter as a prop, and later modify the filter inside the parent, the filter will have changed in the child as well.
As a side note: What you are doing is called deriving state from props, which means that you are using the props to set the state. This is almost never good practice. https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

ComponentDidMount will trigger when a component will do intiate render. After a props or state change, ComponentDidUpdate will trigger .Please check the link : react life cycle example

Related

Why I am getting the old state values after the props change

I just want to understand why in the application I have following situation, below is my constructor of class component:
constructor(props) {
super(props);
this.state = {
tableAlerts: props.householdAlerts,
initialAlerts: props.householdAlerts
}
console.log('householdAlerts', props.householdAlerts)
}
in render function I have:
const { householdAlerts } = this.props;
My issue is that in constructor I got empty array, but in render funtion I have the data. Is it possible to get the data in constructor?
This is a very bad pattern when using the class component. You are ignoring any props updates when you copy the value into state. to manage it:
It requires you to manage two sources of data for the same variable: state and props. Thus, you need to add another render each time your prop change by setting it into state (don't forget to test on equality from prev and next values to avoid being in an infinite loop).
You can avoid setting the state each time your props change by using the getderivedstatefromprops lifecycle method.
So the recommendation is: just use the props; do not copy props into state.
To learn more why you shouldn't, I highly recommend this article.
It is not recommended to set your initial component state in the constructor like so because you gonna lose the ability to use { setState } method after to update this property/state.
The best practice is indeed to refer directly to the prop with { this.prop.householdAlerts }, and keep the state usage for local (or in child components} cases.
if anyhow you want to store props in component state for some reason, call it in lifeCycle -
componentDidMount() {
const { tableAlerts, initialAlerts } = this.props;
this.setState({ tableAlerts, initialAlerts });
}
Hagai Harari is right. Nevertheless, your actual problem seems to be that during your initial rendering the array is empty. Can you ensure that the array has some items, when your component is rendered for the first time?
First rendering -> calls constructor
<YourComponent householdAlerts={[]} />
Second rendering -> updates component
<YourComponent householdAlerts={[alert1, alert2, alert3]} />
If you want initial state to have the prop value.Try something like this with 'this' keyword
constructor(props) {
super(props);
this.state = {
tableAlerts: this.props.householdAlerts,
initialAlerts: this.props.householdAlerts
}
console.log('householdAlerts', props.householdAlerts)
}

Props updates in setInterval not reflected when changing component [duplicate]

Why in the following pseudo-code example Child doesn't re-render when Container changes foo.bar?
Container {
handleEvent() {
this.props.foo.bar = 123
},
render() {
return <Child bar={this.props.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
Even if I call forceUpdate() after modifying the value in Container, Child still shows the old value.
Update the child to have the attribute 'key' equal to the name. The component will re-render every time the key changes.
Child {
render() {
return <div key={this.props.bar}>{this.props.bar}</div>
}
}
Because children do not rerender if the props of the parent change, but if its STATE changes :)
What you are showing is this:
https://facebook.github.io/react/tips/communicate-between-components.html
It will pass data from parent to child through props but there is no rerender logic there.
You need to set some state to the parent then rerender the child on parent change state.
This could help.
https://facebook.github.io/react/tips/expose-component-functions.html
I had the same problem.
This is my solution, I'm not sure that is the good practice, tell me if not:
state = {
value: this.props.value
};
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.setState({value: this.props.value});
}
}
UPD: Now you can do the same thing using React Hooks:
(only if component is a function)
const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
When create React components from functions and useState.
const [drawerState, setDrawerState] = useState(false);
const toggleDrawer = () => {
// attempting to trigger re-render
setDrawerState(!drawerState);
};
This does not work
<Drawer
drawerState={drawerState}
toggleDrawer={toggleDrawer}
/>
This does work (adding key)
<Drawer
drawerState={drawerState}
key={drawerState}
toggleDrawer={toggleDrawer}
/>
Confirmed, adding a Key works. I went through the docs to try and understand why.
React wants to be efficient when creating child components. It won't render a new component if it's the same as another child, which makes the page load faster.
Adding a Key forces React to render a new component, thus resetting State for that new component.
https://reactjs.org/docs/reconciliation.html#recursing-on-children
Obey immutability
Quite an old question but it's an evergreen problem and it doesn't get better if there are only wrong answers and workarounds.
The reason why the child object is not updating is not a missing key or a missing state, the reason is that you don't obey the principle of immutability.
It is the aim of react to make apps faster and more responsive and easier to maintain and so on but you have to follow some principles. React nodes are only rerendered if it is necessary, i.e. if they have updated. How does react know if a component has updated? Because it state has changed. Now don't mix this up with the setState hook. State is a concept and every component has its state. State is the look or behaviour of the component at a given point in time. If you have a static component you only have one state all the time and don't have to take care of it. If the component has to change somehow its state is changing.
Now react is very descriptive. The state of a component can be derived from some changing information and this information can be stored outside of the component or inside. If the information is stored inside than this is some information the component has to keep track itself and we normally use the hooks like setState to manage this information. If this information is stored outside of our component then it is stored inside of a different component and that one has to keep track of it, its theirs state. Now the other component can pass us their state thru the props.
That means react rerenders if our own managed state changes or if the information coming in via props changes. That is the natural behaviour and you don't have to transfer props data into your own state.
Now comes the very important point: how does react know when information has changed? Easy: is makes an comparison! Whenever you set some state or give react some information it has to consider, it compares the newly given information with the actually stored information and if they are not the same, react will rerender all dependencies of that information.
Not the same in that aspect means a javascript === operator.
Maybe you got the point already.
Let's look at this:
let a = 42;
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> false
We are creating an instance of a value, then create another instance, assign the value of the first instance to the second instance and then change the first instance.
Now let's look at the same flow with objects:
let a = { num: 42};
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a.num += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> true
The difference this time is that an object actually is a pointer to a memory area and with the assertion of b=a you set b to the same pointer as a leading to exactly the same object.
Whatever you do in a can be accesed by your a pointer or your b pointer.
Your line:
this.props.foo.bar = 123
actually updates a value in the memory where "this" is pointing at.
React simply can't recognize such alterations by comparing the object references. You can change the contents of your object a thousand times and the reference will always stay the same and react won't do a rerender of the dependent components.
That is why you have to consider all variables in react as immutable. To make a detectable change you need a different reference and you only get that with a new object. So instead of changing your object you have to copy it to a new one and then you can change some values in it before you hand it over to react.
Look:
let a = {num: 42};
console.log('a looks like', a);
let b = {...a};
console.log('b looks like', b);
console.log('is a the same as b?', a === b); // --> false
The spread operator (the one with the three dots) or the map-function are common ways to copy data to a new object or array.
If you obey immutability all child nodes update with new props data.
According to React philosophy component can't change its props. they should be received from the parent and should be immutable. Only parent can change the props of its children.
nice explanation on state vs props
also, read this thread Why can't I update props in react.js?
You should use setState function. If not, state won't save your change, no matter how you use forceUpdate.
Container {
handleEvent= () => { // use arrow function
//this.props.foo.bar = 123
//You should use setState to set value like this:
this.setState({foo: {bar: 123}});
};
render() {
return <Child bar={this.state.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
}
Your code seems not valid. I can not test this code.
You must have used dynamic component.
In this code snippet we are rendering child component multiple time and also passing key.
If we render a component dynamically multiple time then React doesn't render that component until it's key gets changed.
If we change checked by using setState method. It won't be reflected in Child component until we change its key. We have to save that on child's state and then change it to render child accordingly.
class Parent extends Component {
state = {
checked: true
}
render() {
return (
<div className="parent">
{
[1, 2, 3].map(
n => <div key={n}>
<Child isChecked={this.state.checked} />
</div>
)
}
</div>
);
}
}
My case involved having multiple properties on the props object, and the need to re-render the Child on changing any of them.
The solutions offered above were working, yet adding a key to each an every one of them became tedious and dirty (imagine having 15...). If anyone is facing this - you might find it useful to stringify the props object:
<Child
key={JSON.stringify(props)}
/>
This way every change on each one of the properties on props triggers a re-render of the Child component.
Hope that helped someone.
I have the same issue's re-rendering object props, if the props is an object JSON.stringify(obj) and set it as a key for Functional Components. Setting just an id on key for react hooks doesn't work for me. It's weird that to update the component's you have to include all the object properties on the key and concatenate it there.
function Child(props) {
const [thing, setThing] = useState(props.something)
return (
<>
<div>{thing.a}</div>
<div>{thing.b}</div>
</>
)
}
...
function Caller() {
const thing = [{a: 1, b: 2}, {a: 3, b: 4}]
thing.map(t => (
<Child key={JSON.stringify(t)} something={thing} />
))
}
Now anytime the thing object changes it's values on runtime, Child component will re-render it correctly.
You should probably make the Child as functional component if it does not maintain any state and simply renders the props and then call it from the parent. Alternative to this is that you can use hooks with the functional component (useState) which will cause stateless component to re-render.
Also you should not alter the propas as they are immutable. Maintain state of the component.
Child = ({bar}) => (bar);
export default function DataTable({ col, row }) {
const [datatable, setDatatable] = React.useState({});
useEffect(() => {
setDatatable({
columns: col,
rows: row,
});
/// do any thing else
}, [row]);
return (
<MDBDataTableV5
hover
entriesOptions={[5, 20, 25]}
entries={5}
pagesAmount={4}
data={datatable}
/>
);
}
this example use useEffect to change state when props change.
I was encountering the same problem.
I had a Tooltip component that was receiving showTooltip prop, that I was updating on Parent component based on an if condition, it was getting updated in Parent component but Tooltip component was not rendering.
const Parent = () => {
let showTooltip = false;
if(....){ showTooltip = true; }
return(
<Tooltip showTooltip={showTooltip}></Tooltip>
)
}
The mistake I was doing is to declare showTooltip as a let.
I realized what I was doing wrong I was violating the principles of how rendering works, Replacing it with hooks did the job.
const [showTooltip, setShowTooltip] = React.useState<boolean>(false);
define changed props in mapStateToProps of connect method in child component.
function mapStateToProps(state) {
return {
chanelList: state.messaging.chanelList,
};
}
export default connect(mapStateToProps)(ChannelItem);
In my case, channelList's channel is updated so I added chanelList in mapStateToProps
In my case I was updating a loading state that was passed down to a component. Within the Button the props.loading was coming through as expected (switching from false to true) but the ternary that showed a spinner wasn't updating.
I tried adding a key, adding a state that updated with useEffect() etc but none of the other answers worked.
What worked for me was changing this:
setLoading(true);
handleOtherCPUHeavyCode();
To this:
setLoading(true);
setTimeout(() => { handleOtherCPUHeavyCode() }, 1)
I assume it's because the process in handleOtherCPUHeavyCode is pretty heavy and intensive so the app freezes for a second or so. Adding the 1ms timeout allows the loading boolean to update and then the heavy code function can do it's work.
You can use componentWillReceiveProps:
componentWillReceiveProps({bar}) {
this.setState({...this.state, bar})
}
Credit to Josh Lunsford
Considering the rendering limitations with props and the gains we have with states, if you use reaction hooks, there are a few tricks you can use. For example, you can convert props to state manually using useEffect. It probably shouldn't be the best practice, but it helps in theses cases.
import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
export const MyComponent = (props: { users: [] }) => {
const [usersState, setUsersState] = useState([]);
useEffect(() => {
if (!isEqual(props.users, usersState)) {
setUsersState(props.users);
}
}, [props.users]);
<OtherComponent users={usersState} />;
};

NextJS - can't identify where state change is occurring on page component when using shallow Router push

I have an app. that uses NextJS. I have a page that looks like the following:
import React from 'react'
import { parseQuery } from '../lib/searchQuery'
import Search from '../components/search'
class SearchPage extends React.Component {
static getInitialProps ({ query, ...rest }) {
console.log('GET INITIAL PROPS')
const parsedQuery = parseQuery(query)
return { parsedQuery }
}
constructor (props) {
console.log('CONSTRUCTOR OF PAGE CALLED')
super(props)
this.state = props.parsedQuery
}
render () {
return (
<div>
<div>
<h1>Search Results</h1>
</div>
<div>
<h1>DEBUG</h1>
<h2>PROPS</h2>
{JSON.stringify(this.props)}
<h2>STATE</h2>
{JSON.stringify(this.state)}
</div>
<div>
<Search query={this.state} />
</div>
</div>
)
}
}
export default SearchPage
getInitialProps is ran for SSR - it receives the query string as an object (via Express on the back end) runs it through a simple 'cleaner' function - parseQuery - which I made, and injects it into the page via props as props.parsedQuery as you can see above. This all works as expected.
The Search component is a form with numerous fields, most of which are select based with pre-defined fields and a few a number based input fields, for the sake of brevity I've omitted the mark up for the whole component. Search takes the query props and assigns them to its internal state via the constructor function.
On changing both select and input fields on the Search component this code is ran:
this.setState(
{
[label]: labelValue
},
() => {
if (!this.props.homePage) {
const redirectObj = {
pathname: `/search`,
query: queryStringWithoutEmpty({
...this.state,
page: 1
})
}
// Router.push(href, as, { shallow: true }) // from docs.
this.props.router.push(redirectObj, redirectObj, { shallow: true })
}
}
)
The intention here is that CSR takes over - hence the shallow router.push. The page URL changes but getInitialProps shouldn't fire again, and subsequent query changes are handled via componentWillUpdate etc.. I confirmed getInitialProps doesn't fire again by lack of respective console.log firing.
Problem
However, on checking/unchecking the select fields on the Search component I was surprised to find the state of SearchPage was still updating, despite no evidence of this.setState() being called.
constructor isn't being called, nor is getInitialProps, so I'm unaware what is causing state to change.
After initial SSR the debug block looks like this:
// PROPS
{
"parsedQuery": {
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": [],
"lowPrice": "",
"highPrice": ""
}
Then after checking a select field in Search surprisingly it updates to this:
// PROPS
{
"parsedQuery": {
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
}
// STATE
{
"manufacturer": ["Apple"],
"lowPrice": "",
"highPrice": ""
}
I can't find an explanation to this behaviour, nothing is output to the console and I can't find out how to track state changes origins via dev. tools.
Surely the state should only update if I were to do so via componentDidUpdate? And really shouldn't the parsedQuery prop only ever be updated by getInitialProps? As that's what created and injected it?
To add further confusion, if I change a number input field on Search (such as lowPrice), the URL updates as expected, but props nor page state changes in the debug block. Can't understand this inconsistent behaviour.
What's going on here?
EDIT
I've added a repo. which reproduces this problem on as a MWE on GitHub, you can clone it here: problem MWE repo.
Wow, interesting problem. This was a fun little puzzle to tackle.
TL;DR: This was your fault, but how you did it is really subtle. First things first, the problem is on this line:
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L17
Here in this example, it is this:
this.state = props.parsedQuery
Let's consider what is actually happening there.
In IndexPage.getInitialProps you are doing the following:`
const initialQuery = parseQuery({ ...query })
return { initialQuery }
Through Next's mechanisms, this data passes through App.getInitialProps to be returned as pageProps.initialQuery, which then becomes props.initialQuery in IndexPage, and which is then being passed wholesale through to your Search component - where your Search component then "makes a copy" of the object to avoid mutating it. All good, right?
You missed something.
In lib/searchQuery.js is this line:
searchQuery[field] = []
That same array is being passed down into Search - except you aren't copying it. You are copying props.query - which contains a reference to that array.
Then, in your Search component you do this when you change the checkbox:
const labelValue = this.state[label]
https://github.com/benlester/next-problem-example/blob/master/frontend/components/search.js#L57
You're mutating the array you "copied" in the constructor. You are mutating your state directly! THIS is why initialQuery appears to update on the home page - you mutated the manufacturers array referenced by initialQuery - it was never copied. You have the original reference that was created in getInitialProps!
One thing you should know is that even though getInitialProps is not called on shallow pushes, the App component still re-renders. It must in order to reflect the route change to consuming components. When you are mutating that array in memory, your re-render reflects the change. You are NOT mutating the initialQuery object when you add the price.
The solution to all this is simple. In your Search component constructor, you need a deep copy of the query:
this.state = { ...cloneDeep(props.query) }
Making that change, and the issue disappears and you no longer see initialQuery changing in the printout - as you would expect.
You will ALSO want to change this, which is directly accessing the array in your state:
const labelValue = this.state[label]
to this:
const labelValue = [...this.state[label]]
In order to copy the array before you change it. You obscure that problem by immediately calling setState, but you are in fact mutating your component state directly which will lead to all kinds of weird bugs (like this one).
This one arose because you had a global array being mutated inside your component state, so all those mutations were being reflected in various places.

Set state in componentDidMount outside of a callback

I've read that I should not setState directly in componentDidMount, but setting it in a callback is fine (that's why it's the place for ajax calls).
The problem is that I set the state after parsing the search params like this
componentDidMount() {
let price = 0;
const query = new URLSearchParams(this.props.location.search);
for (let param of query.entries()) {
if (param[0] === 'price') {
price = param[1];
}
}
this.setState({ price: price });
}
I then do a conditional rendering: if the price isn't set, I render nothing.
I understand that this will cause 2 renders: The first one will be blank, and the second one will be the intended one, after the price is set.
So, is there any problem with calling setState directly inside componentDidMount? Is there a better alternative?
I don't want to use componentWillMount due to its recent deprecation (react 16.3).
constructor(props){
super(props);
this.state = {
price:false
};
}
Now create a function
togglediv{(this.setState({price:!this.state.price})}
and you can use conditional rendering on the div which you want to show /hide

React: why child component doesn't update when prop changes

Why in the following pseudo-code example Child doesn't re-render when Container changes foo.bar?
Container {
handleEvent() {
this.props.foo.bar = 123
},
render() {
return <Child bar={this.props.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
Even if I call forceUpdate() after modifying the value in Container, Child still shows the old value.
Update the child to have the attribute 'key' equal to the name. The component will re-render every time the key changes.
Child {
render() {
return <div key={this.props.bar}>{this.props.bar}</div>
}
}
Because children do not rerender if the props of the parent change, but if its STATE changes :)
What you are showing is this:
https://facebook.github.io/react/tips/communicate-between-components.html
It will pass data from parent to child through props but there is no rerender logic there.
You need to set some state to the parent then rerender the child on parent change state.
This could help.
https://facebook.github.io/react/tips/expose-component-functions.html
I had the same problem.
This is my solution, I'm not sure that is the good practice, tell me if not:
state = {
value: this.props.value
};
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.setState({value: this.props.value});
}
}
UPD: Now you can do the same thing using React Hooks:
(only if component is a function)
const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
When create React components from functions and useState.
const [drawerState, setDrawerState] = useState(false);
const toggleDrawer = () => {
// attempting to trigger re-render
setDrawerState(!drawerState);
};
This does not work
<Drawer
drawerState={drawerState}
toggleDrawer={toggleDrawer}
/>
This does work (adding key)
<Drawer
drawerState={drawerState}
key={drawerState}
toggleDrawer={toggleDrawer}
/>
Obey immutability
Quite an old question but it's an evergreen problem and it doesn't get better if there are only wrong answers and workarounds.
The reason why the child object is not updating is not a missing key or a missing state, the reason is that you don't obey the principle of immutability.
It is the aim of react to make apps faster and more responsive and easier to maintain and so on but you have to follow some principles. React nodes are only rerendered if it is necessary, i.e. if they have updated. How does react know if a component has updated? Because it state has changed. Now don't mix this up with the setState hook. State is a concept and every component has its state. State is the look or behaviour of the component at a given point in time. If you have a static component you only have one state all the time and don't have to take care of it. If the component has to change somehow its state is changing.
Now react is very descriptive. The state of a component can be derived from some changing information and this information can be stored outside of the component or inside. If the information is stored inside than this is some information the component has to keep track itself and we normally use the hooks like setState to manage this information. If this information is stored outside of our component then it is stored inside of a different component and that one has to keep track of it, its theirs state. Now the other component can pass us their state thru the props.
That means react rerenders if our own managed state changes or if the information coming in via props changes. That is the natural behaviour and you don't have to transfer props data into your own state.
Now comes the very important point: how does react know when information has changed? Easy: is makes an comparison! Whenever you set some state or give react some information it has to consider, it compares the newly given information with the actually stored information and if they are not the same, react will rerender all dependencies of that information.
Not the same in that aspect means a javascript === operator.
Maybe you got the point already.
Let's look at this:
let a = 42;
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> false
We are creating an instance of a value, then create another instance, assign the value of the first instance to the second instance and then change the first instance.
Now let's look at the same flow with objects:
let a = { num: 42};
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a.num += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> true
The difference this time is that an object actually is a pointer to a memory area and with the assertion of b=a you set b to the same pointer as a leading to exactly the same object.
Whatever you do in a can be accesed by your a pointer or your b pointer.
Your line:
this.props.foo.bar = 123
actually updates a value in the memory where "this" is pointing at.
React simply can't recognize such alterations by comparing the object references. You can change the contents of your object a thousand times and the reference will always stay the same and react won't do a rerender of the dependent components.
That is why you have to consider all variables in react as immutable. To make a detectable change you need a different reference and you only get that with a new object. So instead of changing your object you have to copy it to a new one and then you can change some values in it before you hand it over to react.
Look:
let a = {num: 42};
console.log('a looks like', a);
let b = {...a};
console.log('b looks like', b);
console.log('is a the same as b?', a === b); // --> false
The spread operator (the one with the three dots) or the map-function are common ways to copy data to a new object or array.
If you obey immutability all child nodes update with new props data.
Confirmed, adding a Key works. I went through the docs to try and understand why.
React wants to be efficient when creating child components. It won't render a new component if it's the same as another child, which makes the page load faster.
Adding a Key forces React to render a new component, thus resetting State for that new component.
https://reactjs.org/docs/reconciliation.html#recursing-on-children
According to React philosophy component can't change its props. they should be received from the parent and should be immutable. Only parent can change the props of its children.
nice explanation on state vs props
also, read this thread Why can't I update props in react.js?
You should use setState function. If not, state won't save your change, no matter how you use forceUpdate.
Container {
handleEvent= () => { // use arrow function
//this.props.foo.bar = 123
//You should use setState to set value like this:
this.setState({foo: {bar: 123}});
};
render() {
return <Child bar={this.state.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
}
Your code seems not valid. I can not test this code.
You must have used dynamic component.
In this code snippet we are rendering child component multiple time and also passing key.
If we render a component dynamically multiple time then React doesn't render that component until it's key gets changed.
If we change checked by using setState method. It won't be reflected in Child component until we change its key. We have to save that on child's state and then change it to render child accordingly.
class Parent extends Component {
state = {
checked: true
}
render() {
return (
<div className="parent">
{
[1, 2, 3].map(
n => <div key={n}>
<Child isChecked={this.state.checked} />
</div>
)
}
</div>
);
}
}
My case involved having multiple properties on the props object, and the need to re-render the Child on changing any of them.
The solutions offered above were working, yet adding a key to each an every one of them became tedious and dirty (imagine having 15...). If anyone is facing this - you might find it useful to stringify the props object:
<Child
key={JSON.stringify(props)}
/>
This way every change on each one of the properties on props triggers a re-render of the Child component.
Hope that helped someone.
I have the same issue's re-rendering object props, if the props is an object JSON.stringify(obj) and set it as a key for Functional Components. Setting just an id on key for react hooks doesn't work for me. It's weird that to update the component's you have to include all the object properties on the key and concatenate it there.
function Child(props) {
const [thing, setThing] = useState(props.something)
return (
<>
<div>{thing.a}</div>
<div>{thing.b}</div>
</>
)
}
...
function Caller() {
const thing = [{a: 1, b: 2}, {a: 3, b: 4}]
thing.map(t => (
<Child key={JSON.stringify(t)} something={thing} />
))
}
Now anytime the thing object changes it's values on runtime, Child component will re-render it correctly.
You should probably make the Child as functional component if it does not maintain any state and simply renders the props and then call it from the parent. Alternative to this is that you can use hooks with the functional component (useState) which will cause stateless component to re-render.
Also you should not alter the propas as they are immutable. Maintain state of the component.
Child = ({bar}) => (bar);
export default function DataTable({ col, row }) {
const [datatable, setDatatable] = React.useState({});
useEffect(() => {
setDatatable({
columns: col,
rows: row,
});
/// do any thing else
}, [row]);
return (
<MDBDataTableV5
hover
entriesOptions={[5, 20, 25]}
entries={5}
pagesAmount={4}
data={datatable}
/>
);
}
this example use useEffect to change state when props change.
I was encountering the same problem.
I had a Tooltip component that was receiving showTooltip prop, that I was updating on Parent component based on an if condition, it was getting updated in Parent component but Tooltip component was not rendering.
const Parent = () => {
let showTooltip = false;
if(....){ showTooltip = true; }
return(
<Tooltip showTooltip={showTooltip}></Tooltip>
)
}
The mistake I was doing is to declare showTooltip as a let.
I realized what I was doing wrong I was violating the principles of how rendering works, Replacing it with hooks did the job.
const [showTooltip, setShowTooltip] = React.useState<boolean>(false);
define changed props in mapStateToProps of connect method in child component.
function mapStateToProps(state) {
return {
chanelList: state.messaging.chanelList,
};
}
export default connect(mapStateToProps)(ChannelItem);
In my case, channelList's channel is updated so I added chanelList in mapStateToProps
In my case I was updating a loading state that was passed down to a component. Within the Button the props.loading was coming through as expected (switching from false to true) but the ternary that showed a spinner wasn't updating.
I tried adding a key, adding a state that updated with useEffect() etc but none of the other answers worked.
What worked for me was changing this:
setLoading(true);
handleOtherCPUHeavyCode();
To this:
setLoading(true);
setTimeout(() => { handleOtherCPUHeavyCode() }, 1)
I assume it's because the process in handleOtherCPUHeavyCode is pretty heavy and intensive so the app freezes for a second or so. Adding the 1ms timeout allows the loading boolean to update and then the heavy code function can do it's work.
You can use componentWillReceiveProps:
componentWillReceiveProps({bar}) {
this.setState({...this.state, bar})
}
Credit to Josh Lunsford
Considering the rendering limitations with props and the gains we have with states, if you use reaction hooks, there are a few tricks you can use. For example, you can convert props to state manually using useEffect. It probably shouldn't be the best practice, but it helps in theses cases.
import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
export const MyComponent = (props: { users: [] }) => {
const [usersState, setUsersState] = useState([]);
useEffect(() => {
if (!isEqual(props.users, usersState)) {
setUsersState(props.users);
}
}, [props.users]);
<OtherComponent users={usersState} />;
};

Categories

Resources