Table doesn't update after create, edit or delete new row - javascript

I'm using ReactJs with Material-UI to display a table of Car components but never update after Creating, Editing or Deleting a row.
Next is the structure:
class MainCar extends React.Component {
constructor(props) {
super(props);
this.state = {
cars: []
};
this.apiUrl = "http://localhost:8080/api/v1/cars";
this.onCreate = this.onCreate.bind(this);
this.onUpdate = this.onUpdate.bind(this);
this.onDelete = this.onDelete.bind(this);
this.loadFromServer = this.loadFromServer.bind(this);
}
loadFromServer() {
fetch(this.apiUrl)
.then(response => response.json())
.then(json => {
this.setState({
cars: json.cars
});
});
}
onCreate(newCar) {
try {
const result =
fetch(this.apiUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(newCar)
});
} catch(e) {
console.error(e);
}
this.loadFromServer();
}
onUpdate(car, updatedCar) {
try {
const result =
fetch(car._links.self.href, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(updatedCar)
});
} catch(e) {
console.error(e);
}
this.loadFromServer();
}
onDelete(car) {
try {
const result =
fetch(car._links.self.href, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(car)
});
} catch(e) {
console.error(e);
}
this.loadFromServer();
}
render() {
return (
<CarsTable cars={this.state.cars}
onCreate={this.onCreate}
onUpdate={this.onUpdate}
onDelete={this.onDelete} />
);
}
}
class CarsTable extends React.Component {
constructor(props) {
super(props);
}
render() {
const cars = this.props.cars.map(car =>
<Car key={car._links.self.href}
car={car}
onUpdate={this.props.onUpdate}
onDelete={this.props.onDelete} />
);
return (
<Table>
<TableBody>
{cars}
</TableBody>
</Table>
);
}
}
class Car extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<TableRow>
<TableCell>{car.code}</TableCell>
<TableCell>{car.color}</TableCell>
</TableRow>
);
}
}
As you seen, MainCar has cars array on its state, but CarTable and Car have only properties.
When I log on render functions I see data has changed, however the view is not updated.
View only updates
When I press F5 to update page.
Or when I create, update or delete a new Row I see the previous change but not the last one.
I read that React re-render a component when state has changed. Should I set state for CarTable and Car components copying from props? How can I solve this problem?
Thanks in advance.

What is probably happening is that your GET request is returning before your POST/PUT/DELETE is complete. One way to resolve this is to make sure the GET is only fired after the other action has completed by putting it in a then block.
e.g.
onDelete(car) {
try {
const result =
fetch(car._links.self.href, {
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(car)
}).then(a => this.loadFromServer());
} catch(e) {
console.error(e);
}
}

You need to await asynchronous things in JavaScript. Otherwise, you're going to be doing your POST and GET in parallel instead of sequentially. You can use an async function to make this easier.
async onCreate(newCar) {
try {
await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(newCar)
});
} catch(e) {
console.error(e);
}
return this.loadFromServer();
}

Your code looks like it should work (I know that's not the most helpful thing to hear when it clearly deosn't), but here's a few things you could try. Try doing bind(this) on your functions in the constructor, and also try calling this.loadFromServer(), not loadFromServer(). Also, I'm guessing you just left this out of your post but you're missing onDelete
See below (look at constructor and onCreate method):
class MainCar extends React.Component {
constructor(props) {
super(props);
this.loadFromServer = this.loadFromServer.bind(this);
this.onCreate = this.onCreate.bind(this);
this.onUpdate = this.onUpdate.bind(this);
this.state = {
cars: []
};
}
loadFromServer() {
fetch(this.apiUrl)
.then(response => response.json())
.then(json => {
this.setState({
cars: json.cars
});
});
}
onCreate() {
// more code to create...
this.loadFromServer(); // to update cars state
}
onUpdate() {
// more code to put...
this.loadFromServer(); // to update cars state
}
render() {
return (
<CarsTable cars={this.state.cars}
onCreate={this.onCreate}
onUpdate={this.onUpdate}
onDelete={this.onDelete} />
);
}
}

Related

TypeError: this.state.annonces.map is not a function

hi I have an API and from React which represents my frontend I recover the data from the API to display it but from there I encounter this problem.
Thank you in advance for guiding me, explain to me clearly what the problem is
constructor(props) {
super(props);
this.state = {
api: api.api,
annonces: []
}
}
componentDidMount = () => {
console.log(this.state.annonces_id)
this.onLoaodPreloader();
this.getAnnonce();
}
getAnnonce = () => {
var baseApiUrl = this.state.api
axios.get(baseApiUrl + 'annonces' ,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.usertoken}`
}
}).then((response) => {
console.log(response)
this.setState({
annonces: response.data,
})
}).catch((erreur) => {
console.log(erreur)
})
}
render() {
console.log(this);
const { annonces } = this.state
const renderAnnonces = annonces.map((annonces, index) =>{
return (
<div className="col-md-6 col-sm-6 mb-3" key={index}>
{annonces.meeting_type}
</div>
)
});
return <div className="component-annonces">{renderAnnonces}</div>;
}
pretty sure your response.data is not an array. since .map only works if it is an array.
if you console.log('response data type :', typeof response.data) you will see what type the data is.
your data needs to be in an array for the .map to work

Set state data receive from action in componentDidMount

I'm calling an action in componentDidMount as follows
componentDidMount() {
const { allowedEvcCards} = this.props;
allowedEvcCards(id);
}
With these actions i'm doing API calls and receiving some data as the response. I have set the data to a state with my reducer. I want to do some logic in the componentDidMount it self with the data received in the response.
For example in my reducer i'm doing this
case ALLOWED_EVC_SUCCESS:
return {
...state,
allowedEvc: action.data
}
And in componentDidMount i want to use allowedEvc . But it returns undefined as the action call is not complete at the time.
My action
// Get allowed Evc cards
export const ALLOWED_EVC_LOADING = 'ALLOWED_EVC_LOADING';
export const ALLOWED_EVC_SUCCESS = 'ALLOWED_EVC_SUCCESS';
export function allowedEvcCardsLoading() {
return {
type: ALLOWED_EVC_LOADING
}
}
export function allowedEvcCardsSuccess(data) {
return {
type: ALLOWED_EVC_SUCCESS,
data
}
}
export function allowedEvcCards(id) {
return dispatch => {
dispatch(allowedEvcCardsLoading());
axios.get(`${API_URL}/****/****/${id}/*****`, {
headers: {
// 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => {
console.log("Allowed EVC response ", res.data);
if (res.data.success === true) {
dispatch(allowedEvcCardsSuccess(res.data));
} else {
console.log("error");
// alert("error");
}
})
}
}
Unfortunately, componentDidMount is only called when a component is mounted. Unless, you unmount it you can't use that property. However, you could use componentDidUpdate since it is called as soon as it receives props.
Read more on this lifecycle method.
Edit: maybe you could try returning the axios promise along with the data and use it.
// Component
async componentDidMount() {
const { allowedEvcCards} = this.props;
const data = await allowedEvcCards(id);
// ... do something with data
}
// Action
export function allowedEvcCards(id) {
return dispatch => {
dispatch(allowedEvcCardsLoading());
return axios.get(`${API_URL}/****/****/${id}/*****`, {
headers: {
// 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => {
console.log("Allowed EVC response ", res.data);
if (res.data.success === true) {
dispatch(allowedEvcCardsSuccess(res.data));
return res.data;
} else {
console.log("error");
// alert("error");
}
})
}
}

Call the function and get method after receiving the data in componentDidMount()

Expected effect:
In componentDidMount () I download s and saves in the variabletimeId. If timeId is true, passthis.state.timeId to the loadTime () function to https://app/load-id/${id} and call this function. The data returned by this function is saved in the variable checkId. this.state.checkId transfers to theDetails component.
Problem: how to call the function loadId (), after receiving data in componentDidMount ()?
App
class App extends Component {
constructor (props) {
super(props);
this.state = {
checkId: '',
timeId: ''
}
}
componentDidMount() {
axios({
url: `https://app`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => {
this.setState({
timeId: res.data.id,
});
})
.catch(error => {
console.log(error);
})
}
loadId = (id) => { //id ---> this.state.timeId
axios({
url: `https://app/load-id/${id}`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
console.log(response);
this.setState({
checkId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render () {
return (
<div>
<Item
/>
<Details
checkId = {this.state.checkId}
/>
</div>
)
}
}
Details
class Details extends React.Component {
constructor(props) {
super(props);
this.state = {
task: ''
};
}
componentDidUpdate(previousProps, previousState) {
if (previousProps.checkId !== this.props.checkId) {
this.setState({
task: this.props.checkId
})
}
render() {
return (
<div >
</div>
);
}
}
You need to call loadId inside the then function.
axios({
url: `https://app`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => {
this.setState({
timeId: res.data.id,
});
this.loadId(res.data.id);
})
.catch(error => {
console.log(error);
})
You need to bind loadId() to set state and call it when request in componentDidMount() returns response:
class App extends Component {
constructor (props) {
super(props);
this.state = {
checkId: '',
timeId: ''
}
this.loadId = this.loadId.bind(this); // Bind here
}
componentDidMount() {
axios({
url: `https://app`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => {
this.setState({
timeId: res.data.id,
});
this.loadId(res.data.id); // Call loadId
})
.catch(error => {
console.log(error);
})
}
//...
}

Having trouble looping through array object to create list items

I'm able to loop through JSON data to create an array filled with numbers, but when I go to create the list items it doesn't work. The component just renders an empty list.
When I console.log(ticketNumbers) right before the map function, it shows as a collapsed Array [] until I expand it (it then shows all the values)
function apiCall() {
var ticketNumbers = [];
var memId = 'xxx';
var myInit = {
method: 'GET',
mode: 'cors',
headers: {
'authorization': "xxx",
'Access-Control-Allow-Origin':'*',
'content-type': "application/json",
'cache-control': "no-cache"
},
params: {
'orderBy': 'status/name asc',
'pageSize': 300,
'conditions': "resources contains '" + memId + "' AND status/id not in (17,165,36,163,164,42,73,46,78,148,34,132,45,159,60,168,106,51,72,95)"
}
};
axios.get('Url', myInit)
.then((response) => {
console.log(response.data)
for (var ticket in response.data) {
ticketNumbers.push(response.data[ticket].id)
};
})
return ticketNumbers
}
class TicketContainer extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
loading: true,
};
}
componentWillMount() {
this.setState({
data: {
numbers: apiCall()
},
loading: false
})
};
render() {
return(
<div>
{this.state.loading ? 'Loading' : <Tickets data={this.state.data} />}
</div>
)
}
}
class Tickets extends Component {
render() {
const stuff = this.props;
var ticketList = stuff.data.numbers;
console.log(ticketList);
return(
<div>
<ul>Ticket Number
{ticketList.map((ticket, index) => {
return <li key={index}>sweet</li>;
})}
</ul>
</div>
);
}
}
You should correctly use Promise to solve this. First, let's change apiCall so that it will return a Promise:
function apiCall() {
var ticketNumbers = [];
var memId = 'xxx';
var myInit = {
method: 'GET',
mode: 'cors',
headers: {
'authorization': "xxx",
'Access-Control-Allow-Origin':'*',
'content-type': "application/json",
'cache-control': "no-cache"
},
params: {
'orderBy': 'status/name asc',
'pageSize': 300,
'conditions': "resources contains '" + memId + "' AND status/id not in (17,165,36,163,164,42,73,46,78,148,34,132,45,159,60,168,106,51,72,95)"
}
};
return axios.get('Url', myInit)
.then((response) => {
console.log(response.data)
for (var ticket in response.data) {
ticketNumbers.push(response.data[ticket].id)
}
return ticketNumbers;
});
}
You know have a Promise based api that can be used like this:
apiCall().then(ticketNumbers => console.log(ticketNumbers);
We just need to modify componentWillMount know:
componentWillMount() {
apiCall().then(numbers => this.setState({ loading: false, data: numbers });
}
The apiCall function calls an API which is asynchronous process and returning ticketNumbers from function request won't return the result as the return statement will be executed before the API response is ready and ticketNumbers array is populated.
The easiest way for you to do this is to define this function in the React class and directly setState in the callback of axios request
class TicketContainer extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
loading: true,
};
}
componentWillMount() {
this.apiCall()
};
apiCall =() => {
var memId = 'xxx';
var myInit = {
method: 'GET',
mode: 'cors',
headers: {
'authorization': "xxx",
'Access-Control-Allow-Origin':'*',
'content-type': "application/json",
'cache-control': "no-cache"
},
params: {
'orderBy': 'status/name asc',
'pageSize': 300,
'conditions': "resources contains '" + memId + "' AND status/id not in (17,165,36,163,164,42,73,46,78,148,34,132,45,159,60,168,106,51,72,95)"
}
};
axios.get('Url', myInit)
.then((response) => {
var ticketNumbers = [];
for (var ticket in response.data) {
ticketNumbers.push(response.data[ticket].id)
};
this.setState({data: ticketNumbers, loading: false})
})
}
render() {
return(
<div>
{this.state.loading ? 'Loading' : <Tickets data={this.state.data} />}
</div>
)
}
}
class Tickets extends Component {
render() {
const stuff = this.props;
var ticketList = stuff.data.numbers;
console.log(ticketList);
return(
<div>
<ul>Ticket Number
{ticketList.map((ticket, index) => {
return <li key={index}>sweet</li>;
})}
</ul>
</div>
);
}
}
In case you are wondering why the console.log() statment logs the array, check this answer Value below was evaluated just now in JavaScript object

React - Clicking of link doesn't cause re-rendering of table

***This component makes a REST API call and parses promise value and renders the data in the form of a table.
load(function) makes the API call and takes orderType as input. OrderType is passed as a query parameter from the navigation component which is not included here.
class SampleController extends React.Component {
constructor(props){
super(props);
let orderType = this.props.location.query.orderType;
this.load = this.load.bind(this);
this.load(orderType);
this.state = {orderType: orderType, data: null}
}
load(orderType) {
let self = this;
console.log("order type is" + orderType);
let baseURL = base_urls.orderMetricsBaseURL;
console.log("base URL is", baseURL);
let url = baseURL + "/getData";
let response_data = fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
order_type: orderType
})
})
.then((response) => response.json())
.then((responseJson) => {
let order_data = responseJson;
console.log("responseeeeee is: ", order_data);
self.setState({data: order_data});
self.forceUpdate();
})
.catch((error) => {
console.error(error);
});
}
render() {
var content = <DataTable dataList = {this.state.data} />;
return (
content
);
}
}
export { SampleController as default };
remove this line this.load(orderType); from constructor and put it inside componentDidMount:
constructor(){
//....
}
componentDidMount() {
const orderType = this.props.location.query.orderType;
this.load(orderType);
}
You need to do setState to trigger a rerun/rerender on your component.
From the documentation:
setState() does not update the state immediately, so there's a chance you are losing that update because of the forceUpdate(), which shouldn't be needed as setState() will trigger a re-render by itself. Try removing the forceUpdate() call.
Update:
In case you want to call your load() method each time your orderType you'd need to subscribe to the componentWillReceiveProps method.
Example:
class SampleController extends React.Component {
constructor(props){
super(props);
let orderType = this.props.location.query.orderType;
this.load = this.load.bind(this);
this.load(orderType);
this.state = {orderType: orderType, data: null}
}
componentWillReceiveProps(nextProps) {
const orderType = nextProps.location.query.orderType;
const prevOrderType = this.props.location.query.orderType;
if (prevOrderType !== orderType) {
this.load(orderType);
}
}
load(orderType) {
let self = this;
console.log("order type is" + orderType);
let baseURL = base_urls.orderMetricsBaseURL;
console.log("base URL is", baseURL);
let url = baseURL + "/getData";
let response_data = fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
order_type: orderType
})
})
.then((response) => response.json())
.then((responseJson) => {
let order_data = responseJson;
console.log("responseeeeee is: ", order_data);
self.setState({data: order_data});
})
.catch((error) => {
console.error(error);
});
}
render() {
var content = <DataTable dataList = {this.state.data} />;
return (
content
);
}
}
export { SampleController as default };

Categories