setState does not set the state properly, when dropdown values changes - reactjs

I am having this scenario, where I have a dropdown which pulls the accounts of a user and then based on its selection the content of the page changes. I am trying an approach which is shown below , It seems like setState does not get invoked properly or may be calling sequence may be wrong. Not really getting what is wrong. Changing drop down value does not update the content .
Help would be appreciated.
import * as React from 'react';
import Select from 'semantic-ui-react/dist/commonjs/addons/Select';
interface IState{
accountDetails[], // stores all details w.r.t an account
userAccounts: [], // stores all accounts w.r.t a user
selectedAccount : string, // selected account from the dropdown
[x: string] : any,
}
export default class App extends React.Component<{},IState> {
constructor(props:any){
super(props);
this.state = {
accountDetails: [],
userAccounts: [],
selectedAccount: ''
}
}
dropdownChange = (event: React.SyntheticEvent<HTMLElement>, data:any) => {
this.setState(prevState => ({
selectedAccount: data.value
}), () => {});
}
async componentDidMount()
{
await this.fetchUserAccounts();
}
fetchUserAccounts = async() => {
//fetch API call for getting all user accounts by passing a user ID
// I am able to get the data
fetch('/api/fetch/accounts'
.then(response => response.json())
.then(data => this.setState({ userAccounts: data}));
this.fetchAccountDetails();
}
fetchAccountDetails = async() =>
{
let URL = "/api/fetch/account?accountId=" +this.state.selectedAccount;
fetch('URL'
.then(response => response.json())
.then(data => this.setState({ accountDetails: data}));
}
render() {
return(
<div>
<Select
options={this.state.userAccounts}
name="selectedAccount"
value={this.state.selectedAccount}
onChange={this.dropdownChange}
/>
// component to display the details
<DetailComponent accounts={this.state.accountDetails} />
</div>
)
}
}

You need to call fetchAccountDetails right after changing the state, for the function to invoke using the latest state that the dropdown has changed:
dropdownChange = (event: React.SyntheticEvent<HTMLElement>, data:any) => {
this.setState(prevState => ({
selectedAccount: data.value
}), () => { this.fetchAccountDetails() });
}

the setState function is asynchronous, which means you need to take special precaution when updating state and expecting to use the updated value in the state synchronously. For this reason, the setState function has a second parameter which allows you to specify a callback which is executed when the state has actually been updated. This is where you would need to call the fetchAccountDetails function.
You can find the setState callback parameter described in the React docs here.
But you will have trouble compiling this either way. (a) Your interface should end each member declaration with a semicolon, not a comma. (b) You're missing a ")" on each line with a call to fetch. (c) In the fetchAccountDetails function you declare the URL variable, but then you pass a string 'URL' to the fetch function, not the variable.
Here is a working code sandbox showing the fix to your question, and the syntax fixes. I've commented out the calls to the fetch function since they will fail anyway.
https://codesandbox.io/s/thirsty-rain-2u69e

Related

Passing search results to a component in React [duplicate]

This question already has answers here:
How to pass async state to child component props?
(2 answers)
Closed 10 days ago.
I've built a React app to search recordings, that has the following structure:
SearchScreen => SearchParms
=> SearchResults
SearchScreen is rendered like this:
return(
<Fragment>
<div className='containerCR'><SearchParms getRecordingsHandle = {this.handleGetRecordings} />
<SearchResults recordings = {this.state.recordings}/>
</Fragment>)
A button on the Search Parms screen calls this.handleGetRecordings, which calls an async function to get records, adds them to state.recordings and then passes them to the SearchResults screen.
Problem is, because it's async it passes the recordings object before it's populated.
handleGetRecordings sets the state for the search parameter ani. It needs to be done in a then because it doesn't set it immediately:
handleGetRecordings = (ani) =>
{
console.log(this.state.ani);
console.log("Passed value: ", ani);
this.setState({
ani:ani
}, () => {
console.log("Set state finished:", this.state.ani);
this.fetchRecordings();
});
}
The fetchRecordings function gets the data and then calls setRecordings:
fetchRecordings = async () => {
console.log("getting recordings for: ", this.state.ani);
const myInit = {
headers: {},
response: true,
queryStringParameters: {
ani: encodeURIComponent(this.state.ani)
}
};
console.log("Params", myInit);
API.get(myAPI, path, myInit)
.then(response => {
console.log("Response:", response)
let newRecordings = this.state.recordings;
newRecordings.push(response)
this.setRecordings(newRecordings[0].data)
})
.catch(error => {
console.log(error)
})
}
setRecordings writes the list of recordings to the state, ready to pass to the SearchResults page, except by the time it gets here its missed its chance!
setRecordings =(recordings) =>
{
console.log("Passed value: ", recordings);
this.setState({
recordings:recordings
}, () => {
console.log("Current State: ", this.state.recordings);
});
}
What's the correct way of doing this?
Reading the state the way you're doing here, is not guaranteed to give you the latest state possible.
let newRecordings = this.state.recordings
In order to use your old state to create a new one, you need to pass an updater function to setState as the first argument. Look at the examples here in React docs: https://reactjs.org/docs/react-component.html#setstate
Then you can create your new state based on your old state and set it as the new state by returning it from your updater function. This way you can be sure that your new state is what you expect it to be.
Moreover, since your state is an array, its always passed/refered to by reference and React always suggests to not mutate a state (here, we're mutating it by pushing a value to it), but instead, create a new one by cloning the previous one. So the final setState should look like this:
this.setState((prevState) => {
return {
recordings: [...prevState.recordings, response],
}
})

How to get the data to update using UseState?

I having been trying to update the data using UseState with Typescript. The data is never updated what so ever. I am not sure what is it that I am missing. I just want to make sure that the information from local mock data that accepts those 5 properties and be send or updated to the store data. But whenever the button is pressed I am still get the same information that's already there and the mock data was never imported in. Help me if there's something I am missing or something I may need to change. Thank you, and let me know if you need any more informations.
import verification from '../mock-api/sample/TripVerificationSample.json';
interface VerificationTripProps {
isVisible: boolean;
style?: {};
onModalBack: () => void;
onPress: () => void;
data?: Reservation[];
}
const TripVerificationModal: React.FC<VerificationTripProps> = ({
isVisible = false,
onModalBack,
onPress,
data,
}) => {
console.log('before data is pushed ' + JSON.stringify(data));
const [updateData, setUpdateData] = useState<Reservation[]>([]);
const LoadConfirmInfo = () => {
//set old array to add new array
setUpdateData(previous => [
...previous,
{
confirmNumber: verification.confirmNumber,
startDate: new Date(verification.startDate),
endDate: new Date(verification.endDate),
hotel: verification.hotel,
status: ReservationStatusType.Upcoming,
},
]);
console.log('checking if info is pushed ' + JSON.stringify(updateData));
};
return (
<View>
<BackBaseModal
isVisible={isVisible}
onModalBack={onModalBack}
modalText={tripVerification.headerText}
subText={tripVerification.subText}
buttonText={tripVerification.confirmBtn}
onPress={() => {
onPress();
LoadConfirmInfo();
}}>
</View>
It is not guaranteed that the state is directly updated since setState is an async function! By directly logging the state after setting it, will most likely log the old value.
However, setting a new state causes a rerender, thus it is guaranteed that the new value is available in the next render cycle. This can be validated by using a useEffect with the states value as a dependency.
useEffect(() => {
console.log(updateData)}
, [updateData])

the state is not updating after setting new

I am new to react and facing a problem. I am fetching data from an API using Axios then I have to set that data into state and pass that value in another component as props.
My problem is i am changing state using this.setState after fetching API , but the state is not changing. So I am sharing my code below.
constructor(props){
super(props);
this.state={
employeeData:[] // setting empty value
}
}
ComponentDidMount(){
console.log("Current State"+JSON.stringify(this.state)) ///output = []
axios.get("http://localhost:8080/hris/api/employee/get/all")
/// getting values , can see them in network
.then(response => response.data)
.then((data) => {
this.setState({ employeeData: data }) ///setting new value
console.log(this.state.employeeData) /// can see fetched data
})
.catch(err=> console.log(err))
console.log("2Nd STATE "+this.state) /// again empty state, there is no fetched data
}
Then I have to pass that state in another component.
render(){
return(
<div className=" col-md-12" style={viewData}>
<div >
<p><b>All Employee Details</b></p>
</div>
<Table data={this.state.employeeData}/>
</div>
)
}
setState is async function which takes some time to set your new state values. So printing new state after this line will give you previous state only and not new state.
You need a callback, to check the changed state,
this.setState({ employeeData: data }, () => console.log("2Nd STATE "+this.state))
Another thing is, axios is meant to reduce number of .then(). With axios you will get direct JSON value. You can remove 1 .then().
axios.get("http://localhost:8080/hris/api/employee/get/all") /// getting values , can see them in network
.then(response => {
this.setState({ employeeData: response.data }, () => console.log("2Nd STATE "+this.state)) // This will give you new state value. Also make sure your data is in `response.data` it might be just `response`.
console.log(this.state.employeeData) // This will give you previous state only
})
.catch(err=> console.log(err))
Your console.log("2Nd STATE "+this.state) is returning empty because it probably runs before that axios request completes.
Initially your render method gets called with empty state which is probably throwing an error. You need to handle the render with loading state until your request completes.
For example your render could look like this,
render() {
return (!this.state.employeeData.length) ?
(<div>Loading..</div>) :
(
<div className=" col-md-12" style={viewData}>
<div >
<p><b>All Employee Details</b></p>
</div>
<Table data={this.state.employeeData} />
</div>
)
}
setState is async so you cannot see the change instantly where setState() is called. in order to view the change, you need to do a callback.
this.setState({ employeeData: data },()=>console.log(this.state.employeeData)) ///setting new value
change the code to above format and you can see the change in state once it is changed
I guess this is where you are going wrong give it a try with this. It has got nothing to do with react. The way you used Axios is wrong.
ComponentDidMount(){
console.log("Current State" + JSON.stringify(this.state));
axios
.get("http://localhost:8080/hris/api/employee/get/all")
.then(response => {
this.setState({ employeeData: response.data });
})
.catch(err => console.log(err));
};
Alright, both of these problems are occurring because Axios and this.setState() are asynchronous. Its hard to explain asynchronous programming in a single StackOverflow answer, so I would recommend checking this link: [https://flaviocopes.com/javascript-callbacks/][1]
But for now to get your code to work, switch it to this
ComponentDidMount() {
console.log(this.state); // Obviously empty state at beginning
axios.get("http://localhost:8080/hris/api/employee/get/all")
.then(res => res.data)
.then(data => {
this.setState({employeeData: data}, () => { // Notice the additional function
console.log(this.state); // You'll see your changes to state
})
})
.catch(err => console.log(err));
console.log(this.state); // This won't work because Axios is asynchronous, so the state won't change until the callback from axios is fired
}
The part that most new React developers don't tend to realise is that this.setState() like axios is asynchronous, meaning the state doesn't change immediately, the task of actually doing that gets passed on as a background process. If you want to work with your state after it has changed, the this.setState() function provides a second parameter for doing just that
setState(stateChange[, callback])
taken from the react docs. Here the second parameter is a callback (a.k.a function) you can pass that will only get triggered after the state change occurs
// Assuming state = {name: "nothing"}
this.setState({name: "something"}, () => {
console.log(this.state.name); // logs "something"
});
console.log(this.state.name); //logs "nothing"
Hope this helps!!.

ReactJs: Fetch, transform data with function, then setState

I have managed to fetch data from an API successfully. Data transformation of JSON format works too, but i'm having trouble integrating it to "componentDidMount" to set state with a transformed JSON format. I'm getting an undefined state when i console.log(this.state.races).
I'm also getting this error message:
Can't call setState (or forceUpdate) on an unmounted component.
class Races extends Component {
constructor(props) {
super(props);
this.state = {
races: []};
this.processResults = this.processResults.bind(this);
}
componentDidMount(){
fetch(RACE_SERVICE_URL)
.then(results => results.json())
.then(this.processResults)
}
processResults(data) {
const raceId_arr = data.map(d => d.raceId);
const season_arr = data.map(d => d.season);
const raceName_arr = data.map(d => d.raceName);
const url_arr = data.map(d => d.url);
const data_mapped = {'raceId': raceId_arr, 'season': season_arr, 'raceName': raceName_arr, 'url': url_arr};
this.setState({races:data_mapped});
console.log(data_mapped);
console.log(this.state.races);
}
render() {
const title = 'Race Tracks';
return (
<div>
<h2>{title}</h2>
<RacesViz data= {this.state.races.raceId} />
</div>
);
}
}
export default Races;
I have also tried:
.then(data => this.processResults(data))
What console.log(data_mapped) prints:
{raceId:[1, 2, 3]
raceName:["AGP", "BGP", "CGP"]
season: [2018, 2018, 2018]
url: ["http://en.wikipedia.org/wiki/AGP", "http://en.wikipedia.org/wiki/BGP", "http://en.wikipedia.org/wiki/CGP"]}
setState is async so you can't get immediate result with console.log like you did. Use a callback function instead:
this.setState({races:data_mapped}, () => console.log(this.state.races));
Or you can console.log your state in your render method.
Quote from official docs:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Important!
This makes reading this.state right after calling setState() a potential pitfall.
So you will not get state immediately after setState. You have 2 ways to solve it.
1) You should check in componentDidUpdate hook.
componentDidUpdate(prevProps, prevState) {
console.log(this.state.races);//your data updated here.
}
You can see here to use properly.
2) Or you use callback in setState like this setState(updater, callback):
this.setState({races:data_mapped}, () => {
console.log(this.state.races)//your data updated here.
})

React Parent Component not re-rendering

I have a parent component that renders a list of children pulled in from an API (which functions correctly). Each child has an option to delete itself. When a child deletes itself, I cannot get the parent to re-render. I have read about 50 answers on here related to this topic and tried all of them and nothing seems to be working. I am missing something and stuck.
The component has redux wired in to it, but I have tried the code with and without redux wired up. I have also tried this.forceUpdate() in the callback, which also does not work (I've commented it out in the example code below).
class Parent extends React.Component {
constructor(props) {
super(props)
this.refresh = this.refresh.bind(this)
this.state = {
refresh: false,
}
}
componentDidMount(){
this.getChildren()
}
refresh = () => {
console.log("State: ", this.state)
this.setState({ refresh: !this.state.refresh })
// this.forceUpdate();
console.log("new state: ", this.state)
}
getChildren = () => {
axios.get(
config.api_url + `/api/children?page=${this.state.page}`,
{headers: {token: ls('token')}
}).then(resp => {
this.setState({
children: this.state.children.concat(resp.data.children)
)}
})
}
render(){
return (
<div>
{_.map(this.state.children, (chidlren,i) =>
<Children
key={i}
children={children}
refresh={() => this.refresh()}
/>
)}
</div>
)
}
}
And then in my Children component, which works perfectly fine, and the delete button successfully deletes the record from the database, I have the following excerpts:
deleteChild = (e) => {
e.preventDefault();
axios.delete(
config.api_url + `/api/children/${this.state.child.id}`,
{headers: {token: ls('token')}}
).then(resp => {
console.log("The response is: ", resp);
})
this.props.refresh();
}
render() {
return(
<button class="btn" onClick={this.deleteChild}>Delete</button>
)}
}
I am sure I am missing something simple or basic, but I can't find it.
Your parent render method depends only on this.state.children which is not changing in your delete event. Either pass in the child id to your this.props.refresh method like this.props.refresh(this.state.child.id) and update this.state.children inside the refresh method or call the get children method again once a delete happens
Code for delete method in child
this.props.refresh(this.state.child.id)
Code for parent refresh method
refresh = (childIdToBeDeleted) => {
console.log("State: ", this.state)
this.setState({ refresh: !this.state.refresh })
// this.forceUpdate();
console.log("new state: ", this.state)
//New code
this.setState({children: this.state.children.filter(child => child.id !== childIdToBeDeleted);
}
Few notes about the code. First removing from db and then reloading might be slow and not the best solution. Maybe consider adding remove() function which can be passed to the children component to update state more quickly.
Second if you want to call setState that depends on previous state it is better to use the callback method like this (but i think you need something else see below)
this.setState((prevState,prevProps) =>
{children: prevState.children.concat(resp.data.children)})
Lastly and what i think the issue is. You are not actually calling getChildren from refresh method so the state is not updated and if you want gonna reload the whole state from db you shouldn't concat but just set it like this
.then(resp => {
this.setState({children: resp.data.children})
}
Hope it helps.
Edit:
As mentioned in the comments the call to refresh from children should be in promise then

Resources