export class ABC extends React.Component {
constructor(props) {
super(props);
this.state = {
abc: null
};
}
renderOptions() {
this.setState({
abc: abcArray.length !== 0
});
return;
}
renderRadio() {
return (
<Field
id="abc"
name="abc"
values={this.renderOptions()}
/>
);
}
render() {
return (
<div>
{this.renderRadio()}
</div>
);
}
}
export default connect(mapStateToProps)(ABC);
I am trying to setState in renderOptions() which gives me the below error.
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
can't get my head around what im doing wrong here.
any help is appreciated.
in renderRadio() -> values={this.renderOptions()} you are calling the renderOptions function. that function calls the this.setState({abc: abcArray.length !== 0});. then react will try to rerender the component. this will create a loop. to avoid it, you should change values prop to this values={this.renderOptions}
I've refactor your code.
export class ABC extends React.Component {
constructor(props) {
super(props);
this.state = {
abc: null
};
}
renderOptions() {
this.setState({
abc: abcArray.length !== 0
});
return;
}
renderRadio() {
return (
<Field
id="abc"
name="abc"
values={this.renderOptions}
/>
);
}
render() {
return (
<div>
{this.renderRadio()}
</div>
);
}
}
export default connect(mapStateToProps)(ABC);
Your render method is calling setState, which will trigger render to be called again.
You should not call setState inside of renderOptions.
Related
I have a React web application that has three components, a parent and two child subcomponents. I've left out the JavaScript related to <HeaderComponent /> because it isn't relevant to this question.
class App extends Component {
token = null;
constructor(props) {
super(props);
this.state = {
AllInvoices: [],
CurrentInvoice: null
};
this.setCurrentInvoice = this.setCurrentInvoice.bind(this);
}
setCurrentInvoice = (sharedValue) => {
this.setState({
CurrentInvoice: sharedValue
});
}
componentDidMount()
{
fetch("http://api.app.local/api/getALLinvoices", {
"method": "GET"
})
.then(resp => {
this.setState({
AllInvoices: resp.Result
CurrentInvoice: resp.Result[0]
});
});
}
componentDidUpdate()
{
// this DOES trigger
console.log("App componentDidUpdate triggered");
}
render() {
return (
<>
<HeaderComponent AllInvoices={this.state.AllInvoices} setCurrentInvoice={this.setCurrentInvoice} />
<MainFormComponent CurrentInvoice={this.state.CurrentInvoice} />
</>
)
}
}
class MainFormComponent extends Component {
token = null;
constructor(props) {
super(props);
this.state = {
Field1Value: "",
Field2Value: "",
Field3Value: "",
// ...
Field12Value: ""
};
}
componentDidUpdate()
{
//> For some reason this does NOT trigger when CurrentInvoice is updated
console.log("MainFormComponent componentDidUpdate triggered");
}
getInvoiceDetailsAndUpdateForm = () =>
{
fetch("http://api.app.local/api/getinvoicedetails", {
"method": "POST",
"body": {
"invoice_id": this.props.CurrentInvoice.Id
}
})
.then(resp => {
/*
* Run various business logic and update 12+ form fields with AJAX response
*
*/
});
}
render() {
return (
<>
<TextField Value={this.state.Field1Value} />
<TextField Value={this.state.Field2Value} />
<TextField Value={this.state.Field2Value} />
{ /* ... */ }
<TextField Value={this.state.Field12Value} />
</>
)
}
}
class HeaderComponent extends Component {
token = null;
constructor(props) {
super(props);
this.state = {
MiscVariable: ""
};
}
setUpdateCurrentInvoice = (row) =>
{
this.props.setCurrentInvoice(row);
}
render() {
return (
<>
{this.props.AllInvoices.map((row, i) => (
<Button onClick={() => { this.setUpdateCurrentInvoice(row); }} />
))}
</>
)
}
}
Within the App component an AJAX call returns all invoices, and sets an initial value for this.state.CurrentInvoice. Afterwards, buttons in <HeaderComponent /> or <MainFormComponent /> can change CurrentInvoice.
When CurrentInvoice within App is changed, I want to trigger getInvoiceDetailsAndUpdateForm within the <MainFormComponent /> so that I can perform another AJAX call and run other business logic within that component.
What I'm finding is that within <MainFormComponent /> I can't seem to be able to "subscribe" to props.CurrentInvoice value changes. Within the render() method in that Component I see the change. But, that hasn't helped me because I want to trigger getInvoiceDetailsAndUpdateForm.
Does anyone have any ideas on how I can achieve the outcome I want?
Update:
The original code I published did not contain code related to the HeaderComponent. I've now included that to help draw the whole picture.
Basically what's happening is that the user clicks on a button in HeaderComponent which then calls setCurrentInvoice. When this happens componentDidUpdate is triggered, but only in the parent component. I am trying to figure out why componentDidUpdate within MainFormComponent is NOT also firing.
What you are looking for is componentDidUpdate. You can compare prevProps and currentProps and perform necessary actions
you would want to implement this lifecycle method in your MainFormComponent
Also, here's this example which is emulating the behavior you are having.
if you see App is the component storing the state and the handler,
and ChildComponent has the componentDidUpdate in it
now when you click either in App or AnotherChildComponent
it fires the state update, since ChildComponent has ComponentDidUpdate and it is receiving the counter prop you can clearly see it printing the previous and current values.
One thing to note - componentDidUpdate does not get immediately fired but when the state/prop changes so you may not see it fired on the first render but as soon as your api returns something and updates the state you will see it's getting fired for the 1st time in your MainForm component
Also, if you can post some proof of it not firing like screenshot of console.log would be helpful.
I don't see a point of componentDidUpdate not firing.
When chartValue: true an object appears on the chart.
class Chart extends Component {
constructor(props) {
super(props);
this.state = {
chartValue: false,
};
}
componentWillReceiveProps(nextProps) {
this.setState({
chartValue: nextProps.chartValue
});
}
render() {
return (
<div>{this.state.chartValue}</div>
);
}
}
I want to change the value to true via a button in my app component.
class App extends Component {
constructor(props) {
super(props);
this.state = ({
chartValue: false,
});
}
callChartValue() {
this.setState({
chartValue: true
})
}
render() {
return (
<div>
<button onClick={this.callChartValue}>call</button>
<Chart chartValue={this.state.chartValue} />
</div>
)}
}
}
It seems like changing the state in my App.js doesnt translate to a change in the state of the chart component. I've tried without using componentWillReceiveProps but it hasn't worked either.
You have two main issues in your code:
1. You should bind your methods
Otherwise, you won't have access to this inside them.
constructor(props) {
...
// Add this to your constructor
this.callChartValue = this.callChartValue.bind(this);
...
}
2. You shouldn't use strings for referencing the methods
render() {
return (
...
// Wrong
<button onClick="callChartValue">call</button>
// Right
<button onClick={this.callChartValue}>call</button>
...
)}
I am trying to pass a value to child component yet the value doesn't not update as the parent component value changes. Here is my work
Child component:
class Test extends Component {
constructor(props) {
super(props)
this.state = {
data: this.props.data,
}
}
render() {
return(
<div>{this.state.data}</div>
)
}
}
export default Test;
Parent js file
constructor(props) {
super(props)
this.state = {
val: 0,
}
this.addVal = this.addVal.bind(this)
}
addVal() {
let val = this.state.val
val = val + 1
console.log(val)
this.setState({
val
})
}
render() {
return(
<div>
<Button onClick={this.addVal}> add </Button>
<Test data={this.state.val} />
</div>
)
}
The value gets updated on the parent component however, the child component does not get the updated value. What am I missing?
The constructor() only runs once in the start. When the parent is updated it sends new props to the Test but it doesnot change state.data to props.data after the component is rendered first time
You are printing this.state variable which is not updated. Instead you should print value from this.props
render(){
return(
<div>{this.props.data}</div>
)
}
If you want to detect the new props you can use compnentWillRecieveProps()
componentWillRecieveProps(nextProps){
this.setState({data:nextProps.data})
}
state is unnecessary in the child component. It should be enough to render this.props.data since that is what you are passing to the child from the parent.
I realize questions like this have been asked before but from reading several Q&As here it seems like in a lot of cases people are recommending using componentWillUpdate but from my (very) basic understanding of React, if I setState() won't child components re-render if they are affected?
This is my App component (showing the State being set, the function to update the state handleClick, the Display component (which shows the current input from state) and a Button component which shows a number and is passed the function handleClick:
this.State = {
calcValue: 0
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(val) {
this.setState({ calcValue: val })
}
render() {
return(
<div class="calcBody">
<Display currentValue={this.State.calcValue} />
<h1>Calculator</h1>
<div class="numPad">
<Button btn="num col1" operator={1} handleClick={this.handleClick.bind(this)} />
This is the Button component:
class Button extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
/*the button when clicked takes the handleClick function and passes it props based on whatever number is pressed */
<button onClick={() => this.props.handleClick(this.props.operator)}>
<div class={this.props.btn}>{this.props.operator}</div>
</button>
)
}
}
Lastly, this is the Display component:
class Display extends React.Component {
constructor(props){
super(props);
this.props = {
currentValue: this.props.currentValue
}
}
render() {
return(
<h1>{this.props.currentValue}</h1>
);
}
}
I'm wondering why this does not update when handleClick(val) is called?
You're defining state as this.State which is incorrect it should be lowercased: this.state:
this.state = {
calcValue: 0
}
Also, this line:
this.props = {
currentValue: this.props.currentValue
}
doesn't have much sense, as props are passed outside, component shouldn't change them.
I currently have a reducer that does a deep copy of state and returns it with the updated value.
function countableItems(state = initialState, action) {
switch (action.type) {
case types.ADD_TO_SUM:
let denomMap = findDenomination(state.denomGroups, action),
nestedCopy = Immutable.fromJS(state);
return nestedCopy.setIn(['denomGroups', denomMap.group, denomMap.key, denomMap.index, 'sum'], parseFloat(action.value)).toJS();
default:
return state;
}
}
In my render function of the display Component I see the correct updated values in this.props.denoms The render() function builds up child <DenomInput> components, and when I set my breakpoints I see the correct data being passed in
render() {
let denomGroups = this.props.denoms.map((denom, i) => {
return (
Object.keys(denom).map((key) => {
let denoms = denom[key].map((item, i) => {
return <DenomInput denom={item} onDenomChange={this.onDenomChange} key={i}></DenomInput>
});
return (<div className="col"><h2>{key}</h2>{denoms}</div>)
})
);
});
return (
<div className="countable-item-wrapper">
<div className="row">
{denomGroups}
</div>
</div>
);
}
However when the <DenomInput> components render it renders the same value as what they were initially set
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class DenomInput extends Component {
constructor(props) {
super(props);
this.state = { denom: props.denom }
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp = (e) => {
this.props.onDenomChange(e.target.value, this.state.denom.name);
}
render() {
return (
<div className="input-group denom">
<span className="input-group-addon">{this.state.denom.label}</span>
<input
type="text"
className="form-control"
onChange={this.handleKeyUp}
value={this.state.denom.sum} />
<span className="input-group-addon">{this.state.denom.count | 0}</span>
</div>
);
}
}
DenomInput.PropTypes = {
denom: PropTypes.object.isRequired,
onDenomChange: PropTypes.function
}
export default DenomInput;
What piece am I missing to update the view with React and Redux?
May be componentWillReceiveProps can do the trick. It will update the state of the component whenever new data is receive from parent, and call the render function again.
Try
class DenomInput extends Component {
...
componentWillReceiveProps(nextProps) {
this.setState({ denom: nextProps.denom })
}
...
}
It looks like you're seeding your initial state with the props from your store. You then render from the component state, but you never update the component state. They only get set once because constructor is only called once the component is rendered. To fix, either remove this component state entirely and just connect it to the redux store, or update the component state onChange. I recommend removing the local state. I have found that keeping the two states in sync is error-prone.
constructor(props) {
super(props);
this.state = { denom: props.denom }
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp = (e) => {
this.props.onDenomChange(e.target.value, this.state.denom.name);
this.setState({ denom: /*new state identitcal to change in redux store*/ })
}
edit2: An example of raising state up. The steps are:
1. Connect one of your parent components and grab the appropriate slice of state with a mapStateToProps function.
2. Pass the props through your connected parent component to DenomInput.
4. In this.denomsChange, dispatch the appropriate action. It is unclear what this is since you did not include your action in the post.
class DenomInput extends Component {
...
render() {
return (
<div className="input-group denom">
<span className="input-group-addon">{this.props.denom.label}</span>
<input
type="text"
className="form-control"
onChange={this.handleKeyUp}
value={this.props.denom.sum} />
<span className="input-group-addon">{this.props.denom.count | 0}</span>
</div>
);
}
}
export default DenomInput;