Is it possible to access state variable outside of react render function? - reactjs

Is it possible to access state variables outside of render(), but inside react custom component method?
componentDidMount() {
fetch(
"some url here"
)
.then(res => res.json())
.then(json => {
const days = this.getRainyDays(json)
this.setState({
data: json,
rainyDays: days
});
});
}
getRainyDays = (data) => {
console.log('data inside getRainyDays', this.state.data) // this.state.data is undefined here
console.log('data inside getRainyDays2', data) // data returns json fine
}
render() {
console.log(this.state.data) // this.state.data is fine too.
return (
<div className="App">
<header className="App-header">
Calendar showing {this.state.rainyDays} rainy days
</header>
<Year weatherData={this.state.data} />
<Weather />
</div>
);
}

Yes, it is possible to access state inside of a component outside of the render method.
According to the React documentation:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
Since your initial state most likely doesn't have the data property set to some value, it comes back as undefined.
You're trying to access this.state.data inside of getRainyDays before you even call this.setState inside of componentDidMount:
componentDidMount() {
fetch(...)
.then(json => {
const days = this.getRainyDays(json)
this.setState(...)
})
}
getRainyDays = (data) => {
console.log('data inside getRainyDays', this.state.data)
}
But even if you called this.getRainyDays after this.setState, you cannot rely on this.setState updating your state immediately.
Again, from React documentation:
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.
If you would like to double check, however, that your state updates correctly, you can pass a callback to setState that will get called after setState updates component's state, like so:
componentDidMount() {
fetch(..)
.then(res => res.json())
.then(json => {
const days = this.getRainyDays(json)
this.setState({
data: json,
rainyDays: days
}, () => {
console.log('data in state', this.state.data)
});
});
}
One thing that I'd recommend, is to check, inside your render function, whether state has the right data in it before you return some JSX. You might run into issues where some components expects an array of items and it renders it immediately, but you're passing undefined to it and it crashes...
render() {
// exit early and return `null` so that nothing gets rendered
// if your API call hasn't finished
if (!this.state.data || !this.state.rainyDays) {
return null
}
return (
<div className="App">
<header className="App-header">
Calendar showing {this.state.rainyDays} rainy days
</header>
<Year weatherData={this.state.data} />
<Weather />
</div>
);
}

Related

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!!.

React setState won't update the view of my table

I am having hard time changing the view of the Bootstrap table, even though the state is being updated.
I did some research and found that this.setState is async, so I made sure to check and change the state in the callback function; however, the state is changed even at the callback function. I am confused if this is still a this.setState problem.
export class EduInfo extends React.Component {
constructor(props){
super(props);
this.state = {
education: this.props.incomingClass.degreeEdu
};
this.updateEdu = this.updateEdu.bind(this);
}
updateEdu = (e) => {
e.preventDefault();
let newEdu = {...this.props.incomingClass};
BackEndRestService.updateEdu(newEdu).then(data => {
this.setState({
education: data.degreeEdu,
}, () => console.log(data));
}).catch(e => {
console.log(e);
});
}
render(){
return(
<BootstrapTable
hover
condensed={true}
bootstrap4={true}
keyField={'id'}
data={this.state.education}
columns={this.columns}
/>
);
}
}
Once the state is updated, it should be re-rendering and updating the 'data={this.state.education}' in the Bootstrap table. However, the table view is not changing.
At your return function have something like:
return(
{ (this.state.readyToShow)?
<div>
<BootStrapTable ...
/>
</div>
: ''
} );
After the state of ReadyToShow is set you should be able to see the Bootstrap table with the info.
And change the state of the state readyToShow (possibly using a callback) only at the end of the response of the request you sent for the data. The problem I see is that your data might not be arriving before react renders. This situation happened to me a lot of times. For example, if using Axios to get data:
val axios4data = axios.get(*some link to the controller to get data*).then(function (response) {
...
*make something with the response and set the state of the varable for the data table of the bootstrap table*
self.setState({education: `dataFromResponse`},
() =>
{
*execute call back or code to set the state of readyToShow to true*
}
)
});
it is important to make the state of ReadyToShow be updated after the state for education was set using the call back for the setState.

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

React component is rendering but not updating when state is updating

React component is showing data when state is null but, when its getting data then its not updating the content of the view.
constructor(props){
super(props);
this.state = {
post: null
}
this.getTotalDownloadSize = this.getTotalDownloadSize.bind(this);
}
componentWillMount(){
const {match} = this.props;
const postId = _.get(match, 'params.id');
getDownloadInfo(postId).then((response) => {
this.setState({
post: _.get(response, 'data')
});
}).catch((err) => {
console.log("an error while fetching data", err);
})
}
inside my render i am getting null value for render(){
render(){
const {post} = this.state;
console.log{post};
const files = _.get(post, 'files', []);
)
initially its showing the null value but after it has value but its not updating the content of the view.
can anyone help me with this.
thanks in advance.
componentDidMount is place where you can place request logic.
componentDidMount() {
const {match} = this.props;
const postId = _.get(match, 'params.id');
getDownloadInfo(postId).then((response) => {
this.setState((state) => ({ post: _.get(response, 'data')}));
}).catch((err) => {
console.log("an error while fetching data", err);
})
}
If your data came from an asynchronous request you should use componentDidMount
Invoked once, only on the client (not on the server), immediately
after the initial rendering occurs. At this point in the lifecycle,
you can access any refs to your children (e.g., to access the
underlying DOM representation). The componentDidMount() method of
child components is invoked before that of parent components.
If you want to integrate with other JavaScript frameworks, set timers
using setTimeout or setInterval, or send AJAX requests, perform those
operations in this method.

Resources