Cannot update during an existing state transition without any setState in render() - reactjs

I am learning react and I encountered this error and could not find the solution.
The error I recieve is:
Cannot update during an existing state transition (such as within
render). Render methods should be a pure function of props and
state. at clientsDisplay
(http://localhost:3000/static/js/main.chunk.js:672:39) at div at App
(http://localhost:3000/static/js/main.chunk.js:163:5)
The problem is in this code:
import React from 'react'
import Client from './Client/Client'
const clientsDisplay=(props)=>props.clientsArray.map(
(client,index) =>{
return <Client
clientName={client.name}
clientProjDeadline={client.deadline}
clickDel={props.clientDel(index)}
key={client.id}
changed={event=>props.changed(event,client.id)}
len={props.clArraylength}
/>}
)
export default clientsDisplay
The main component which contains the render function looks like this:
import appCss from './App.module.css';
import React,{Component} from 'react';
import ClientsDisplay from './components/ClientHandle/ClientsDisplay';
class App extends Component{
state = {
userName:'Julian',
showPara: false,
clientsArray: [
{
id:"001",
name: "Max",
deadline: "2021/05/17"
},
{
id:"002",
name: "James",
deadline: "2021/12/06"
},
{
id:"003",
name: "Johnny",
deadline: "2021/07/21"
}
]
}
deleteClient = (delClientIndex)=>{
let clientsArrayCopy=[...this.state.clientsArray]
clientsArrayCopy.splice(delClientIndex,1)
this.setState({
clientsArray:clientsArrayCopy
})
}
valueChanger = (event)=>{
this.setState({
userName: event.target.value
})
}
valueChangerClient = (event,id)=>{
let targetInd=this.state.clientsArray.findIndex(elem=>elem.id===id)
let changedClientArray=[...this.state.clientsArray]
changedClientArray[targetInd].name=event.target.value
this.setState({
clientsArray:changedClientArray
})
}
togglePara = ()=>{
this.setState({
showPara: !this.state.showPara
})
}
render(){
let clientArraylength=this.state.clientsArray.length
return(
<div className={appCss.App}>
<ClientsDisplay
clientsArray={this.state.clientsArray}
changed={this.valueChangerClient}
clientDel={this.deleteClient}
clArrayLength={clientArraylength}/>
</div>
)
}

Currently you're actually calling props.clientDel on every render:
clickDel={props.clientDel(index)}
should be
clickDel={() => props.clientDel(index)}

Related

Unable to use usehistory in class component, example of withrouter

I have below code:-
import React, { Component } from "react";
import { useHistory } from "react-router-dom";
class clusterServersDropdown extends Component {
constructor() {
super();
this.state = {
clusterslist: [],
servertype: [],
selectserver: "",
selectcluster: ""
};
}
componentDidMount() {
this.setState({
clusterslist: [
{ label: "cluster1", servertype: ["test1", "test2", "test3"] },
{ label: "cluster2", servertype: ["test1", "test2", "test3"] }
]
});
}
selectclusterChange(e) {
this.setState({ selectcluster: e.target.value });
this.setState({
servertype: this.state.clusterslist.find(
(x) => x.label === e.target.value
).servertype
});
}
routeChange = (e) => {
this.setState({ selectserver: e.target.value}, () => {
console.log(this.state.selectserver);
let path = "http://localhost:3000/inventory/cluster/" + this.state.selectcluster +"/servertype/" + this.state.selectserver;
console.log(path);
withRouter(path);
});
};
render() {
return (
<div>
<center>
<h1>
Implement cascading Dropdown list
<h2>
ReactJS tutorials
<hr />
<select
value={this.state.selectcluster}
onChange={this.selectclusterChange.bind(this)}
>
<option>-- Select --</option>
{this.state.clusterslist.map((x) => {
return <option>{x.label}</option>;
})}
</select>
<select
value={this.state.selectserver}
onChange={this.routeChange}
>
<option>--------selection disabled------</option>
{this.state.servertype.map((x) => {
return <option>{x}</option>;
})}
</select>
</h2>
</h1>
</center>
</div>
);
}
}
export default clusterServersDropdown;
Based on the output that I get i was trying to redirect to another link after creating the link here. When i do console.log my link gets printed as http://localhost:3000/inventory/cluster/cluster1/servertype/test1 to which I need to redirect. I have used usehistory in past but as its a hook, i am unable to use it here. Any ideas how can i use withrouter here?
withRouter is a Higher Order Component, import it and decorate the ClusterServersDropdown component.
import { withRouter } from "react-router-dom";
class ClusterServersDropdown extends Component {
...
}
export default withRouter(ClusterServersDropdown);
This injects route props (history, location, match) into your class component. Access the history object from props.
routeChange = (e) => {
this.setState({ selectserver: e.target.value}, () => {
console.log(this.state.selectserver);
let path = "http://localhost:3000/inventory/cluster/" + this.state.selectcluster +"/servertype/" + this.state.selectserver;
console.log(path);
this.props.history.push(path);
});
};
You can use hooks only in function components.
This is class component so you will need to use withRouter function when exporting clusterServersDropdown
export default withRouter(clusterServersDropdown);
and then you can use history object with
this.props.history

How do you filter array of objects in react using setState?

I have a simple array of objects but I can't seem to update state with the filtered values. If you console.log() the filteredData variable, the data is filtering correctly. However if I use the same variable inside setState() the filtered results aren't returning when console logging the people array. Does anyone know why this is happening? I'd also like to be able to re-render the list of filtered results. Do I need to use .map() inside the setState() method?
Thanks in advance.
import React from 'react';
import ReactDOM from 'react-dom';
import { v4 as uuidv4 } from 'uuid';
class App extends React.Component {
constructor(props) {
super(props);
this.handleSearch = this.handleSearch.bind(this);
this.state = {
people: [
{ id: uuidv4(), name: 'dave' },
{ id: uuidv4(), name: 'bryan' },
{ id: uuidv4(), name: 'abi' },
{ id: uuidv4(), name: 'chris' },
],
text: ''
}
}
handleSearch(e) {
const value = e.target.value.toLowerCase()
this.setState((prevState) => ({ text: value }));
}
render() {
const { people, text } = this.state;
const filteredData = people.filter((person) => {
return person.name.toLowerCase().includes(text.toLowerCase())
})
return (
<div>
<input type="text" name="searchPeople" placeholder="Search..." onChange={ this.handleSearch } />
<ul>
{
filteredData.map((person) => (<li key={ person.id }>{ person.name }</li>))
}
</ul>
</div>
);
}
}
const root = document.querySelector('#appRoot');
ReactDOM.render(<App />, root);
Edit both setState to retain the previous state unchanged properties this way:
this.setState({
...this.state,
people: filteredData,
});
this.setState({ ...this.state, filters: { text: value } });
Like #Cybershadow mentioned in the comment above, setState is asynchronous. And your log is being triggered before the value in this.state.people changes i.e. logged the previous state value. You can use a setState callback function to make use of the changed data state after a setState update is completed. And to use the setState callback, you need to pass the callback function as an second argument to the setState() method. In your case something like this:
this.setState(
{people: filteredData},
()=>console.log(this.state.people) //callback
);
More on React's setState() method.
#mjwals as setState is non concurrent the refreshed state will not be accessible quickly, so you can compose a callback work in the setState strategy inside the callback you will get the refreshed state, so from that point you can do other activity with the refreshed information. genuinely take a look at the code underneath
import React from 'react';
import "./styles.css";
import { v4 as uuidv4 } from 'uuid';
class App extends React.Component {
constructor(props) {
super(props);
this.handleSearch = this.handleSearch.bind(this);
this.state = {
people: [
{ id: uuidv4(), name: 'dave' },
{ id: uuidv4(), name: 'bryan' },
{ id: uuidv4(), name: 'abi' },
{ id: uuidv4(), name: 'chris' }
],
text: ''
}
}
handleSearch(e) {
const value = e.target.value.toLowerCase()
this.setState({ text: value }, () => {
const { people, text } = this.state;
const filteredData = people.filter((person) => {
return person.name.toLowerCase().includes(text.toLowerCase())
})
this.setState({ people: filteredData })
});
}
render() {
const { people } = this.state;
return (
<div>
<p>Please enter a input to search</p>
<input type="text" name="searchPeople" placeholder="Search..." onChange={this.handleSearch} />
<ul>
{people.map((person) => <li key={person.id}>{person.name}</li>)}
</ul>
</div>
);
}
}
export default App;

Show a Simple Animation on setState in react

I display a data from JSON file to the DIV in my component.
I Set timeout for few seconds and after that the data displays.
I want to show a simple animation when the state changes to true.
Here is a sample of my Code Structure:
import someData from "../Data";
export default class Example extends Component {
constructor(props) {
super(props);
this.state = { contentLoad: false }
}
componentDidMount() {
setTimeout(() => {
this.setState({
contentLoad: true
})
}, 2500)
}
render() {
return (
<div>
{someData.map((someData) => {
return (
<div> {this.state.contentLoad && someData.name}</div>
)
})}
</div>
)
}
}
I read about react transition group, but cant understand cause i'm new to react. someone please use this as a template and provide me a codepen or codesandbox link for solution.
I agree with #JohnRuddell that Pose is a heavy library if all you need is a simple fade in. However I would look it if you are doing multiple animations that are more complex.
Sample code:
import React from "react";
import ReactDOM from "react-dom";
import posed from "react-pose";
import "./styles.css";
const AnimatedDiv = posed.div({
hidden: { opacity: 0 },
visible: { opacity: 1 }
});
class Example2 extends React.Component {
constructor(props) {
super(props);
this.state = { contentLoad: false };
}
componentDidMount() {
setTimeout(() => {
this.setState({
contentLoad: true
});
}, 2500);
}
someData = ["hello", "hi", "test"];
render() {
return (
<AnimatedDiv pose={this.state.contentLoad ? "visible" : "hidden"}>
{this.someData.map(t => {
return <div>{t}</div>;
})}
</AnimatedDiv>
);
}
}
ReactDOM.render(<Example2 />, document.getElementById("root"));
Sandbox link

How do I only render one result in a separate component using axios in React?

Edit//
I suppose my question isn’t so clear. I’m trying to get one park returned when my url points to http://localhost:1233/details/‘${parkcode}’. I’ve defined the param for the url in my results.js file. But I’m having trouble in defining the this.setState in my details.js to render just one result of the park based on the id which also happens to be the park code.
I'm new to React (and possibly to JavaScript, I don't know anymore). I am following a tutorial - instead of using an npm package for an API I decided to branch out and use axios.get() to fetch data from an API. I am able to render the results from a component into the browser, however after adding on reach-router (I assume it's similar to React Router), I am having troubles rendering just one result of my API call as the page I am attempting to build is supposed to only show ONE result based on the ID I have defined.
In my main file, which is Results.js here, I am able to get the data with no problem and include them in my file using JSX and render them. I'm attempting to use the same logic as I did in that page in my Details.js page (which is the page that is supposed to show only one result to the ID in the route).
How I'm using axios in Results.js
componentDidMount() {
axios
.get(
"https://developer.nps.gov/api/v1/parks?stateCode=wa&fields=images&api_key=" +
`${nps}`
)
// https://css-tricks.com/using-data-in-react-with-the-fetch-api-and-axios/
.then(res =>
res.data.data.map(park => ({
description: `${park.description}`,
fullname: `${park.fullName}`,
states: `${park.states}`,
parkcode: `${park.parkCode}`,
image: `${park.images[0] ? park.images[0].url : "No Image"}`,
designation: `${park.designation}`
}))
)
.then(parks => {
this.setState({
parks
});
console.log(parks);
});
}
How I'm attempting to use the same logic in Details.js
It's not recognizing park.name even though I did the API call. However, if I hard code park[0].name it works. I have no idea what I'm doing wrong here. It might be an obvious problem but help me.
class Details extends React.Component {
constructor (props) {
super(props);
this.state = {
loading: true,
}
}
componentDidMount() {
axios
.get(
"https://developer.nps.gov/api/v1/parks?stateCode=wa&fields=images&api_key=" +
`${nps}`,
{ id: this.props.id }
).then(res => {
const park = res.data.data.map(park => ({
description: `${park.description}`,
fullname: `${park.fullName}`,
states: `${park.states}`,
parkcode: `${park.parkCode}`,
image: `${park.images[0] ? park.images[0].url : "No Image"}`,
designation: `${park.designation}`
}))
console.log(park.name);
this.setState({
name: park.name;
loading: false
})
}).catch(err => {
this.setState({error: err});
})
}
I'm expecting the page to recognize the id as defined in the GET request along with the axios, and render the park details in relation to the id. But now, it's doing none of it and I've been stuck on this for forever :(
There are some unnecessary parts in your code. You don't need to construct your data as you do in your setState part. You are getting park list and it is already a structured data. So, just set your state with the data you get back.
After that, you can map over this data and render the parks with links for React Router. You can use parkCode as your URL param for Link. In Details component you can extract this parkCode and make a new request for park details, then set this to your state.
I'm providing an example.
index.js
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Results from "./Results";
import Details from "./Details";
const Routes = () => (
<Router>
<Switch>
<Route exact path="/" component={Results} />
<Route path="/details/:parkCode" component={Details} />
</Switch>
</Router>
);
ReactDOM.render(<Routes />, document.getElementById("root"));
Results
import React from "react";
import axios from "axios";
import { Link } from "react-router-dom";
class Results extends React.Component {
state = {
parks: [],
loading: true,
};
componentDidMount() {
axios(
"https://developer.nps.gov/api/v1/parks?stateCode=wa&fields=images&api_key=LbqZVj21QMimfJyAHbPAWabFaBmfaTZtseq5Yc6t"
).then(res => this.setState({ parks: res.data.data, loading: false }));
}
renderParks = () =>
this.state.parks.map(park => (
<Link to={`/details/${park.parkCode}`} key={park.parkCode}>
<div>{park.fullName}</div>
</Link>
));
render() {
return (
<div>{this.state.loading ? <p>Loading...</p> : this.renderParks()}</div>
);
}
}
export default Results;
Details
import React from "react";
import axios from "axios";
class Details extends React.Component {
state = { park: "", loading: true };
componentDidMount() {
const { match } = this.props;
axios(
`https://developer.nps.gov/api/v1/parks?parkCode=${match.params.parkCode}&api_key=${nps}`
).then(res => this.setState({ park: res.data.data[0], loading: false }));
}
render() {
return (
<div>
{this.state.loading ? <p>Loading...</p> : this.state.park.description}
</div>
);
}
}
export default Details;
You can try this in .then()
let park = res.data.data.map(park => ({
description: `${park.description}`,
fullname: `${park.fullName}`,
states: `${park.states}`,
parkcode: `${park.parkCode}`,
image: `${park.images[0] ? park.images[0].url : "No Image"}`,
designation: `${park.designation}`
}))
park = park[0]; // convert arrays of parks to single park
console.log(park.fullname); // now you can use `park.fullname`
or this
const park = {
description: `${res.data.data[0].description}`,
fullname: `${res.data.data[0].fullName}`,
states: `${res.data.data[0].states}`,
parkcode: `${res.data.data[0].parkCode}`,
image: `${res.data.data[0].images[0] ? park.images[0].url : "No Image"}`,
designation: `${res.data.data[0].designation}`
}
console.log(park.fullname); // now you can use `park.fullname`
otherwise do it in API
I think you can first set a state for your responses and then try to show them
same this :
state = {
result: []
}
componentDidMount() {
axios
.get("https://developer.nps.gov/api/v1/parks?stateCode=wa&fields=images&api_key=" +`${nps}`).then((res) => {
this.setState({result: res.data.data})
})
}
render(){
const result = this.state.result.map((el, index) => {
return(
//data
)
})
return(
<div>
{result}
</div>
)
}
I believe this is the part you are getting wrong
const parks = res.data.data.map(park => ({
description: `${park.description}`,
fullname: `${park.fullName}`,
states: `${park.states}`,
parkcode: `${park.parkCode}`,
image: `${park.images[0] ? park.images[0].url : "No Image"}`,
designation: `${park.designation}`
}))
console.log(parks) // this should display your array of all parks
this.setState({
parks,
loading: false
})
displayParks(parks) {
const allParks = parks.map((park, index) => {
return <div key={park.parkCode}>{park.fullname}<div>
})
}
render() {
const { parks } = this.state;
const displayParks = parks && parks.length > 0 ? this.displayParks(parks) : <div>Loading parks</div>
return (
<div>{ displayParks }</div>
);
}
When you do a .map on an array you are basically creating another array and that is what is returned to your park variable.
So in your render method, you can then loop over every item in parks

TODO project is not woking properly

Components ->
Box
Todolist
Add
AddModal
Main component App
But it is not working that is when I add a new task. It does not get added properly.
I think I cannot use this.setstate twice in a function.
Hope I am correct
Here is given the main component.
App.js :
import React, { Component } from 'react';
import './App.css';
import Box from './Components/Box';
import Add from './Components/Add';
import Todolist from './Components/Todolist';
class App extends Component {
constructor(props) {
super(props);
this.state = {
lists: '',
inputValue: '',
itemArray: []
}
}
onAddTask = () => {
this.setState ({
lists: this.state.inputValue
});
const item = this.state.itemArray;
const title = this.state.lists;
item.push({ title })
this.setState(prevState => ({
itemArray: [...prevState.lists, title]
}))
}
updateInputValue = (event) => {
this.setState({
inputValue: event.target.value
});
}
render() {
let length = this.state.itemArray.length;
return (
<div className="App">
<Box createTodo = {
<div>
{this.state.itemArray.map((itemArr) => {
return (
<div className="box">
<Todolist tasks = {itemArr} />
</div>
)
})}
</div>
}>
</Box>
<Add addTask = {this.onAddTask} inputValues = {this.updateInputValue} inputV = {this.state.inputValue} />
</div>
);
}
}
export default App;
Your addTasks function is not correct, you are mixing up things here.
In your inputValue you save the current value from the input field right? So if you write the following
this.setState({
lists: this.state.inputValue
});
you set your todo list to this single value. And your todo list is not an array anymore.
Secondly, state is imutable. So if you write the following
this.state.itemArray.push({ title });
the state will not be updated. What you actually want is the following:
onAddTask = () => {
this.setState({
itemArray: [...this.state.itemArray, this.state.inputValue]
})
}
And I'm not sure what the lists property on the state is for. You don't use it anywhere besides in your onAddTask function. So I guess you can remove it.

Resources