I am having a weird problem, I readed some answers about it and some solutions but still can not manage to solve mine, that is my problem, which is well known, ( lot of code I know ) :
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.
This is my component :
class Operations extends Component {
state = {
data: [],
mode: 1, // 1 desktop - 2 phone - 3 bigdesktop - 4 tablette
contratSelected: 0
};
constructor(props) {
super(props);
}
componentDidMount() {
this.setState({ data: !isEmpty(this.props.operations) ? this.props.operations : "" });
//
// Many other methods here, but not needed to show the problem
//
sortDateOperation = ({ order }) => {
const data = partition(this.state.data, item => !item.isChild);
for (let counter = 0; counter < data[0].length; counter += 1) {
data[0][counter].chiffrage = this.dateToNumber(data[0][counter].dateOperation);
}
const result = orderBy(
data[0],
["annee", "chiffrage"],
["desc", order === 1 ? "asc" : "desc"]
);
result.forEach((res, index) => {
res.id = index;
});
// The Line causing error
this.setState({ data: result });
return result;
};
render() {
return (
<Fragment>
<Title text={this.props.title || ""} color="primary" />
{this.state.mode !== 2 && (
<div className="co-table-data">
<div className="co-data-table">
<Grid container>
<Grid item xs={12} sm={12}>
<Datatable
value={this.state.data}
type="grey"
autoLayout
upperCaseHeader
rowGroupHeaderTemplate={data => data.annee}
rowGroupFooterTemplate={() => undefined}
rowGroupMode="subheader"
groupField="annee"
className="co-operations-contrat"
>
<Column
header={intl.get("CONTRAT_DATE_DE_VALEUR")}
field="dateOperation"
sortable="custom"
sortFunction={this.sortDateOperation}
body={this.getDateContent}
/>
<Column
header={intl.get("CONTRAT_TYPE_MOUVEMENT")}
field="typeMouvement"
body={this.getTypeContent}
/>
<Column
header={`${intl.get("MONTANT")}(€)`}
field="montantOperation"
sortable="custom"
sortFunction={this.sortMontanta}
body={this.getMontantContent}
/>
</Datatable>
</Grid>
<Grid item xs={12} />
</Grid>
</div>
</div>
)}
{this.state.mode === 2 && <MobileDatatable />}
</Fragment>
);
}
}
export default Operations;
So when I click on my Columln is the datatable, my dates get sorted, I need to update my state ( data ) but I get this error, here exactly :
....
<Column
header={intl.get("CONTRAT_DATE_DE_VALEUR")}
field="dateOperation"
sortable="custom"
sortFunction={this.sortDateOperation}
body={this.getDateContent}
/>
....
Any help would be much appreciated.
problem is in your Column componet's header prop
header={intl.get("CONTRAT_DATE_DE_VALEUR")}
should be this
header={() => {intl.get("CONTRAT_DATE_DE_VALEUR")}}
you can't execute function directly inside component react will automatically call for you
so, change all three Column component's header property to this
header={() => {intl.get("CONTRAT_DATE_DE_VALEUR")}}
header={() => {intl.get("CONTRAT_TYPE_MOUVEMENT")}}
header={() => {`${intl.get("MONTANT")}(€)`}}
Related
I have the error "Warning: Cannot update a component (App) while rendering a different component (Car). To locate the bad setState() call inside Car" when I run my code. If I take out the function removeFromCart(p), the code is good, but if this function is in App, the code is broken.
function App() {
const [product, setProduct] = useState({
product: data.products,
cartItem:[],
size: "",
sort: ""
})
const removeFromCart = (p) => {
//Copy the recent products in cart
let cartItems = product.cartItem;
//Take out the product you choose in cart
setProduct({
...product,
cartItem: cartItems.filter(x=>x._id!==p._id)
});
}
//Add item to the product.cartItem
const addToCart = (p) => {
//Copy what does the cart has
const cartItems = product.cartItem;
let inCart = false;
//To check the item you click is in the cart or not
cartItems.forEach((item) => {
if (item._id === p._id) {
//If in the cart, the item count +1
item.count++;
//inCart become true to tell next statement the item you click already in in the cart,
//WE don't need to push a need item to the cart.
inCart = true;
}
})
//If the item you click is not in the cart,
//Pushing this to the cart, and set this item's count in 1.
if (!inCart) {
cartItems.push({...p, count:1})
}
//To renew the cartItem in the product state.
setProduct({...product, cartItem:cartItems});
}
//To show the items which has the size you choose
const sizeProducts = (e) => {
//value is the size you choose
let { value } = e.target
//s is the sort parameter you choose
let s = product.sort;
//If you didn't choose any size, show all product.
if (value === "") {
setProduct({ size:"", sort:s, product: data.products });
} else {
//If you choose any size, show the product that have the size you choose,
//and sort it will the sort parameter yuou choose
setProduct({
size: value,
sort:s,
product: data.products.filter(p =>
p.availableSizes.indexOf(value) >= 0
).sort((a, b) =>
s === "lowest" ? ((a.price > b.price) ? 1 : -1) :
s === "highest" ? ((a.price < b.price) ? 1 : -1) :
((a._id > b._id) ? 1 : -1)
)
});
}
}
const sortProducts = (e) => {
//The value is the sort parameter you choose
let { value } = e.target;
//The curr is the product after yiu choose the parameter in the size
let curr = product.product;
//Sort product
setProduct({
sort: value,
product: curr.sort((a, b) =>
value === "lowest" ? ((a.price > b.price) ? 1 : -1) :
value === "highest" ? ((a.price < b.price) ? 1 : -1) :
((a._id > b._id) ? 1 : -1)
)
});
}
return (
<div >
<Grid xs={ 12 }>
<Navigation />
</Grid>
<Grid container>
<Grid xs={0} sm={2}></Grid>
<Grid container xs={12} sm={8}>
<Grid item xs={12}>
<Filter count={product.product.length}
size={product.size}
sort={product.sort}
sizeProducts={sizeProducts}
sortProducts={ sortProducts}
></Filter>
</Grid>
<Product
products={product.product}
addToCart={ addToCart }
></Product>
</Grid>
<Grid xs={0} sm={2}></Grid>
</Grid>
<div className="cartSection">
<Car cartItems={product.cartItem}
removeFromCart={ removeFromCart } />
</div>
</div>
);
}
Car component:
export default class Car extends Component {
render() {
const { cartItems } = this.props;
return (
<Grid container className="cart">
<Grid container xs={12}>
<Grid xs={12} sm={6} md={3} >
<div>
{cartItems.length === 0 ?
(<div>Cart Empty</div>) :
(<div>You have {cartItems.length} in cart</div>)
}
{cartItems.map(item => (
<div key={ item._id } className="cartItem">
<img src={item.image} alt="" />
<div className="info">
<div>
{ item.title }
</div>
<div>
<span>{item.count} X ${item.price}</span>
<Button
variant="contained"
color="primary"
onClick={ this.props.removeFromCart(item) }
>Remove</Button>
</div>
</div>
</div>
))}
</div>
</Grid>
</Grid>
</Grid>
)
}
}
Error:
index.js:1 Warning: Cannot update a component (`App`) while rendering a different component (`Car`). To locate the bad setState() call inside `Car`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at Car
at div
at div
at App
doing
onClick={ this.props.removeFromCart(item) }
will call this.props.removeFromCart immediately, causing the App component to update while rendering Car ( because it's updating its state ), replace it with :
// in the Car component
onClick={ () => this.props.removeFromCart(item) }
see the snippet below for the difference , the first one will call greet even before you click on the button :
const App = () => {
const greet = (msg) => { console.log('hello, ', msg); }
return <div>
<button onClick={greet('world')}>click me</button>
<button onClick={() => greet('world 2')}>click me</button>
</div>
}
ReactDOM.render(<App />, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I converted a class component into a function component using hooks. Currently, I'm struggling to figure out why the checkboxes within this map is not updating with checked value, despite the onChange handler firing, and updating the array as necessary. (The onSubmit also works, and updates the value within the DB properly).
import {
Container,
Typography,
Grid,
Checkbox,
FormControlLabel,
Button
} from "#material-ui/core";
import Select from "react-select";
import localeSelect from "../services/localeSelect";
import {
linkCharactersToGame,
characterLinked,
linkCharacters
} from "../data/locales";
import dbLocale from "../services/dbLocale";
import { LanguageContext } from "../contexts/LanguageContext";
import { UserContext } from "../contexts/UserContext";
import { GameContext } from "../contexts/GameContext";
import { CharacterContext } from "../contexts/CharacterContext";
import { Redirect } from "react-router-dom";
export default function LinkCharacter() {
const { language } = useContext(LanguageContext);
const { user } = useContext(UserContext);
const { games, loading, error, success, connectCharacters } = useContext(
GameContext
);
const { characters } = useContext(CharacterContext);
const [game, setGame] = useState("");
const [selectedCharacters, setSelectedCharacters] = useState([]);
if (!user) {
return <Redirect to="/" />;
}
return (
<section className="link-character">
<Container maxWidth="sm">
<Typography variant="h5">
{localeSelect(language, linkCharactersToGame)}
</Typography>
{error && (
<p className="error">
<span>Error:</span> {error}
</p>
)}
{success && <p>{localeSelect(language, characterLinked)}</p>}
<Select
options={games.map(game => {
return {
label: dbLocale(language, game),
value: game._id
};
})}
onChange={e => {
setGame(e.value);
const selected = [];
const index = games.findIndex(x => x._id === e.value);
games[index].characters.forEach(character => {
selected.push(character._id);
});
setSelectedCharacters(selected);
}}
/>
</Container>
<Container maxWidth="md">
{game !== "" && (
<>
<Grid container spacing={2}>
{characters.map((character, index) => {
return (
<Grid item key={index} md={3} sm={4} xs={6}>
<FormControlLabel
control={
<Checkbox
value={character._id}
onChange={e => {
const index = selectedCharacters.indexOf(
e.target.value
);
if (index === -1) {
selectedCharacters.push(e.target.value);
} else {
selectedCharacters.splice(index, 1);
}
}}
color="primary"
checked={
selectedCharacters.indexOf(character._id) !== -1
}
/>
}
label={dbLocale(language, character)}
/>
</Grid>
);
})}
</Grid>
<Button
variant="contained"
color="primary"
onClick={e => {
e.preventDefault();
connectCharacters(game, selectedCharacters);
}}
>
{localeSelect(language, linkCharacters)}
</Button>
</>
)}
</Container>
</section>
);
}
I feel like there's something I'm missing within Hooks (or there's some sort of issue with Hooks handling something like this). I have been searching and asking around and no one else has been able to figure out this issue as well.
The state returned by [state, setState] = useState([]) is something that you should only be reading from. If you modify it, React won't know that the data has changed and that it needs to re-render. When you need to modify data, you have to use setState, or in your case setSelectedCharacters.
Also, modifying the data by reference might lead to unpredictable results if the array is read elsewhere, later on.
In addition to that, if you give the same value to setState, that the hook returned you in state, React will skip the update entirely. It is not a problem when using numbers or strings, but it becomes one when you use arrays, because the reference (the value React uses to tell if there is a difference) can be the same, when the content might have changed. So you must pass a new array to setState.
With that in mind, your onChange function could look like:
onChange={e => {
const index = selectedCharacters.indexOf(
e.target.value
);
if (index === -1) {
// creating a new array with [], so the original one stays intact
setSelectedCharacters([...selectedCharacters, e.target.value]);
} else {
// Array.filter also creates new array
setSelectedCharacters(selectedCharacters.filter((char, i) => i !== index));
}
}}
Doc is here https://en.reactjs.org/docs/hooks-reference.html#usestate
although I defined a key for SearchDropDownItem it shows a warning
component DropDown
filteredItems.length > 0 ? (
filteredItems.map(item => {
return (
<SearchDropDownItem
item={item}
buttonTitle={{ buttonJoin: content.buttonJoin }}
onItemSelect={onItemSelect}
/>
);
})
) : (
<SearchDropDownItem emptyList={content.noCommunityFound} />
)
searchDropDownItem component :
const SearchDropDownItem = ({
item = { },
onItemSelect,
buttonTitle = "",
emptyList
}) => {
return (
<DropdownItem key={item.id || 1}>
{!emptyList ? (
<Box>
<Span>{item.name} </Span>
<JoinButton
item={item}
index={item.id}
onSuccess={onItemSelect}
content={buttonTitle}
/>
</Box>
) : (
<Box>
<Span>{item.emptyList}</Span>
</Box>
)}
</DropdownItem>
);
};
Warning: Each child in a list should have a unique "key" prop. Check the render method of SearchBox.
in SearchDropDownItem (at SearchBox/index.jsx:52)
You should place the key where you use the SearchDropdownItem, so in the loop.
filteredItems.length > 0 ? (
filteredItems.map(item => {
return (
<SearchDropDownItem
key={item.id} // <-- This is where it has to be
item={item}
buttonTitle={{ buttonJoin: content.buttonJoin }}
onItemSelect={onItemSelect}
/>
);
})
) : (
<SearchDropDownItem emptyList={content.noCommunityFound} />
)
Docs on keys in React: https://reactjs.org/docs/lists-and-keys.html#keys
I got the same warning message:
Warning: Each child in a list should have a unique "key" prop.
However, my problem and solution were a bit different than the accepted answer. I thought adding my solution to this question might help someone.
I am using a 3rd party component, which has a unique key. However, when I used a loop to dynamically generate several instances of the component, I got the warning message above.
The warning disappeared after I added a key prop to the component. This key is NOT part of the props for the component.
let filterJSX = [];
let i = 0;
for (let [key1, value1] of Object.entries(state.filterListNew)) {
i++;
filterJSX.push(
<MultiSelectModalField
key={i.toString()} // I added this one
items={optionList}
uniqueKey="value"
displayKey="name"
// more properties here...
/>
);
}
Why isn't my Chart component re-rendered when I change the state with setState in componentDidMount?
I want to fetch the data from the database and when they are loaded, render the chart. Instead, the chart is rendered with empty data and the data from the database isn't shown.
changeJoystick = () => {
this.setState({robustActive: !this.state.robustActive, compactActive: !this.state.compactActive});
};
async fetchHeatMapData() {
let robustData = [];
let compactData = [];
try {
let response = await getDoubleAxisSegmentAverage();
robustData = response.data;
let {seriesRobust} = this.state;
robustData = robustData.slice(1, 37);
for (let i = 0; i < 6; i++) {
seriesRobust[i].data = robustData.slice(6 * i, 6 * (i + 1));
}
return seriesRobust;
} catch (err) {
console.log(err);
}
}
componentDidMount() {
this.fetchHeatMapData()
.then(seriesRobust => {
this.setState({seriesRobust});
console.log(this.state.seriesRobust);
}
)
}
render() {
let robust_variant = this.state.robustActive ? 'contained' : 'outlined';
let compact_variant = this.state.compactActive ? 'contained' : 'outlined';
return (
<Fragment>
<Grid container direction='row' justify='flex-start'>
<Grid item>
<Button variant={robust_variant} color='secondary' onClick={this.changeJoystick.bind(this)}>Robust
Joystick</Button>
</Grid>
<Grid item>
<Button variant={compact_variant} color='secondary'
onClick={this.changeJoystick.bind(this)}>Compact
Joystick</Button>
</Grid>
</Grid>
<br/>
<Grid container justify='space-evenly'>
<Grid item>
<Chart
options={this.state.options}
series={this.state.robustActive ? this.state.seriesRobust :
this.state.seriesCompact}
type='heatmap'
width={1000}
height={1000}/>
</Grid>
</Grid>
</Fragment>
componentDidMount() {
this.fetchHeatMapData()
.then(seriesRobust => {
this.setState({seriesRobust});
console.log(this.state.seriesRobust);
}
)
}
You should not expect updated state value just after setState call!! Mayority of 'setState not rerendered' questions is about this.
You can do just
componentDidMount() {
this.fetchHeatMapData()
}
and setState() inside fetchHeatMapData() instead of return
let {seriesRobust} = this.state;
this code uses the same ref for object, it's enough to
const seriesRobust = [...this.state.seriesRobust]
this.state.seriesRobust is almost NOT USED in render, it's used conditionally (only if robustActive is true)
series={this.state.robustActive ? this.state.seriesRobust :
I changed my code like this:
componentDidMount() {
this.fetchHeatMapData().then(() => this.forceUpdate());
}
In the fetchHeatMapData() function I set the state with
this.setState({seriesRobust});
When the data has been fetched I'm doing a forceUpdate in componentDidMount() and now it's working as I intended.
I know that you should usually avoid using forceUpdate() but this is the only solution I can think of right now.
setRadio= (id) => {
const {formRating} = this.state;
fetch(`http://localhost:3030/getLessonCondsDB?formId=${id}`)
.then(response => response.json())
.then(response=>{
this.setState({formRating:response.data})
console.log(response.data);})
.catch(err=>console.error(err))
}
The above method assigns the JSON object which is displayed in console as [RowDataPacket {condId: 'C2.1(a)', rate: 3, condition: 'Random text here' }, RowDataPacket {condId: 'C2.2(b)',rate: 3,condition: 'more random text' }]to the state object formRating which is displayed in dev tools as below
formRating: Array
> 0: Object
condId: 'C2.1(a)'
rate: '3',
condition: 'Random text here'
> 1: Object
condId: 'C2.2(b)'
rate: '3',
condition: 'more random text'
Any attempt to console.log(formRating) just prints and empty line on the console.
Instead of fetching from the server I had previously hardcoded this data into an array as below
const formValues= [{condId :'C2.1(a)',rate:'3', condition:'Random text here'},{condId :'C2.2(b)',rate:'3', condition:'more random text'}]
and had a method in another component to create radioGroups mapping each set of conditions allowing users to change the rate value as discussed in How to set defaultValue of a radioGroup from a nested Array object in React state? which works with the hardcoded array but not the JSON array which produces a "TypeError: values.formRating.map is not a function" with the below function in the component where radioGroups are displayed allowing the user to customise the "rate" value.
createRadioGroups = ()=>{
const {values} = this.props;
console.log(values.formRating);
return(values.formRating.map(
item =>
<Grid container>
<Grid item xs={2} style={{marginTop:20, marginRight:0}}>{item.condId} </Grid>
<Grid item xs={6} style={{marginTop:20}}>{item.condition} </Grid>
<Grid item xs={4} style={{marginTop:10}}>
<RadioGroup defaultValue={item.rate} name={item.condId} onChange={this.changeButton(item.condId)} style={{display: 'flex', flexDirection: 'row'}}>
<FormControlLabel value="3" control={<Radio color="primary" />} label=' ' labelPlacement="top"/>
<FormControlLabel value="2" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
<FormControlLabel value="1" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
<FormControlLabel value="N/A" control={<Radio color="primary" />}label=' ' labelPlacement="top"/>
</RadioGroup>
</Grid>
</Grid>
))
};
Any help is appreciated.
That is because the fetch operation within setRadio() is asynchronous, thus any operations that are dependent on the state, or the values from setRadio() will fail. This is why calling createRadioGroups() before setRadio() is returned and completed will result in an undefined value.
I am not sure how exactly is your component structured, but you should handle any subsequent operations within the .then() block,
setRadio= (id) => {
const {formRating} = this.state;
fetch(`http://localhost:3030/getLessonCondsDB?formId=${id}`)
.then(response => response.json())
.then(response=>{
this.setState({formRating:response.data})
console.log(response.data);
// do the rest here
})
.catch(err=>console.error(err))
}
Or if the rendering is handled on the template, you should conditionally call the method only after formRating is populated.
render() {
const { formRating } = this.state;
return <>
{ formRating && formRating.length && this.createRadioGroups() }
</>
}
Or, if createRadioGroups() is on another child component,
render() {
const { values } = this.props;
return <>
{ values && values.formRating && values.formRating.length && this.createRadioGroups() }
</>
}
How are you passing the 'values' prop to the createRadioGroup? Seems like you need to pass it in (see snippet below) then try console logging the entire props object to make sure you are actually receiving it.
createRadioGroups = (props)=>{
const {values} = this.props;
After you check that, then consider when you are calling setRadio? Are you sure the state has already been updated so that it is available when you call createRadioGroup? If it possibly hasn't been updated then you can try initializing your state with the 'shape' of your expected data so that it will render with no data, then rerender once the state is updated. Would look something like this:
this.state = {
formValues=
[
{
condId :'',
rate:'',
condition:''
}
];
Try this
return(
<>
this.props.values&&this.props.values.formRating.map()
</>
)