React setState won't update the view of my table - reactjs

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.

Related

How do I make Component rerender when props passed from parent and state changed?

I'm making a Todolist. The Submit component to handle input from user and make PUT request to the server to update the database. The Progress component shows the progress. When user submits, Submit will send props to Progress and Progress will call axios.get to get data and update the state. The props is passed successfully and the Progress state's variables do change but the component is not re-rendered.
submit.js
import Progress from './progress';
export default class Submit extends Component {
constructor(props) {
super(props);
this.state = {
updateProgress: 'No'
}
}
handleSubmit = (e) => {
// ...
this.setState({updateProgress: 'Yes'}));
}
render() {
return (
<div>
<div style={{visibility: 'hidden'}}>
<Progress update={this.state.updateProgress} />
</div>
// Form
</div>
)
}
}
progress.js
export default class Progress extends Component {
constructor(props) {
super(props);
this.state = {
tasksDone: 0
};
}
getData = () => {
axios.get('/task')
.then(res => {
let resData = res.data[0];
this.setState({tasksDone: resData.done});
})
.catch(err => console.log(err));
}
componentDidUpdate(prevProps) {
if (this.props.update != prevProps.update) {
console.log('Props changed');
this.getData();
}
}
componentDidMount() {
this.getData();
}
render() {
return (<div>You have done: {this.state.tasksDone}</div>)
}
}
I know getData() won't be called again after the first submit because the props passed doesn't change but that's not the case because it doesn't work the first submit already.
When I use console.log(this.state.tasksDone) in componentDidUpdate() the tasksDone is updated but the div is not re-rendered, even I tried using forceUpdate() after calling getData().
I have read other questions and they said mutating state doesn't trigger re-render. Is my setState just mutating? I see changing state this way is very common. What am I missing?
Reproduce problem: https://codesandbox.io/s/determined-curie-ipr6v
The problem lies in your use for Progress.
The Progress you are updating is not the Progress that is shown on the display.
https://codesandbox.io/s/vigilant-ardinghelli-vd9nl
this is the working code you provided.

State not changing after calling this.setState

I am getting the data from my form component and trying to set the state of my app component with this data.
However, the state.data is an empty object and is not updating the data. I console log the model data before setting it to check if it exists. Their is data within the model.
import React, { Component, Fragment } from "react";
import Form from "../components/Form";
import product from "./product.json";
class App extends Component {
constructor() {
super();
this.state = {
data: {}
};
}
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
});
console.log("Form: ", this.state);
}
render() {
const fields = product.fields;
return (
<Fragment>
<div>Header</div>
<Form
model={fields}
onSubmit={(model) => {this.onSubmit(model);}}
/>
<div>Footer</div>
</Fragment>
);
}
}
export default App;
setState() is an async call in React. So you won't likely get the updated state value in the next line. To check the updated value on successful state update, you could check in the callback handler.
Change this
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
});
console.log("Form: ", this.state);
}
to
onSubmit = (model) => {
console.log("Outer", model);
this.setState({
data: model
}, () => {
console.log("Form: ", this.state);
});
}
As per the react docs, setState is an asynchronous call. You can ensure your state has updated to perform a particular action in two ways as shown below:
You can pass the setState a function which will have your current state and props and you the value you return will be your next state of the component.
Keep in mind following:
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
Following is an example:
this.setState((state, props) => {
//do something
return {counter: state.counter + props.step};
});
You can pass a callback to the setState function as mentioned in Dinesh's
answer. The callback will be executed once the state has been updated successfully hence ensuring you will have the updated state in the call back.
Following is an example:
this.setState({ ...new state }, () => {
// do something
});
Hope it helps.
I just want to add, that if you will do like this its not going to work:
this.setState({things} , console.log(this.state))
You have to pass a refarence to the call back and not the exscutable code itself. If you won't do so, the function will envoke before the state is updated,even you will see the log.

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